1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.shared.clocks
18 
19 import android.graphics.Point
20 import android.view.animation.Interpolator
21 import com.android.app.animation.Interpolators
22 import com.android.internal.annotations.Keep
23 import com.android.systemui.monet.Style as MonetStyle
24 import com.android.systemui.shared.clocks.view.HorizontalAlignment
25 import com.android.systemui.shared.clocks.view.VerticalAlignment
26 
27 /** Data format for a simple asset-defined clock */
28 @Keep
29 data class ClockDesign(
30     val id: String,
31     val name: String? = null,
32     val description: String? = null,
33     val thumbnail: String? = null,
34     val large: ClockFace? = null,
35     val small: ClockFace? = null,
36     @MonetStyle.Type val colorPalette: Int? = null,
37 )
38 
39 /** Describes a clock using layers */
40 @Keep
41 data class ClockFace(
42     val layers: List<ClockLayer> = listOf<ClockLayer>(),
43     val layerBounds: LayerBounds = LayerBounds.FIT,
44     val wallpaper: String? = null,
45     val faceLayout: DigitalFaceLayout? = null,
46     val pickerScale: ClockFaceScaleInPicker? = ClockFaceScaleInPicker(1.0f, 1.0f),
47 )
48 
49 @Keep data class ClockFaceScaleInPicker(val scaleX: Float, val scaleY: Float)
50 
51 /** Base Type for a Clock Layer */
52 @Keep
53 interface ClockLayer {
54     /** Override of face LayerBounds setting for this layer */
55     val layerBounds: LayerBounds?
56 }
57 
58 /** Clock layer that renders a static asset */
59 @Keep
60 data class AssetLayer(
61     /** Asset to render in this layer */
62     val asset: AssetReference,
63     override val layerBounds: LayerBounds? = null,
64 ) : ClockLayer
65 
66 /** Clock layer that renders the time (or a component of it) using numerals */
67 @Keep
68 data class DigitalHandLayer(
69     /** See SimpleDateFormat for timespec format info */
70     val timespec: DigitalTimespec,
71     val style: TextStyle,
72     // adoStyle concrete type must match style,
73     // cause styles will transition between style and aodStyle
74     val aodStyle: TextStyle?,
75     val timer: Int? = null,
76     override val layerBounds: LayerBounds? = null,
77     var faceLayout: DigitalFaceLayout? = null,
78     // we pass 12-hour format from json, which will be converted to 24-hour format in codes
79     val dateTimeFormat: String,
80     val alignment: DigitalAlignment?,
81     // ratio of margins to measured size, currently used for handwritten clocks
82     val marginRatio: DigitalMarginRatio? = DigitalMarginRatio(),
83 ) : ClockLayer
84 
85 /** Clock layer that renders the time (or a component of it) using numerals */
86 @Keep
87 data class ComposedDigitalHandLayer(
88     val customizedView: String? = null,
89     /** See SimpleDateFormat for timespec format info */
90     val digitalLayers: List<DigitalHandLayer> = listOf<DigitalHandLayer>(),
91     override val layerBounds: LayerBounds? = null,
92 ) : ClockLayer
93 
94 @Keep
95 data class DigitalAlignment(
96     val horizontalAlignment: HorizontalAlignment?,
97     val verticalAlignment: VerticalAlignment?,
98 )
99 
100 @Keep
101 data class DigitalMarginRatio(
102     val left: Float = 0F,
103     val top: Float = 0F,
104     val right: Float = 0F,
105     val bottom: Float = 0F,
106 )
107 
108 /** Clock layer which renders a component of the time using an analog hand */
109 @Keep
110 data class AnalogHandLayer(
111     val timespec: AnalogTimespec,
112     val tickMode: AnalogTickMode,
113     val asset: AssetReference,
114     val timer: Int? = null,
115     val clock_pivot: Point = Point(0, 0),
116     val asset_pivot: Point? = null,
117     val length: Float = 1f,
118     override val layerBounds: LayerBounds? = null,
119 ) : ClockLayer
120 
121 /** Clock layer which renders the time using an AVD */
122 @Keep
123 data class AnimatedHandLayer(
124     val timespec: AnalogTimespec,
125     val asset: AssetReference,
126     val timer: Int? = null,
127     override val layerBounds: LayerBounds? = null,
128 ) : ClockLayer
129 
130 /** A collection of asset references for use in different device modes */
131 @Keep
132 data class AssetReference(
133     val light: String,
134     val dark: String,
135     val doze: String? = null,
136     val lightTint: String? = null,
137     val darkTint: String? = null,
138     val dozeTint: String? = null,
139 )
140 
141 /**
142  * Core TextStyling attributes for text clocks. Both color and sizing information can be applied to
143  * either subtype.
144  */
145 @Keep
146 interface TextStyle {
147     // fontSizeScale is a scale factor applied to the default clock's font size.
148     val fontSizeScale: Float?
149 }
150 
151 /**
152  * This specifies a font and styling parameters for that font. This is rendered using a text view
153  * and the text animation classes used by the default clock. To ensure default value take effects,
154  * all parameters MUST have a default value
155  */
156 @Keep
157 data class FontTextStyle(
158     // Font to load and use in the TextView
159     val fontFamily: String? = null,
160     val lineHeight: Float? = null,
161     val borderWidth: String? = null,
162     // ratio of borderWidth / fontSize
163     val borderWidthScale: Float? = null,
164     // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
165     val fillColorLight: String? = null,
166     // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
167     val fillColorDark: String? = null,
168     override val fontSizeScale: Float? = null,
169     // used when alternate in one font file is needed
170     var fontFeatureSettings: String? = null,
171     val renderType: RenderType = RenderType.STROKE_TEXT,
172     val outlineColor: String? = null,
173     val transitionDuration: Long = -1L,
174     val transitionInterpolator: InterpolatorEnum? = null,
175 ) : TextStyle
176 
177 /**
178  * As an alternative to using a font, we can instead render a digital clock using a set of drawables
179  * for each numeral, and optionally a colon. These drawables will be rendered directly after sizing
180  * and placing them. This may be easier than generating a font file in some cases, and is provided
181  * for ease of use. Unlike fonts, these are not localizable to other numeric systems (like Burmese).
182  */
183 @Keep
184 data class LottieTextStyle(
185     val numbers: List<String> = listOf(),
186     // Spacing between numbers, dimension string
187     val spacing: String = "0dp",
188     // Colon drawable may be omitted if unused in format spec
189     val colon: String? = null,
190     // key is keypath name to get strokes from lottie, value is the color name to query color in
191     // palette, e.g. @android:color/system_accent1_100
192     val fillColorLightMap: Map<String, String>? = null,
193     val fillColorDarkMap: Map<String, String>? = null,
194     override val fontSizeScale: Float? = null,
195     val paddingVertical: String = "0dp",
196     val paddingHorizontal: String = "0dp",
197 ) : TextStyle
198 
199 /** Layer sizing mode for the clockface or layer */
200 enum class LayerBounds {
201     /**
202      * Sized so the larger dimension matches the allocated space. This results in some of the
203      * allocated space being unused.
204      */
205     FIT,
206 
207     /**
208      * Sized so the smaller dimension matches the allocated space. This will clip some content to
209      * the edges of the space.
210      */
211     FILL,
212 
213     /** Fills the allocated space exactly by stretching the layer */
214     STRETCH,
215 }
216 
217 /** Ticking mode for analog hands. */
218 enum class AnalogTickMode {
219     SWEEP,
220     TICK,
221 }
222 
223 /** Timspec options for Analog Hands. Named for tick interval. */
224 enum class AnalogTimespec {
225     SECONDS,
226     MINUTES,
227     HOURS,
228     HOURS_OF_DAY,
229     DAY_OF_WEEK,
230     DAY_OF_MONTH,
231     DAY_OF_YEAR,
232     WEEK,
233     MONTH,
234     TIMER,
235 }
236 
237 enum class DigitalTimespec {
238     TIME_FULL_FORMAT,
239     DIGIT_PAIR,
240     FIRST_DIGIT,
241     SECOND_DIGIT,
242     DATE_FORMAT,
243 }
244 
245 enum class DigitalFaceLayout {
246     // can only use HH_PAIR, MM_PAIR from DigitalTimespec
247     TWO_PAIRS_VERTICAL,
248     TWO_PAIRS_HORIZONTAL,
249     // can only use HOUR_FIRST_DIGIT, HOUR_SECOND_DIGIT, MINUTE_FIRST_DIGIT, MINUTE_SECOND_DIGIT
250     // from DigitalTimespec, used for tabular layout when the font doesn't support tnum
251     FOUR_DIGITS_ALIGN_CENTER,
252     FOUR_DIGITS_HORIZONTAL,
253 }
254 
255 enum class RenderType {
256     CHANGE_WEIGHT,
257     HOLLOW_TEXT,
258     STROKE_TEXT,
259     OUTER_OUTLINE_TEXT,
260 }
261 
262 enum class InterpolatorEnum(factory: () -> Interpolator) {
<lambda>null263     STANDARD({ Interpolators.STANDARD }),
<lambda>null264     EMPHASIZED({ Interpolators.EMPHASIZED });
265 
266     val interpolator: Interpolator by lazy(factory)
267 }
268 
generateDigitalLayerIdStringnull269 fun generateDigitalLayerIdString(layer: DigitalHandLayer): String {
270     return if (
271         layer.timespec == DigitalTimespec.TIME_FULL_FORMAT ||
272             layer.timespec == DigitalTimespec.DATE_FORMAT
273     ) {
274         layer.timespec.toString()
275     } else {
276         if ("h" in layer.dateTimeFormat) {
277             "HOUR" + "_" + layer.timespec.toString()
278         } else {
279             "MINUTE" + "_" + layer.timespec.toString()
280         }
281     }
282 }
283