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.annotation.SuppressLint
21 import android.content.res.ColorStateList
22 import android.util.StateSet
23 import android.view.HapticFeedbackConstants
24 import android.view.View
25 import androidx.compose.ui.graphics.Color
26 import androidx.compose.ui.graphics.toArgb
27 import androidx.core.view.isInvisible
28 import androidx.lifecycle.Lifecycle
29 import androidx.lifecycle.repeatOnLifecycle
30 import com.android.app.tracing.coroutines.launch
31 import com.android.systemui.common.ui.view.LongPressHandlingView
32 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
33 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
34 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
35 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
36 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
37 import com.android.systemui.lifecycle.repeatWhenAttached
38 import com.android.systemui.plugins.FalsingManager
39 import com.android.systemui.res.R
40 import com.android.systemui.statusbar.VibratorHelper
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.ExperimentalCoroutinesApi
43 import kotlinx.coroutines.launch
44 
45 @ExperimentalCoroutinesApi
46 object DeviceEntryIconViewBinder {
47     private const val TAG = "DeviceEntryIconViewBinder"
48 
49     /**
50      * Updates UI for:
51      * - device entry containing view (parent view for the below views)
52      *     - long-press handling view (transparent, no UI)
53      *     - foreground icon view (lock/unlock/fingerprint)
54      *     - background view (optional)
55      */
56     @SuppressLint("ClickableViewAccessibility")
57     @JvmStatic
58     fun bind(
59         applicationScope: CoroutineScope,
60         view: DeviceEntryIconView,
61         viewModel: DeviceEntryIconViewModel,
62         fgViewModel: DeviceEntryForegroundViewModel,
63         bgViewModel: DeviceEntryBackgroundViewModel,
64         falsingManager: FalsingManager,
65         vibratorHelper: VibratorHelper,
66         overrideColor: Color? = null,
67     ) {
68         DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()
69         val longPressHandlingView = view.longPressHandlingView
70         val fgIconView = view.iconView
71         val bgView = view.bgView
72         longPressHandlingView.listener =
73             object : LongPressHandlingView.Listener {
74                 override fun onLongPressDetected(view: View, x: Int, y: Int) {
75                     if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
76                         return
77                     }
78                     vibratorHelper.performHapticFeedback(
79                         view,
80                         HapticFeedbackConstants.CONFIRM,
81                     )
82                     applicationScope.launch { viewModel.onUserInteraction() }
83                 }
84             }
85 
86         view.repeatWhenAttached {
87             // Repeat on CREATED so that the view will always observe the entire
88             // GONE => AOD transition (even though the view may not be visible until the middle
89             // of the transition.
90             repeatOnLifecycle(Lifecycle.State.CREATED) {
91                 launch("$TAG#viewModel.isVisible") {
92                     viewModel.isVisible.collect { isVisible ->
93                         longPressHandlingView.isInvisible = !isVisible
94                         view.isClickable = isVisible
95                     }
96                 }
97                 launch("$TAG#viewModel.isLongPressEnabled") {
98                     viewModel.isLongPressEnabled.collect { isEnabled ->
99                         longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
100                     }
101                 }
102                 launch("$TAG#viewModel.isUdfpsSupported") {
103                     viewModel.isUdfpsSupported.collect { udfpsSupported ->
104                         longPressHandlingView.longPressDuration =
105                             if (udfpsSupported) {
106                                 {
107                                     view.resources
108                                         .getInteger(R.integer.config_udfpsDeviceEntryIconLongPress)
109                                         .toLong()
110                                 }
111                             } else {
112                                 {
113                                     view.resources
114                                         .getInteger(R.integer.config_lockIconLongPress)
115                                         .toLong()
116                                 }
117                             }
118                     }
119                 }
120                 launch("$TAG#viewModel.accessibilityDelegateHint") {
121                     viewModel.accessibilityDelegateHint.collect { hint ->
122                         view.accessibilityHintType = hint
123                         if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
124                             view.setOnClickListener {
125                                 vibratorHelper.performHapticFeedback(
126                                     view,
127                                     HapticFeedbackConstants.CONFIRM,
128                                 )
129                                 applicationScope.launch { viewModel.onUserInteraction() }
130                             }
131                         } else {
132                             view.setOnClickListener(null)
133                         }
134                     }
135                 }
136                 launch("$TAG#viewModel.useBackgroundProtection") {
137                     viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
138                         if (useBackgroundProtection) {
139                             bgView.visibility = View.VISIBLE
140                         } else {
141                             bgView.visibility = View.GONE
142                         }
143                     }
144                 }
145                 launch("$TAG#viewModel.burnInOffsets") {
146                     viewModel.burnInOffsets.collect { burnInOffsets ->
147                         view.translationX = burnInOffsets.x.toFloat()
148                         view.translationY = burnInOffsets.y.toFloat()
149                         view.aodFpDrawable.progress = burnInOffsets.progress
150                     }
151                 }
152 
153                 launch("$TAG#viewModel.deviceEntryViewAlpha") {
154                     viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
155                 }
156             }
157         }
158 
159         fgIconView.repeatWhenAttached {
160             repeatOnLifecycle(Lifecycle.State.STARTED) {
161                 // Start with an empty state
162                 fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
163                 launch("$TAG#fpIconView.viewModel") {
164                     fgViewModel.viewModel.collect { viewModel ->
165                         fgIconView.setImageState(
166                             view.getIconState(viewModel.type, viewModel.useAodVariant),
167                             /* merge */ false
168                         )
169                         if (viewModel.type.contentDescriptionResId != -1) {
170                             fgIconView.contentDescription =
171                                 fgIconView.resources.getString(
172                                     viewModel.type.contentDescriptionResId
173                                 )
174                         }
175                         fgIconView.imageTintList =
176                             ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint)
177                         fgIconView.setPadding(
178                             viewModel.padding,
179                             viewModel.padding,
180                             viewModel.padding,
181                             viewModel.padding,
182                         )
183                     }
184                 }
185             }
186         }
187 
188         bgView.repeatWhenAttached {
189             repeatOnLifecycle(Lifecycle.State.CREATED) {
190                 launch("$TAG#bgViewModel.alpha") {
191                     bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
192                 }
193                 launch("$TAG#bgViewModel.color") {
194                     bgViewModel.color.collect { color ->
195                         bgView.imageTintList = ColorStateList.valueOf(color)
196                     }
197                 }
198             }
199         }
200     }
201 }
202