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 
18 package com.android.systemui.keyguard.ui.binder
19 
20 import android.content.Context
21 import android.util.DisplayMetrics
22 import android.view.View
23 import android.view.View.INVISIBLE
24 import android.view.View.VISIBLE
25 import android.view.ViewGroup
26 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
27 import androidx.constraintlayout.widget.ConstraintLayout
28 import androidx.constraintlayout.widget.ConstraintSet
29 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
30 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
31 import androidx.constraintlayout.widget.ConstraintSet.START
32 import androidx.constraintlayout.widget.ConstraintSet.TOP
33 import androidx.core.view.isVisible
34 import androidx.lifecycle.Lifecycle
35 import androidx.lifecycle.repeatOnLifecycle
36 import com.android.app.tracing.coroutines.launch
37 import com.android.internal.policy.SystemBarUtils
38 import com.android.systemui.customization.R as customizationR
39 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
40 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
41 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
42 import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
43 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
44 import com.android.systemui.lifecycle.repeatWhenAttached
45 import com.android.systemui.plugins.clocks.ClockController
46 import com.android.systemui.res.R
47 import com.android.systemui.shared.clocks.ClockRegistry
48 import com.android.systemui.util.Utils
49 import kotlin.reflect.KSuspendFunction1
50 
51 /** Binder for the small clock view, large clock view. */
52 object KeyguardPreviewClockViewBinder {
53     @JvmStatic
54     fun bind(
55         largeClockHostView: View,
56         smallClockHostView: View,
57         viewModel: KeyguardPreviewClockViewModel,
58     ) {
59         largeClockHostView.repeatWhenAttached {
60             repeatOnLifecycle(Lifecycle.State.STARTED) {
61                 launch("$TAG#viewModel.isLargeClockVisible") {
62                     viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
63                 }
64             }
65         }
66 
67         smallClockHostView.repeatWhenAttached {
68             repeatOnLifecycle(Lifecycle.State.STARTED) {
69                 launch("$TAG#viewModel.isSmallClockVisible") {
70                     viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
71                 }
72             }
73         }
74     }
75 
76     @JvmStatic
77     fun bind(
78         context: Context,
79         rootView: ConstraintLayout,
80         viewModel: KeyguardPreviewClockViewModel,
81         clockRegistry: ClockRegistry,
82         updateClockAppearance: KSuspendFunction1<ClockController, Unit>,
83     ) {
84         rootView.repeatWhenAttached {
85             repeatOnLifecycle(Lifecycle.State.STARTED) {
86                 var lastClock: ClockController? = null
87                 launch("$TAG#viewModel.previewClock") {
88                         viewModel.previewClock.collect { currentClock ->
89                             lastClock?.let { clock ->
90                                 (clock.largeClock.layout.views + clock.smallClock.layout.views)
91                                     .forEach { rootView.removeView(it) }
92                             }
93                             lastClock = currentClock
94                             updateClockAppearance(currentClock)
95 
96                             if (viewModel.shouldHighlightSelectedAffordance) {
97                                 (currentClock.largeClock.layout.views +
98                                         currentClock.smallClock.layout.views)
99                                     .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
100                             }
101                             currentClock.largeClock.layout.views.forEach {
102                                 (it.parent as? ViewGroup)?.removeView(it)
103                                 rootView.addView(it)
104                             }
105 
106                             currentClock.smallClock.layout.views.forEach {
107                                 (it.parent as? ViewGroup)?.removeView(it)
108                                 rootView.addView(it)
109                             }
110                             applyPreviewConstraints(context, rootView, currentClock, viewModel)
111                         }
112                     }
113                     .invokeOnCompletion {
114                         // recover seed color especially for Transit clock
115                         lastClock?.events?.onSeedColorChanged(clockRegistry.seedColor)
116                     }
117             }
118         }
119     }
120 
121     private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
122         constraints.apply {
123             constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
124             constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
125             val largeClockTopMargin =
126                 SystemBarUtils.getStatusBarHeight(context) +
127                     context.resources.getDimensionPixelSize(
128                         customizationR.dimen.small_clock_padding_top
129                     ) +
130                     context.resources.getDimensionPixelSize(
131                         R.dimen.keyguard_smartspace_top_offset
132                     ) +
133                     getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
134                     getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
135             connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
136             connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
137             connect(
138                 R.id.lockscreen_clock_view_large,
139                 ConstraintSet.END,
140                 PARENT_ID,
141                 ConstraintSet.END
142             )
143 
144             // In preview, we'll show UDFPS icon for UDFPS devices
145             // and nothing for non-UDFPS devices,
146             // but we need position of device entry icon to constrain clock
147             if (getConstraint(R.id.lock_icon_view) != null) {
148                 connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
149             } else {
150                 // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
151                 val bottomPaddingPx =
152                     context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
153                 val defaultDensity =
154                     DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
155                         DisplayMetrics.DENSITY_DEFAULT.toFloat()
156                 val lockIconRadiusPx = (defaultDensity * 36).toInt()
157                 val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
158                 connect(
159                     R.id.lockscreen_clock_view_large,
160                     BOTTOM,
161                     PARENT_ID,
162                     BOTTOM,
163                     clockBottomMargin
164                 )
165             }
166 
167             constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
168             constrainHeight(
169                 R.id.lockscreen_clock_view,
170                 context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
171             )
172             connect(
173                 R.id.lockscreen_clock_view,
174                 START,
175                 PARENT_ID,
176                 START,
177                 context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
178                     context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
179             )
180             val smallClockTopMargin =
181                 context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
182                     Utils.getStatusBarHeaderHeightKeyguard(context)
183             connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
184         }
185     }
186 
187     private fun applyPreviewConstraints(
188         context: Context,
189         rootView: ConstraintLayout,
190         previewClock: ClockController,
191         viewModel: KeyguardPreviewClockViewModel
192     ) {
193         val cs = ConstraintSet().apply { clone(rootView) }
194         applyClockDefaultConstraints(context, cs)
195         previewClock.largeClock.layout.applyPreviewConstraints(cs)
196         previewClock.smallClock.layout.applyPreviewConstraints(cs)
197 
198         // When selectedClockSize is the initial value, make both clocks invisible to avoid
199         // flickering
200         val largeClockVisibility =
201             when (viewModel.selectedClockSize.value) {
202                 ClockSizeSetting.DYNAMIC -> VISIBLE
203                 ClockSizeSetting.SMALL -> INVISIBLE
204                 null -> INVISIBLE
205             }
206         val smallClockVisibility =
207             when (viewModel.selectedClockSize.value) {
208                 ClockSizeSetting.DYNAMIC -> INVISIBLE
209                 ClockSizeSetting.SMALL -> VISIBLE
210                 null -> INVISIBLE
211             }
212         cs.apply {
213             setVisibility(previewClock.largeClock.layout.views, largeClockVisibility)
214             setVisibility(previewClock.smallClock.layout.views, smallClockVisibility)
215         }
216         cs.applyTo(rootView)
217     }
218 
219     private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
220     private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
221     private const val TAG = "KeyguardPreviewClockViewBinder"
222 }
223