• 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.view.View
20 import androidx.constraintlayout.helper.widget.Layer
21 import androidx.constraintlayout.widget.ConstraintLayout
22 import androidx.lifecycle.Lifecycle
23 import androidx.lifecycle.repeatOnLifecycle
24 import com.android.app.tracing.coroutines.launchTraced as launch
25 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
26 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
27 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
28 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
29 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
30 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
31 import com.android.systemui.lifecycle.repeatWhenAttached
32 import com.android.systemui.plugins.clocks.VRectF
33 import com.android.systemui.res.R
34 import com.android.systemui.shared.R as sharedR
35 import kotlinx.coroutines.DisposableHandle
36 import kotlinx.coroutines.flow.combine
37 
38 object KeyguardSmartspaceViewBinder {
39     @JvmStatic
40     fun bind(
41         keyguardRootView: ConstraintLayout,
42         keyguardRootViewModel: KeyguardRootViewModel,
43         clockViewModel: KeyguardClockViewModel,
44         smartspaceViewModel: KeyguardSmartspaceViewModel,
45         blueprintInteractor: KeyguardBlueprintInteractor,
46     ): DisposableHandle {
47         return keyguardRootView.repeatWhenAttached {
48             repeatOnLifecycle(Lifecycle.State.CREATED) {
49                 launch("$TAG#clockViewModel.hasCustomWeatherDataDisplay") {
50                     combine(
51                             smartspaceViewModel.isWeatherVisible,
52                             clockViewModel.hasCustomWeatherDataDisplay,
53                             ::Pair,
54                         )
55                         .collect {
56                             updateDateWeatherToBurnInLayer(
57                                 keyguardRootView,
58                                 clockViewModel,
59                                 smartspaceViewModel,
60                             )
61                             blueprintInteractor.refreshBlueprint(
62                                 Config(
63                                     Type.SmartspaceVisibility,
64                                     checkPriority = false,
65                                     terminatePrevious = false,
66                                 )
67                             )
68                         }
69                 }
70 
71                 launch("$TAG#smartspaceViewModel.bcSmartspaceVisibility") {
72                     smartspaceViewModel.bcSmartspaceVisibility.collect {
73                         updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
74                         blueprintInteractor.refreshBlueprint(
75                             Config(
76                                 Type.SmartspaceVisibility,
77                                 checkPriority = false,
78                                 terminatePrevious = false,
79                             )
80                         )
81                     }
82                 }
83 
84                 if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
85                     val xBuffer =
86                         keyguardRootView.context.resources.getDimensionPixelSize(
87                             R.dimen.smartspace_padding_horizontal
88                         )
89                     val yBuffer =
90                         keyguardRootView.context.resources.getDimensionPixelSize(
91                             R.dimen.smartspace_padding_vertical
92                         )
93 
94                     val smallViewId = sharedR.id.date_smartspace_view
95 
96                     val largeViewId = sharedR.id.date_smartspace_view_large
97 
98                     launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
99                         combine(
100                                 keyguardRootViewModel.burnInLayerVisibility,
101                                 clockViewModel.isLargeClockVisible,
102                                 ::Pair,
103                             )
104                             .collect { (visibility, isLargeClock) ->
105                                 if (isLargeClock) {
106                                     // hide small clock date/weather
107                                     keyguardRootView.findViewById<View>(smallViewId)?.let {
108                                         it.visibility = View.GONE
109                                     }
110                                 }
111                             }
112                     }
113 
114                     launch("$TAG#clockEventController.onClockBoundsChanged") {
115                         // Whenever the doze amount changes, the clock may update it's view bounds.
116                         // We need to update our layout position as a result. We could do this via
117                         // `requestLayout`, but that's quite expensive when enclosed in since this
118                         // recomputes the entire ConstraintLayout, so instead we do it manually. We
119                         // would use translationX/Y for this, but that's used by burnin.
120                         combine(
121                                 clockViewModel.isLargeClockVisible,
122                                 clockViewModel.clockEventController.onClockBoundsChanged,
123                                 ::Pair,
124                             )
125                             .collect { (isLargeClock, clockBounds) ->
126                                 val viewId = if (isLargeClock) smallViewId else largeViewId
127                                 keyguardRootView.findViewById<View>(viewId)?.let {
128                                     it.visibility = View.GONE
129                                 }
130 
131                                 if (clockBounds == VRectF.ZERO) return@collect
132                                 if (isLargeClock) {
133                                     val largeDateHeight =
134                                         keyguardRootView
135                                             .findViewById<View>(
136                                                 sharedR.id.date_smartspace_view_large
137                                             )
138                                             ?.height ?: 0
139 
140                                     keyguardRootView.findViewById<View>(largeViewId)?.let { view ->
141                                         val viewHeight = view.height
142                                         val offset = (largeDateHeight - viewHeight) / 2
143                                         view.top = (clockBounds.bottom + yBuffer + offset).toInt()
144                                         view.bottom = view.top + viewHeight
145                                     }
146                                 } else if (
147                                     !KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(
148                                         keyguardRootView.resources.configuration
149                                     )
150                                 ) {
151                                     keyguardRootView.findViewById<View>(smallViewId)?.let { view ->
152                                         val viewWidth = view.width
153                                         if (view.isLayoutRtl()) {
154                                             view.right = (clockBounds.left - xBuffer).toInt()
155                                             view.left = view.right - viewWidth
156                                         } else {
157                                             view.left = (clockBounds.right + xBuffer).toInt()
158                                             view.right = view.left + viewWidth
159                                         }
160                                     }
161                                 }
162                             }
163                     }
164                 }
165             }
166         }
167     }
168 
169     private fun updateBCSmartspaceInBurnInLayer(
170         keyguardRootView: ConstraintLayout,
171         clockViewModel: KeyguardClockViewModel,
172     ) {
173         // Visibility is controlled by updateTargetVisibility in CardPagerAdapter
174         val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer)
175         burnInLayer.apply {
176             val smartspaceView =
177                 keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view)
178             if (smartspaceView.visibility == View.VISIBLE) {
179                 addView(smartspaceView)
180             } else {
181                 removeView(smartspaceView)
182             }
183         }
184         clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
185     }
186 
187     private fun updateDateWeatherToBurnInLayer(
188         keyguardRootView: ConstraintLayout,
189         clockViewModel: KeyguardClockViewModel,
190         smartspaceViewModel: KeyguardSmartspaceViewModel,
191     ) {
192         if (clockViewModel.hasCustomWeatherDataDisplay.value) {
193             removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
194         } else {
195             addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
196         }
197         clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
198     }
199 
200     private fun addDateWeatherToBurnInLayer(
201         constraintLayout: ConstraintLayout,
202         smartspaceViewModel: KeyguardSmartspaceViewModel,
203     ) {
204         val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
205         burnInLayer.apply {
206             if (
207                 smartspaceViewModel.isSmartspaceEnabled &&
208                     smartspaceViewModel.isDateWeatherDecoupled
209             ) {
210                 val dateView =
211                     constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
212                 addView(dateView)
213             }
214         }
215     }
216 
217     private fun removeDateWeatherFromBurnInLayer(
218         constraintLayout: ConstraintLayout,
219         smartspaceViewModel: KeyguardSmartspaceViewModel,
220     ) {
221         val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
222         burnInLayer.apply {
223             if (
224                 smartspaceViewModel.isSmartspaceEnabled &&
225                     smartspaceViewModel.isDateWeatherDecoupled
226             ) {
227                 val dateView =
228                     constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
229                 removeView(dateView)
230             }
231         }
232     }
233 
234     private const val TAG = "KeyguardSmartspaceViewBinder"
235 }
236