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.systemui.keyguard.MigrateClocksToBlueprint
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.KeyguardClockViewModel
36 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
37 import com.android.systemui.lifecycle.repeatWhenAttached
38 import com.android.systemui.plugins.clocks.AodClockBurnInModel
39 import com.android.systemui.plugins.clocks.ClockController
40 import kotlinx.coroutines.launch
41 
42 object KeyguardClockViewBinder {
43     private val TAG = KeyguardClockViewBinder::class.simpleName!!
44     // When changing to new clock, we need to remove old clock views from burnInLayer
45     private var lastClock: ClockController? = null
46 
47     @JvmStatic
48     fun bind(
49         clockSection: ClockSection,
50         keyguardRootView: ConstraintLayout,
51         viewModel: KeyguardClockViewModel,
52         keyguardClockInteractor: KeyguardClockInteractor,
53         blueprintInteractor: KeyguardBlueprintInteractor,
54         rootViewModel: KeyguardRootViewModel,
55     ) {
56         keyguardRootView.repeatWhenAttached {
57             repeatOnLifecycle(Lifecycle.State.CREATED) {
58                 keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView)
59             }
60         }
61 
62         keyguardRootView.repeatWhenAttached {
63             repeatOnLifecycle(Lifecycle.State.CREATED) {
64                 launch {
65                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
66                     viewModel.currentClock.collect { currentClock ->
67                         cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
68                         addClockViews(currentClock, keyguardRootView)
69                         updateBurnInLayer(keyguardRootView, viewModel, viewModel.clockSize.value)
70                         applyConstraints(clockSection, keyguardRootView, true)
71                     }
72                 }
73 
74                 launch {
75                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
76                     viewModel.clockSize.collect { clockSize ->
77                         updateBurnInLayer(keyguardRootView, viewModel, clockSize)
78                         blueprintInteractor.refreshBlueprint(Type.ClockSize)
79                     }
80                 }
81 
82                 launch {
83                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
84                     viewModel.clockShouldBeCentered.collect {
85                         viewModel.currentClock.value?.let {
86                             // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
87                             // migrate clocks to blueprint is fully rolled out
88                             if (
89                                 it.largeClock.config.hasCustomPositionUpdatedAnimation &&
90                                     !it.config.useCustomClockScene
91                             ) {
92                                 blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
93                             } else {
94                                 blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
95                             }
96                         }
97                     }
98                 }
99 
100                 launch {
101                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
102                     viewModel.isAodIconsVisible.collect {
103                         viewModel.currentClock.value?.let {
104                             if (
105                                 viewModel.isLargeClockVisible.value && it.config.useCustomClockScene
106                             ) {
107                                 blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
108                             }
109                         }
110                     }
111                 }
112 
113                 launch {
114                     if (!MigrateClocksToBlueprint.isEnabled) return@launch
115                     rootViewModel.burnInModel.collect { burnInModel ->
116                         viewModel.currentClock.value?.let {
117                             it.largeClock.layout.applyAodBurnIn(
118                                 AodClockBurnInModel(
119                                     translationX = burnInModel.translationX.toFloat(),
120                                     translationY = burnInModel.translationY.toFloat(),
121                                     scale = burnInModel.scale
122                                 )
123                             )
124                         }
125                     }
126                 }
127             }
128         }
129     }
130 
131     @VisibleForTesting
132     fun updateBurnInLayer(
133         keyguardRootView: ConstraintLayout,
134         viewModel: KeyguardClockViewModel,
135         clockSize: ClockSize,
136     ) {
137         val burnInLayer = viewModel.burnInLayer
138         val clockController = viewModel.currentClock.value
139         // Large clocks won't be added to or removed from burn in layer
140         // Weather large clock has customized burn in preventing mechanism
141         // Non-weather large clock will only scale and translate vertically
142         clockController?.let { clock ->
143             when (clockSize) {
144                 ClockSize.LARGE -> {
145                     clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
146                 }
147                 ClockSize.SMALL -> {
148                     clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
149                 }
150             }
151         }
152         viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
153     }
154 
155     private fun cleanupClockViews(
156         currentClock: ClockController?,
157         rootView: ConstraintLayout,
158         burnInLayer: Layer?
159     ) {
160         if (lastClock == currentClock) {
161             return
162         }
163 
164         lastClock?.let { clock ->
165             clock.smallClock.layout.views.forEach {
166                 burnInLayer?.removeView(it)
167                 rootView.removeView(it)
168             }
169             clock.largeClock.layout.views.forEach { rootView.removeView(it) }
170         }
171         lastClock = currentClock
172     }
173 
174     @VisibleForTesting
175     fun addClockViews(
176         clockController: ClockController?,
177         rootView: ConstraintLayout,
178     ) {
179         // We'll collect the same clock when exiting wallpaper picker without changing clock
180         // so we need to remove clock views from parent before addView again
181         clockController?.let { clock ->
182             clock.smallClock.layout.views.forEach {
183                 if (it.parent != null) {
184                     (it.parent as ViewGroup).removeView(it)
185                 }
186                 rootView.addView(it).apply { it.visibility = INVISIBLE }
187             }
188             clock.largeClock.layout.views.forEach {
189                 if (it.parent != null) {
190                     (it.parent as ViewGroup).removeView(it)
191                 }
192                 rootView.addView(it).apply { it.visibility = INVISIBLE }
193             }
194         }
195     }
196 
197     fun applyConstraints(
198         clockSection: ClockSection,
199         rootView: ConstraintLayout,
200         animated: Boolean,
201         set: TransitionSet? = null,
202     ) {
203         val constraintSet = ConstraintSet().apply { clone(rootView) }
204         clockSection.applyConstraints(constraintSet)
205         if (animated) {
206             set?.let { TransitionManager.beginDelayedTransition(rootView, it) }
207                 ?: run { TransitionManager.beginDelayedTransition(rootView) }
208         }
209         constraintSet.applyTo(rootView)
210     }
211 }
212