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