• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Rect
20 import android.view.ViewGroup
21 import android.view.animation.Interpolator
22 import android.widget.RelativeLayout
23 import androidx.annotation.VisibleForTesting
24 import com.android.systemui.animation.TextAnimator
25 import com.android.systemui.customization.R
26 import com.android.systemui.log.core.Logger
27 import com.android.systemui.plugins.clocks.AlarmData
28 import com.android.systemui.plugins.clocks.ClockAnimations
29 import com.android.systemui.plugins.clocks.ClockAxisStyle
30 import com.android.systemui.plugins.clocks.ClockEvents
31 import com.android.systemui.plugins.clocks.ClockFaceConfig
32 import com.android.systemui.plugins.clocks.ClockFaceEvents
33 import com.android.systemui.plugins.clocks.ThemeConfig
34 import com.android.systemui.plugins.clocks.WeatherData
35 import com.android.systemui.plugins.clocks.ZenData
36 import com.android.systemui.shared.clocks.view.HorizontalAlignment
37 import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
38 import com.android.systemui.shared.clocks.view.VerticalAlignment
39 import java.util.Locale
40 import java.util.TimeZone
41 
42 private val TAG = SimpleDigitalHandLayerController::class.simpleName!!
43 
44 // TODO(b/364680879): The remains of ClockDesign. Cut further.
45 data class LayerConfig(
46     val style: FontTextStyle,
47     val aodStyle: FontTextStyle,
48     val alignment: DigitalAlignment,
49     val timespec: DigitalTimespec,
50     val dateTimeFormat: String,
51 ) {
generateDigitalLayerIdStringnull52     fun generateDigitalLayerIdString(): String {
53         return when {
54             timespec == DigitalTimespec.TIME_FULL_FORMAT -> "$timespec"
55             "h" in dateTimeFormat -> "HOUR_$timespec"
56             else -> "MINUTE_$timespec"
57         }
58     }
59 }
60 
61 data class DigitalAlignment(
62     val horizontalAlignment: HorizontalAlignment?,
63     val verticalAlignment: VerticalAlignment?,
64 )
65 
66 data class FontTextStyle(
67     val lineHeight: Float? = null,
68     val fontSizeScale: Float? = null,
69     val transitionDuration: Long = TextAnimator.DEFAULT_ANIMATION_DURATION,
70     val transitionInterpolator: Interpolator? = null,
71 )
72 
73 enum class DigitalTimespec {
74     TIME_FULL_FORMAT,
75     DIGIT_PAIR,
76     FIRST_DIGIT,
77     SECOND_DIGIT,
78 }
79 
80 open class SimpleDigitalHandLayerController(
81     private val clockCtx: ClockContext,
82     private val layerCfg: LayerConfig,
83     isLargeClock: Boolean,
84 ) : SimpleClockLayerController {
85     override val view = SimpleDigitalClockTextView(clockCtx, isLargeClock)
86     private val logger = Logger(clockCtx.messageBuffer, TAG)
87     val timespec = DigitalTimespecHandler(layerCfg.timespec, layerCfg.dateTimeFormat)
88     override var onViewBoundsChanged by view::onViewBoundsChanged
89 
90     @VisibleForTesting
91     override var fakeTimeMills: Long?
92         get() = timespec.fakeTimeMills
93         set(value) {
94             timespec.fakeTimeMills = value
95         }
96 
97     override val config = ClockFaceConfig()
98     var dozeState: DefaultClockController.AnimationState? = null
99 
100     init {
101         view.layoutParams =
102             RelativeLayout.LayoutParams(
103                 ViewGroup.LayoutParams.WRAP_CONTENT,
104                 ViewGroup.LayoutParams.WRAP_CONTENT,
105             )
<lambda>null106         layerCfg.alignment.verticalAlignment?.let { view.verticalAlignment = it }
<lambda>null107         layerCfg.alignment.horizontalAlignment?.let { view.horizontalAlignment = it }
108         view.applyStyles(layerCfg.style, layerCfg.aodStyle)
109         view.id =
110             clockCtx.resources.getIdentifier(
111                 layerCfg.generateDigitalLayerIdString(),
112                 "id",
113                 clockCtx.context.getPackageName(),
114             )
115     }
116 
refreshTimenull117     fun refreshTime() {
118         timespec.updateTime()
119         val text = timespec.getDigitString()
120         if (view.text != text) {
121             view.text = text
122             view.refreshTime()
123             logger.d({ "refreshTime: new text=$str1" }) { str1 = text }
124         }
125     }
126 
applyLayoutnull127     private fun applyLayout() {
128         // TODO: Remove NO-OP
129         if (view.layoutParams is RelativeLayout.LayoutParams) {
130             val lp = view.layoutParams as RelativeLayout.LayoutParams
131             lp.addRule(RelativeLayout.TEXT_ALIGNMENT_CENTER)
132             when (view.id) {
133                 R.id.HOUR_DIGIT_PAIR -> {
134                     lp.addRule(RelativeLayout.CENTER_VERTICAL)
135                     lp.addRule(RelativeLayout.ALIGN_PARENT_START)
136                 }
137                 R.id.MINUTE_DIGIT_PAIR -> {
138                     lp.addRule(RelativeLayout.CENTER_VERTICAL)
139                     lp.addRule(RelativeLayout.END_OF, R.id.HOUR_DIGIT_PAIR)
140                 }
141                 else -> {
142                     throw Exception("cannot apply two pairs layout to view ${view.id}")
143                 }
144             }
145             view.layoutParams = lp
146         }
147     }
148 
149     override val events =
150         object : ClockEvents {
151             override var isReactiveTouchInteractionEnabled = false
152 
onLocaleChangednull153             override fun onLocaleChanged(locale: Locale) {
154                 timespec.updateLocale(locale)
155                 refreshTime()
156             }
157 
158             /** Call whenever the text time format changes (12hr vs 24hr) */
onTimeFormatChangednull159             override fun onTimeFormatChanged(is24Hr: Boolean) {
160                 timespec.is24Hr = is24Hr
161                 refreshTime()
162             }
163 
onTimeZoneChangednull164             override fun onTimeZoneChanged(timeZone: TimeZone) {
165                 timespec.timeZone = timeZone
166                 refreshTime()
167             }
168 
onWeatherDataChangednull169             override fun onWeatherDataChanged(data: WeatherData) {}
170 
onAlarmDataChangednull171             override fun onAlarmDataChanged(data: AlarmData) {}
172 
onZenDataChangednull173             override fun onZenDataChanged(data: ZenData) {}
174         }
175 
176     override val animations =
177         object : ClockAnimations {
enternull178             override fun enter() {
179                 applyLayout()
180                 refreshTime()
181             }
182 
dozenull183             override fun doze(fraction: Float) {
184                 if (dozeState == null) {
185                     dozeState = DefaultClockController.AnimationState(fraction)
186                     view.animateDoze(dozeState!!.isActive, false)
187                 } else {
188                     val (hasChanged, hasJumped) = dozeState!!.update(fraction)
189                     if (hasChanged) view.animateDoze(dozeState!!.isActive, !hasJumped)
190                 }
191                 view.dozeFraction = fraction
192             }
193 
194             private var hasFontAxes = false
195 
onFontAxesChangednull196             override fun onFontAxesChanged(style: ClockAxisStyle) {
197                 view.updateAxes(style, isAnimated = hasFontAxes)
198                 hasFontAxes = true
199             }
200 
foldnull201             override fun fold(fraction: Float) {
202                 applyLayout()
203                 refreshTime()
204             }
205 
chargenull206             override fun charge() {
207                 view.animateCharge()
208             }
209 
onPickerCarouselSwipingnull210             override fun onPickerCarouselSwiping(swipingFraction: Float) {}
211 
onPositionUpdatednull212             override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
213 
onPositionUpdatednull214             override fun onPositionUpdated(distance: Float, fraction: Float) {}
215 
onFidgetTapnull216             override fun onFidgetTap(x: Float, y: Float) {
217                 view.animateFidget(x, y)
218             }
219         }
220 
221     override val faceEvents =
222         object : ClockFaceEvents {
onTimeTicknull223             override fun onTimeTick() {
224                 refreshTime()
225                 if (layerCfg.timespec == DigitalTimespec.TIME_FULL_FORMAT) {
226                     view.contentDescription = timespec.getContentDescription()
227                 }
228             }
229 
onFontSettingChangednull230             override fun onFontSettingChanged(fontSizePx: Float) {
231                 view.applyTextSize(fontSizePx)
232             }
233 
onThemeChangednull234             override fun onThemeChanged(theme: ThemeConfig) {
235                 view.updateColor(theme.getDefaultColor(clockCtx.context))
236                 refreshTime()
237             }
238 
onTargetRegionChangednull239             override fun onTargetRegionChanged(targetRegion: Rect?) {}
240 
onSecondaryDisplayChangednull241             override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
242         }
243 }
244