• 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.Gravity
21 import android.view.View
22 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
23 import android.widget.FrameLayout
24 import com.android.systemui.animation.GSFAxes
25 import com.android.systemui.customization.R
26 import com.android.systemui.plugins.clocks.AlarmData
27 import com.android.systemui.plugins.clocks.ClockAnimations
28 import com.android.systemui.plugins.clocks.ClockAxisStyle
29 import com.android.systemui.plugins.clocks.ClockEvents
30 import com.android.systemui.plugins.clocks.ClockFaceConfig
31 import com.android.systemui.plugins.clocks.ClockFaceController
32 import com.android.systemui.plugins.clocks.ClockFaceEvents
33 import com.android.systemui.plugins.clocks.ClockFaceLayout
34 import com.android.systemui.plugins.clocks.ClockFontAxis.Companion.merge
35 import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
36 import com.android.systemui.plugins.clocks.ThemeConfig
37 import com.android.systemui.plugins.clocks.WeatherData
38 import com.android.systemui.plugins.clocks.ZenData
39 import com.android.systemui.shared.clocks.FlexClockController.Companion.getDefaultAxes
40 import com.android.systemui.shared.clocks.FontUtils.get
41 import com.android.systemui.shared.clocks.FontUtils.set
42 import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff
43 import com.android.systemui.shared.clocks.view.FlexClockView
44 import com.android.systemui.shared.clocks.view.HorizontalAlignment
45 import com.android.systemui.shared.clocks.view.VerticalAlignment
46 import java.util.Locale
47 import java.util.TimeZone
48 import kotlin.math.max
49 import kotlin.math.roundToInt
50 
51 // TODO(b/364680879): Merge w/ ComposedDigitalLayerController
52 class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: Boolean) :
53     ClockFaceController {
54     override val view: View
55         get() = layerController.view
56 
57     override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
58 
59     override var theme = ThemeConfig(true, clockCtx.settings.seedColor)
60 
61     private val keyguardLargeClockTopMargin =
62         clockCtx.resources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin)
63     val layerController: SimpleClockLayerController
64     val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")
65 
66     init {
67         layerController =
68             if (isLargeClock) ComposedDigitalLayerController(clockCtx)
69             else SimpleDigitalHandLayerController(clockCtx, SMALL_LAYER_CONFIG, isLargeClock)
70 
71         layerController.view.layoutParams =
<lambda>null72             FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT).apply { gravity = Gravity.CENTER }
73     }
74 
75     /** See documentation at [FlexClockView.offsetGlyphsForStepClockAnimation]. */
offsetGlyphsForStepClockAnimationnull76     private fun offsetGlyphsForStepClockAnimation(
77         clockStartLeft: Int,
78         direction: Int,
79         fraction: Float,
80     ) {
81         (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
82             clockStartLeft,
83             direction,
84             fraction,
85         )
86     }
87 
88     override val layout: ClockFaceLayout =
<lambda>null89         DefaultClockFaceLayout(view).apply {
90             views[0].id =
91                 if (isLargeClock) R.id.lockscreen_clock_view_large else R.id.lockscreen_clock_view
92         }
93 
94     override val events = FlexClockFaceEvents()
95 
96     // TODO(b/364680879): Remove ClockEvents
97     inner class FlexClockFaceEvents : ClockEvents, ClockFaceEvents {
98         override var isReactiveTouchInteractionEnabled = false
99             get() = field
100             set(value) {
101                 field = value
102                 layerController.events.isReactiveTouchInteractionEnabled = value
103             }
104 
onTimeTicknull105         override fun onTimeTick() {
106             timespecHandler.updateTime()
107             view.contentDescription = timespecHandler.getContentDescription()
108             layerController.faceEvents.onTimeTick()
109         }
110 
onTimeZoneChangednull111         override fun onTimeZoneChanged(timeZone: TimeZone) {
112             timespecHandler.timeZone = timeZone
113             layerController.events.onTimeZoneChanged(timeZone)
114         }
115 
onTimeFormatChangednull116         override fun onTimeFormatChanged(is24Hr: Boolean) {
117             timespecHandler.is24Hr = is24Hr
118             layerController.events.onTimeFormatChanged(is24Hr)
119         }
120 
onLocaleChangednull121         override fun onLocaleChanged(locale: Locale) {
122             timespecHandler.updateLocale(locale)
123             layerController.events.onLocaleChanged(locale)
124         }
125 
onFontSettingChangednull126         override fun onFontSettingChanged(fontSizePx: Float) {
127             layerController.faceEvents.onFontSettingChanged(fontSizePx)
128             view.requestLayout()
129         }
130 
onThemeChangednull131         override fun onThemeChanged(theme: ThemeConfig) {
132             this@FlexClockFaceController.theme = theme
133             layerController.faceEvents.onThemeChanged(theme)
134         }
135 
136         /**
137          * targetRegion passed to all customized clock applies counter translationY of Keyguard and
138          * keyguard_large_clock_top_margin from default clock
139          */
onTargetRegionChangednull140         override fun onTargetRegionChanged(targetRegion: Rect?) {
141             var maxWidth = 0f
142             var maxHeight = 0f
143 
144             layerController.faceEvents.onTargetRegionChanged(targetRegion)
145             maxWidth = max(maxWidth, view.layoutParams.width.toFloat())
146             maxHeight = max(maxHeight, view.layoutParams.height.toFloat())
147 
148             val lp =
149                 if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) {
150                     // No specified width/height. Just match parent size.
151                     FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
152                 } else {
153                     // Scale to fit in targetRegion based on largest child elements.
154                     val ratio = maxWidth / maxHeight
155                     val targetRatio = targetRegion.width() / targetRegion.height().toFloat()
156                     val scale =
157                         if (ratio > targetRatio) targetRegion.width() / maxWidth
158                         else targetRegion.height() / maxHeight
159 
160                     FrameLayout.LayoutParams(
161                         (maxWidth * scale).roundToInt(),
162                         (maxHeight * scale).roundToInt(),
163                     )
164                 }
165 
166             lp.gravity = Gravity.CENTER
167             view.layoutParams = lp
168             targetRegion?.let {
169                 val diff = view.computeLayoutDiff(it, isLargeClock)
170                 view.translationX = diff.x
171                 view.translationY = diff.y
172             }
173         }
174 
onSecondaryDisplayChangednull175         override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
176 
onWeatherDataChangednull177         override fun onWeatherDataChanged(data: WeatherData) {
178             layerController.events.onWeatherDataChanged(data)
179         }
180 
onAlarmDataChangednull181         override fun onAlarmDataChanged(data: AlarmData) {
182             layerController.events.onAlarmDataChanged(data)
183         }
184 
onZenDataChangednull185         override fun onZenDataChanged(data: ZenData) {
186             layerController.events.onZenDataChanged(data)
187         }
188     }
189 
190     override val animations =
191         object : ClockAnimations {
enternull192             override fun enter() {
193                 layerController.animations.enter()
194             }
195 
dozenull196             override fun doze(fraction: Float) {
197                 layerController.animations.doze(fraction)
198             }
199 
foldnull200             override fun fold(fraction: Float) {
201                 layerController.animations.fold(fraction)
202             }
203 
chargenull204             override fun charge() {
205                 layerController.animations.charge()
206             }
207 
onPickerCarouselSwipingnull208             override fun onPickerCarouselSwiping(swipingFraction: Float) {
209                 if (isLargeClock) {
210                     view.translationY = keyguardLargeClockTopMargin / 2F * swipingFraction
211                 }
212                 layerController.animations.onPickerCarouselSwiping(swipingFraction)
213                 view.invalidate()
214             }
215 
onPositionUpdatednull216             override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
217                 layerController.animations.onPositionUpdated(fromLeft, direction, fraction)
218                 if (isLargeClock) offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
219             }
220 
onPositionUpdatednull221             override fun onPositionUpdated(distance: Float, fraction: Float) {
222                 layerController.animations.onPositionUpdated(distance, fraction)
223             }
224 
onFidgetTapnull225             override fun onFidgetTap(x: Float, y: Float) {
226                 layerController.animations.onFidgetTap(x, y)
227             }
228 
onFontAxesChangednull229             override fun onFontAxesChanged(style: ClockAxisStyle) {
230                 var axes = ClockAxisStyle(getDefaultAxes(clockCtx.settings).merge(style))
231                 if (!isLargeClock && axes[GSFAxes.WIDTH] > SMALL_CLOCK_MAX_WDTH) {
232                     axes[GSFAxes.WIDTH] = SMALL_CLOCK_MAX_WDTH
233                 }
234 
235                 layerController.animations.onFontAxesChanged(axes)
236             }
237         }
238 
239     companion object {
240         val SMALL_CLOCK_MAX_WDTH = 120f
241 
242         val SMALL_LAYER_CONFIG =
243             LayerConfig(
244                 timespec = DigitalTimespec.TIME_FULL_FORMAT,
245                 style = FontTextStyle(fontSizeScale = 0.98f),
246                 aodStyle = FontTextStyle(),
247                 alignment = DigitalAlignment(HorizontalAlignment.START, VerticalAlignment.CENTER),
248                 dateTimeFormat = "h:mm",
249             )
250     }
251 }
252