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