1 /* 2 * Copyright (C) 2022 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.decor 18 19 import android.content.Context 20 import android.util.Log 21 import android.view.DisplayCutout 22 import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM 23 import android.view.DisplayCutout.BOUNDS_POSITION_LEFT 24 import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT 25 import android.view.DisplayCutout.BOUNDS_POSITION_TOP 26 import android.view.DisplayInfo 27 import android.view.Gravity 28 import android.view.Surface 29 import android.view.View 30 import android.view.ViewGroup 31 import android.widget.FrameLayout 32 import com.android.keyguard.KeyguardUpdateMonitor 33 import com.android.systemui.FaceScanningOverlay 34 import com.android.systemui.biometrics.AuthController 35 import com.android.systemui.dagger.SysUISingleton 36 import com.android.systemui.dagger.qualifiers.Main 37 import com.android.systemui.flags.FeatureFlags 38 import com.android.systemui.log.ScreenDecorationsLogger 39 import com.android.systemui.plugins.statusbar.StatusBarStateController 40 import java.util.concurrent.Executor 41 import javax.inject.Inject 42 43 @SysUISingleton 44 class FaceScanningProviderFactory @Inject constructor( 45 private val authController: AuthController, 46 private val context: Context, 47 private val statusBarStateController: StatusBarStateController, 48 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 49 @Main private val mainExecutor: Executor, 50 private val logger: ScreenDecorationsLogger, 51 private val featureFlags: FeatureFlags, 52 ) : DecorProviderFactory() { 53 private val display = context.display 54 private val displayInfo = DisplayInfo() 55 56 override val hasProviders: Boolean 57 get() { 58 if (authController.faceSensorLocation == null) { 59 return false 60 } 61 62 // update display info 63 display?.getDisplayInfo(displayInfo) ?: run { 64 Log.w(TAG, "display is null, can't update displayInfo") 65 } 66 return DisplayCutout.getFillBuiltInDisplayCutout( 67 context.resources, displayInfo.uniqueId) 68 } 69 70 override val providers: List<DecorProvider> 71 get() { 72 if (!hasProviders) { 73 return emptyList() 74 } 75 76 return ArrayList<DecorProvider>().also { list -> 77 // displayInfo must be updated before using it; however it will already have 78 // been updated when accessing the hasProviders field above 79 displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds -> 80 // Add a face scanning view for each screen orientation. 81 // Cutout drawing is updated in ScreenDecorations#updateCutout 82 for (bound in bounds) { 83 list.add( 84 FaceScanningOverlayProviderImpl( 85 bound.baseOnRotation0(displayInfo.rotation), 86 authController, 87 statusBarStateController, 88 keyguardUpdateMonitor, 89 mainExecutor, 90 logger, 91 featureFlags, 92 ) 93 ) 94 } 95 } 96 } 97 } 98 99 fun canShowFaceScanningAnim(): Boolean { 100 return hasProviders && keyguardUpdateMonitor.isFaceEnrolled 101 } 102 103 fun shouldShowFaceScanningAnim(): Boolean { 104 return canShowFaceScanningAnim() && 105 (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing) 106 } 107 } 108 109 class FaceScanningOverlayProviderImpl( 110 override val alignedBound: Int, 111 private val authController: AuthController, 112 private val statusBarStateController: StatusBarStateController, 113 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 114 private val mainExecutor: Executor, 115 private val logger: ScreenDecorationsLogger, 116 private val featureFlags: FeatureFlags, 117 ) : BoundDecorProvider() { 118 override val viewId: Int = com.android.systemui.R.id.face_scanning_anim 119 120 override fun onReloadResAndMeasure( 121 view: View, 122 reloadToken: Int, 123 @Surface.Rotation rotation: Int, 124 tintColor: Int, 125 displayUniqueId: String? 126 ) { 127 (view.layoutParams as FrameLayout.LayoutParams).let { 128 updateLayoutParams(it, rotation) 129 view.layoutParams = it 130 (view as? FaceScanningOverlay)?.let { overlay -> 131 overlay.setColor(tintColor) 132 overlay.updateConfiguration(displayUniqueId) 133 } 134 } 135 } 136 137 override fun inflateView( 138 context: Context, 139 parent: ViewGroup, 140 @Surface.Rotation rotation: Int, 141 tintColor: Int 142 ): View { 143 val view = FaceScanningOverlay( 144 context, 145 alignedBound, 146 statusBarStateController, 147 keyguardUpdateMonitor, 148 mainExecutor, 149 logger, 150 authController, 151 featureFlags 152 ) 153 view.id = viewId 154 view.setColor(tintColor) 155 FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 156 ViewGroup.LayoutParams.MATCH_PARENT).let { 157 updateLayoutParams(it, rotation) 158 parent.addView(view, it) 159 } 160 return view 161 } 162 163 private fun updateLayoutParams( 164 layoutParams: FrameLayout.LayoutParams, 165 @Surface.Rotation rotation: Int 166 ) { 167 layoutParams.let { lp -> 168 lp.width = ViewGroup.LayoutParams.MATCH_PARENT 169 lp.height = ViewGroup.LayoutParams.MATCH_PARENT 170 logger.faceSensorLocation(authController.faceSensorLocation) 171 authController.faceSensorLocation?.y?.let { faceAuthSensorHeight -> 172 val faceScanningHeight = (faceAuthSensorHeight * 2) 173 when (rotation) { 174 Surface.ROTATION_0, Surface.ROTATION_180 -> 175 lp.height = faceScanningHeight 176 Surface.ROTATION_90, Surface.ROTATION_270 -> 177 lp.width = faceScanningHeight 178 } 179 } 180 181 lp.gravity = when (rotation) { 182 Surface.ROTATION_0 -> Gravity.TOP or Gravity.START 183 Surface.ROTATION_90 -> Gravity.LEFT or Gravity.START 184 Surface.ROTATION_180 -> Gravity.BOTTOM or Gravity.END 185 Surface.ROTATION_270 -> Gravity.RIGHT or Gravity.END 186 else -> -1 /* invalid rotation */ 187 } 188 } 189 } 190 } 191 192 fun DisplayCutout.getBoundBaseOnCurrentRotation(): List<Int> { 193 return ArrayList<Int>().also { 194 if (!boundingRectLeft.isEmpty) { 195 it.add(BOUNDS_POSITION_LEFT) 196 } 197 if (!boundingRectTop.isEmpty) { 198 it.add(BOUNDS_POSITION_TOP) 199 } 200 if (!boundingRectRight.isEmpty) { 201 it.add(BOUNDS_POSITION_RIGHT) 202 } 203 if (!boundingRectBottom.isEmpty) { 204 it.add(BOUNDS_POSITION_BOTTOM) 205 } 206 } 207 } 208 209 fun Int.baseOnRotation0(@DisplayCutout.BoundsPosition currentRotation: Int): Int { 210 return when (currentRotation) { 211 Surface.ROTATION_0 -> this 212 Surface.ROTATION_90 -> when (this) { 213 BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_TOP 214 BOUNDS_POSITION_TOP -> BOUNDS_POSITION_RIGHT 215 BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_BOTTOM 216 else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_LEFT 217 } 218 Surface.ROTATION_270 -> when (this) { 219 BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_BOTTOM 220 BOUNDS_POSITION_TOP -> BOUNDS_POSITION_LEFT 221 BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_TOP 222 else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_RIGHT 223 } 224 else /* Surface.ROTATION_180 */ -> when (this) { 225 BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_RIGHT 226 BOUNDS_POSITION_TOP -> BOUNDS_POSITION_BOTTOM 227 BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_LEFT 228 else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_TOP 229 } 230 } 231 } 232 233 private const val TAG = "FaceScanningProvider" 234