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.view.layout.sections 19 20 import android.content.Context 21 import android.graphics.Point 22 import android.graphics.Rect 23 import android.util.DisplayMetrics 24 import android.util.Log 25 import android.view.View 26 import android.view.WindowManager 27 import androidx.annotation.VisibleForTesting 28 import androidx.constraintlayout.widget.ConstraintLayout 29 import androidx.constraintlayout.widget.ConstraintSet 30 import com.android.keyguard.LockIconView 31 import com.android.keyguard.LockIconViewController 32 import com.android.systemui.biometrics.AuthController 33 import com.android.systemui.dagger.qualifiers.Application 34 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor 35 import com.android.systemui.flags.FeatureFlags 36 import com.android.systemui.flags.Flags 37 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor 38 import com.android.systemui.keyguard.MigrateClocksToBlueprint 39 import com.android.systemui.keyguard.shared.model.KeyguardSection 40 import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder 41 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView 42 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel 43 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel 44 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel 45 import com.android.systemui.plugins.FalsingManager 46 import com.android.systemui.res.R 47 import com.android.systemui.shade.NotificationPanelView 48 import com.android.systemui.statusbar.VibratorHelper 49 import dagger.Lazy 50 import javax.inject.Inject 51 import kotlinx.coroutines.CoroutineScope 52 import kotlinx.coroutines.ExperimentalCoroutinesApi 53 54 /** Includes the device entry icon. */ 55 @ExperimentalCoroutinesApi 56 class DefaultDeviceEntrySection 57 @Inject 58 constructor( 59 @Application private val applicationScope: CoroutineScope, 60 private val authController: AuthController, 61 private val windowManager: WindowManager, 62 private val context: Context, 63 private val notificationPanelView: NotificationPanelView, 64 private val featureFlags: FeatureFlags, 65 private val lockIconViewController: Lazy<LockIconViewController>, 66 private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, 67 private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, 68 private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, 69 private val falsingManager: Lazy<FalsingManager>, 70 private val vibratorHelper: Lazy<VibratorHelper>, 71 ) : KeyguardSection() { 72 private val deviceEntryIconViewId = R.id.device_entry_icon_view 73 74 override fun addViews(constraintLayout: ConstraintLayout) { 75 if ( 76 !KeyguardBottomAreaRefactor.isEnabled && 77 !MigrateClocksToBlueprint.isEnabled && 78 !DeviceEntryUdfpsRefactor.isEnabled 79 ) { 80 return 81 } 82 83 notificationPanelView.findViewById<View>(R.id.lock_icon_view).let { 84 notificationPanelView.removeView(it) 85 } 86 87 val view = 88 if (DeviceEntryUdfpsRefactor.isEnabled) { 89 DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } 90 } else { 91 // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled 92 LockIconView(context, null).apply { id = R.id.lock_icon_view } 93 } 94 constraintLayout.addView(view) 95 } 96 97 override fun bindData(constraintLayout: ConstraintLayout) { 98 if (DeviceEntryUdfpsRefactor.isEnabled) { 99 constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { 100 DeviceEntryIconViewBinder.bind( 101 applicationScope, 102 it, 103 deviceEntryIconViewModel.get(), 104 deviceEntryForegroundViewModel.get(), 105 deviceEntryBackgroundViewModel.get(), 106 falsingManager.get(), 107 vibratorHelper.get(), 108 ) 109 } 110 } else { 111 constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let { 112 lockIconViewController.get().setLockIconView(it) 113 } 114 } 115 } 116 117 override fun applyConstraints(constraintSet: ConstraintSet) { 118 val isUdfpsSupported = 119 if (DeviceEntryUdfpsRefactor.isEnabled) { 120 Log.d( 121 "DefaultDeviceEntrySection", 122 "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}" 123 ) 124 deviceEntryIconViewModel.get().isUdfpsSupported.value 125 } else { 126 authController.isUdfpsSupported 127 } 128 val scaleFactor: Float = authController.scaleFactor 129 val mBottomPaddingPx = 130 context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) 131 val bounds = windowManager.currentWindowMetrics.bounds 132 var widthPixels = bounds.right.toFloat() 133 if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { 134 // Assumed to be initially neglected as there are no left or right insets in portrait. 135 // However, on landscape, these insets need to included when calculating the midpoint. 136 val insets = windowManager.currentWindowMetrics.windowInsets 137 widthPixels -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat() 138 } 139 val heightPixels = bounds.bottom.toFloat() 140 val defaultDensity = 141 DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / 142 DisplayMetrics.DENSITY_DEFAULT.toFloat() 143 val iconRadiusPx = (defaultDensity * 36).toInt() 144 145 if (isUdfpsSupported) { 146 if (DeviceEntryUdfpsRefactor.isEnabled) { 147 deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation -> 148 Log.d( 149 "DeviceEntrySection", 150 "udfpsLocation=$udfpsLocation, " + 151 "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " + 152 "unusedAuthController=${authController.udfpsLocation}" 153 ) 154 centerIcon( 155 Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()), 156 udfpsLocation.radius, 157 constraintSet 158 ) 159 } 160 } else { 161 authController.udfpsLocation?.let { udfpsLocation -> 162 Log.d("DeviceEntrySection", "udfpsLocation=$udfpsLocation") 163 centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) 164 } 165 } 166 } else { 167 centerIcon( 168 Point( 169 (widthPixels / 2).toInt(), 170 (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt() 171 ), 172 iconRadiusPx * scaleFactor, 173 constraintSet, 174 ) 175 } 176 } 177 178 override fun removeViews(constraintLayout: ConstraintLayout) { 179 if (DeviceEntryUdfpsRefactor.isEnabled) { 180 constraintLayout.removeView(deviceEntryIconViewId) 181 } else { 182 constraintLayout.removeView(R.id.lock_icon_view) 183 } 184 } 185 186 @VisibleForTesting 187 internal fun centerIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { 188 val sensorRect = 189 Rect().apply { 190 set( 191 center.x - radius.toInt(), 192 center.y - radius.toInt(), 193 center.x + radius.toInt(), 194 center.y + radius.toInt(), 195 ) 196 } 197 198 val iconId = 199 if (DeviceEntryUdfpsRefactor.isEnabled) { 200 deviceEntryIconViewId 201 } else { 202 R.id.lock_icon_view 203 } 204 205 constraintSet.apply { 206 constrainWidth(iconId, sensorRect.right - sensorRect.left) 207 constrainHeight(iconId, sensorRect.bottom - sensorRect.top) 208 connect( 209 iconId, 210 ConstraintSet.TOP, 211 ConstraintSet.PARENT_ID, 212 ConstraintSet.TOP, 213 sensorRect.top 214 ) 215 connect( 216 iconId, 217 ConstraintSet.START, 218 ConstraintSet.PARENT_ID, 219 ConstraintSet.START, 220 sensorRect.left 221 ) 222 } 223 224 // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled 225 // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not 226 // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer 227 // being in NPVC and laying out prior to the KeyguardRootView. 228 // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled. 229 if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) { 230 with(notificationPanelView) { 231 val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value 232 val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 233 findViewById<View>(R.id.ambient_indication_container)?.let { 234 val (ambientLeft, ambientTop) = it.locationOnScreen 235 if (isUdfpsSupported) { 236 // make top of ambient indication view the bottom of the lock icon 237 it.layout( 238 ambientLeft, 239 sensorRect.bottom, 240 bottomAreaViewRight - ambientLeft, 241 ambientTop + it.measuredHeight 242 ) 243 } else { 244 // make bottom of ambient indication view the top of the lock icon 245 it.layout( 246 ambientLeft, 247 sensorRect.top - it.measuredHeight, 248 bottomAreaViewRight - ambientLeft, 249 sensorRect.top 250 ) 251 } 252 } 253 } 254 } 255 } 256 } 257