• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.keyguard.ui.binder
18 
19 import android.transition.TransitionManager
20 import android.transition.TransitionSet
21 import android.view.View.INVISIBLE
22 import android.view.ViewGroup
23 import androidx.annotation.VisibleForTesting
24 import androidx.constraintlayout.helper.widget.Layer
25 import androidx.constraintlayout.widget.ConstraintLayout
26 import androidx.constraintlayout.widget.ConstraintSet
27 import androidx.lifecycle.Lifecycle
28 import androidx.lifecycle.repeatOnLifecycle
29 import com.android.app.tracing.coroutines.launchTraced as launch
30 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
31 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
32 import com.android.systemui.keyguard.shared.model.ClockSize
33 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
34 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
35 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
36 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
37 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
38 import com.android.systemui.lifecycle.repeatWhenAttached
39 import com.android.systemui.plugins.clocks.AodClockBurnInModel
40 import com.android.systemui.plugins.clocks.ClockController
41 import com.android.systemui.util.kotlin.DisposableHandles
42 import com.android.systemui.util.ui.value
43 import kotlinx.coroutines.DisposableHandle
44 import kotlinx.coroutines.flow.combine
45 import kotlinx.coroutines.flow.distinctUntilChanged
46 import kotlinx.coroutines.flow.map
47 
48 object KeyguardClockViewBinder {
49     private val TAG = KeyguardClockViewBinder::class.simpleName!!
50 
51     @JvmStatic
52     fun bind(
53         clockSection: ClockSection,
54         keyguardRootView: ConstraintLayout,
55         viewModel: KeyguardClockViewModel,
56         keyguardClockInteractor: KeyguardClockInteractor,
57         blueprintInteractor: KeyguardBlueprintInteractor,
58         rootViewModel: KeyguardRootViewModel,
59         aodBurnInViewModel: AodBurnInViewModel,
60     ): DisposableHandle {
61         val disposables = DisposableHandles()
62         disposables +=
63             keyguardRootView.repeatWhenAttached {
64                 repeatOnLifecycle(Lifecycle.State.CREATED) {
65                     keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
66                 }
67             }
68 
69         disposables +=
70             keyguardRootView.repeatWhenAttached {
71                 repeatOnLifecycle(Lifecycle.State.CREATED) {
72                     // When changing to new clock, we need to remove old views from burnInLayer
73                     var lastClock: ClockController? = null
74                     launch {
75                         viewModel.currentClock.collect { currentClock ->
76                             if (lastClock != currentClock) {
77                                 cleanupClockViews(
78                                     lastClock,
79                                     keyguardRootView,
80                                     viewModel.burnInLayer,
81                                 )
82                                 lastClock = currentClock
83                             }
84 
85                             addClockViews(currentClock, keyguardRootView)
86                             updateBurnInLayer(
87                                 keyguardRootView,
88                                 viewModel,
89                                 viewModel.clockSize.value,
90                             )
91                             applyConstraints(clockSection, keyguardRootView, true)
92                         }
93                     }
94                         .invokeOnCompletion {
95                             cleanupClockViews(lastClock, keyguardRootView, viewModel.burnInLayer)
96                             lastClock = null
97                         }
98 
99                     launch {
100                         viewModel.clockSize.collect { clockSize ->
101                             updateBurnInLayer(keyguardRootView, viewModel, clockSize)
102                             blueprintInteractor.refreshBlueprint(Type.ClockSize)
103                         }
104                     }
105 
106                     launch {
107                         viewModel.clockShouldBeCentered.collect {
108                             viewModel.currentClock.value?.let {
109                                 if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
110                                     blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
111                                 } else {
112                                     blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
113                                 }
114                             }
115                         }
116                     }
117 
118                     launch {
119                         combine(
120                             viewModel.hasAodIcons,
121                             rootViewModel.isNotifIconContainerVisible.map { it.value },
122                         ) { hasIcon, isVisible ->
123                             hasIcon && isVisible
124                         }
125                             .distinctUntilChanged()
126                             .collect { _ ->
127                                 viewModel.currentClock.value?.let {
128                                     if (it.config.useCustomClockScene) {
129                                         blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
130                                     }
131                                 }
132                             }
133                     }
134 
135                     launch {
136                         aodBurnInViewModel.movement.collect { burnInModel ->
137                             viewModel.currentClock.value
138                                 ?.largeClock
139                                 ?.layout
140                                 ?.applyAodBurnIn(
141                                     AodClockBurnInModel(
142                                         translationX = burnInModel.translationX.toFloat(),
143                                         translationY = burnInModel.translationY.toFloat(),
144                                         scale = burnInModel.scale,
145                                     )
146                                 )
147                         }
148                     }
149 
150                     launch {
151                         viewModel.largeClockTextSize.collect { fontSizePx ->
152                             viewModel.currentClock.value
153                                 ?.largeClock
154                                 ?.events
155                                 ?.onFontSettingChanged(fontSizePx = fontSizePx.toFloat())
156                         }
157                     }
158                 }
159             }
160 
161         disposables +=
162             keyguardRootView.repeatWhenAttached {
163                 repeatOnLifecycle(Lifecycle.State.STARTED) {
164                     viewModel.currentClock.collect { currentClock ->
165                         currentClock?.apply {
166                             smallClock.run { events.onThemeChanged(theme) }
167                             largeClock.run { events.onThemeChanged(theme) }
168                         }
169                     }
170                 }
171             }
172 
173         return disposables
174     }
175 
176     @VisibleForTesting
177     fun updateBurnInLayer(
178         keyguardRootView: ConstraintLayout,
179         viewModel: KeyguardClockViewModel,
180         clockSize: ClockSize,
181     ) {
182         val burnInLayer = viewModel.burnInLayer
183         val clockController = viewModel.currentClock.value
184         // Large clocks won't be added to or removed from burn in layer
185         // Weather large clock has customized burn in preventing mechanism
186         // Non-weather large clock will only scale and translate vertically
187         clockController?.let { clock ->
188             when (clockSize) {
189                 ClockSize.LARGE -> {
190                     clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
191                 }
192                 ClockSize.SMALL -> {
193                     clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
194                 }
195             }
196         }
197         viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
198     }
199 
200     fun cleanupClockViews(
201         lastClock: ClockController?,
202         rootView: ConstraintLayout,
203         burnInLayer: Layer?,
204     ) {
205         lastClock?.run {
206             smallClock.layout.views.forEach {
207                 burnInLayer?.removeView(it)
208                 rootView.removeView(it)
209             }
210             largeClock.layout.views.forEach { rootView.removeView(it) }
211         }
212     }
213 
214     @VisibleForTesting
215     fun addClockViews(clockController: ClockController?, rootView: ConstraintLayout) {
216         // We'll collect the same clock when exiting wallpaper picker without changing clock
217         // so we need to remove clock views from parent before addView again
218         clockController?.let { clock ->
219             clock.smallClock.layout.views.forEach {
220                 if (it.parent != null) {
221                     (it.parent as ViewGroup).removeView(it)
222                 }
223                 rootView.addView(it).apply { it.visibility = INVISIBLE }
224             }
225             clock.largeClock.layout.views.forEach {
226                 if (it.parent != null) {
227                     (it.parent as ViewGroup).removeView(it)
228                 }
229                 rootView.addView(it).apply { it.visibility = INVISIBLE }
230             }
231         }
232     }
233 
234     fun applyConstraints(
235         clockSection: ClockSection,
236         rootView: ConstraintLayout,
237         animated: Boolean,
238         set: TransitionSet? = null,
239     ) {
240         val constraintSet = ConstraintSet().apply { clone(rootView) }
241         clockSection.applyConstraints(constraintSet)
242         if (animated) {
243             set?.let { TransitionManager.beginDelayedTransition(rootView, it) }
244                 ?: run { TransitionManager.beginDelayedTransition(rootView) }
245         }
246         constraintSet.applyTo(rootView)
247     }
248 }
249