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.composable.section 18 19 import android.content.Context 20 import android.util.DisplayMetrics 21 import android.view.View 22 import android.view.WindowManager 23 import androidx.compose.runtime.Composable 24 import androidx.compose.ui.Modifier 25 import androidx.compose.ui.graphics.Color 26 import androidx.compose.ui.layout.layout 27 import androidx.compose.ui.platform.LocalContext 28 import androidx.compose.ui.unit.Constraints 29 import androidx.compose.ui.unit.IntOffset 30 import androidx.compose.ui.unit.IntRect 31 import androidx.compose.ui.viewinterop.AndroidView 32 import com.android.compose.animation.scene.ElementKey 33 import com.android.compose.animation.scene.SceneScope 34 import com.android.keyguard.LockIconView 35 import com.android.keyguard.LockIconViewController 36 import com.android.systemui.biometrics.AuthController 37 import com.android.systemui.dagger.qualifiers.Application 38 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor 39 import com.android.systemui.flags.FeatureFlagsClassic 40 import com.android.systemui.flags.Flags 41 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor 42 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder 43 import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines 44 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView 45 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel 46 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel 47 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel 48 import com.android.systemui.plugins.FalsingManager 49 import com.android.systemui.res.R 50 import com.android.systemui.shade.NotificationPanelView 51 import com.android.systemui.statusbar.VibratorHelper 52 import dagger.Lazy 53 import javax.inject.Inject 54 import kotlinx.coroutines.CoroutineScope 55 56 class LockSection 57 @Inject 58 constructor( 59 @Application private val applicationScope: CoroutineScope, 60 private val windowManager: WindowManager, 61 private val authController: AuthController, 62 private val featureFlags: FeatureFlagsClassic, 63 private val lockIconViewController: Lazy<LockIconViewController>, 64 private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, 65 private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, 66 private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, 67 private val falsingManager: Lazy<FalsingManager>, 68 private val vibratorHelper: Lazy<VibratorHelper>, 69 private val notificationPanelView: NotificationPanelView, 70 ) { 71 @Composable 72 fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { 73 if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) { 74 return 75 } 76 77 notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let { 78 notificationPanelView.removeView(it) 79 } 80 81 val context = LocalContext.current 82 83 AndroidView( 84 factory = { context -> 85 val view = 86 if (DeviceEntryUdfpsRefactor.isEnabled) { 87 DeviceEntryIconView(context, null).apply { 88 id = R.id.device_entry_icon_view 89 DeviceEntryIconViewBinder.bind( 90 applicationScope, 91 this, 92 deviceEntryIconViewModel.get(), 93 deviceEntryForegroundViewModel.get(), 94 deviceEntryBackgroundViewModel.get(), 95 falsingManager.get(), 96 vibratorHelper.get(), 97 overrideColor, 98 ) 99 } 100 } else { 101 // KeyguardBottomAreaRefactor.isEnabled 102 LockIconView(context, null).apply { 103 id = R.id.lock_icon_view 104 lockIconViewController.get().setLockIconView(this) 105 } 106 } 107 view 108 }, 109 modifier = 110 modifier.element(LockIconElementKey).layout { measurable, _ -> 111 val lockIconBounds = lockIconBounds(context) 112 val placeable = 113 measurable.measure( 114 Constraints.fixed( 115 width = lockIconBounds.width, 116 height = lockIconBounds.height, 117 ) 118 ) 119 layout( 120 width = placeable.width, 121 height = placeable.height, 122 alignmentLines = 123 mapOf( 124 BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left, 125 BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top, 126 BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right, 127 BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom, 128 ), 129 ) { 130 placeable.place(0, 0) 131 } 132 }, 133 ) 134 } 135 136 /** 137 * Returns the bounds of the lock icon, in window view coordinates. 138 * 139 * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are 140 * the same as the bounds of the sensor. 141 */ 142 private fun lockIconBounds( 143 context: Context, 144 ): IntRect { 145 val windowViewBounds = windowManager.currentWindowMetrics.bounds 146 var widthPx = windowViewBounds.right.toFloat() 147 if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { 148 val insets = windowManager.currentWindowMetrics.windowInsets 149 // Assumed to be initially neglected as there are no left or right insets in portrait. 150 // However, on landscape, these insets need to included when calculating the midpoint. 151 @Suppress("DEPRECATION") 152 widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat() 153 } 154 val defaultDensity = 155 DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / 156 DisplayMetrics.DENSITY_DEFAULT.toFloat() 157 val lockIconRadiusPx = (defaultDensity * 36).toInt() 158 159 val udfpsLocation = authController.udfpsLocation 160 val (center, radius) = 161 if (authController.isUdfpsSupported && udfpsLocation != null) { 162 Pair( 163 IntOffset( 164 x = udfpsLocation.x, 165 y = udfpsLocation.y, 166 ), 167 authController.udfpsRadius.toInt(), 168 ) 169 } else { 170 val scaleFactor = authController.scaleFactor 171 val bottomPaddingPx = 172 context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) 173 val heightPx = windowViewBounds.bottom.toFloat() 174 175 Pair( 176 IntOffset( 177 x = (widthPx / 2).toInt(), 178 y = 179 (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)) 180 .toInt(), 181 ), 182 (lockIconRadiusPx * scaleFactor).toInt(), 183 ) 184 } 185 186 return IntRect(center, radius) 187 } 188 } 189 190 private val LockIconElementKey = ElementKey("LockIcon") 191