blob: 22a7cd15ee31be0efb1e47c0cecc9acc77bdf824 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.decor
import android.content.Context
import android.util.Log
import android.view.DisplayCutout
import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
import android.view.DisplayCutout.BOUNDS_POSITION_TOP
import android.view.DisplayInfo
import android.view.Gravity
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
import javax.inject.Inject
@SysUISingleton
class FaceScanningProviderFactory @Inject constructor(
private val authController: AuthController,
private val context: Context,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val featureFlags: FeatureFlags
) : DecorProviderFactory() {
private val display = context.display
private val displayInfo = DisplayInfo()
override val hasProviders: Boolean
get() {
// update display info:
display?.getDisplayInfo(displayInfo) ?: run {
Log.w(TAG, "display is null, can't update displayInfo")
}
val hasDisplayCutout = DisplayCutout.getFillBuiltInDisplayCutout(
context.resources, displayInfo.uniqueId)
return hasDisplayCutout &&
authController.faceAuthSensorLocation != null &&
featureFlags.isEnabled(Flags.FACE_SCANNING_ANIM)
}
override val providers: List<DecorProvider>
get() {
if (!hasProviders) {
return emptyList()
}
return ArrayList<DecorProvider>().also { list ->
// displayInfo must be updated before using it; however it will already have
// been updated when accessing the hasProviders field above
displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds ->
// Add a face scanning view for each screen orientation.
// Cutout drawing is updated in ScreenDecorations#updateCutout
for (bound in bounds) {
list.add(
FaceScanningOverlayProviderImpl(
bound.baseOnRotation0(displayInfo.rotation),
authController,
statusBarStateController,
keyguardUpdateMonitor)
)
}
}
}
}
fun canShowFaceScanningAnim(): Boolean {
return hasProviders && keyguardUpdateMonitor.isFaceEnrolled
}
fun shouldShowFaceScanningAnim(): Boolean {
return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceScanning
}
}
class FaceScanningOverlayProviderImpl(
override val alignedBound: Int,
private val authController: AuthController,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
override fun onReloadResAndMeasure(
view: View,
reloadToken: Int,
rotation: Int,
displayUniqueId: String?
) {
// no need to handle rotation changes
}
override fun inflateView(
context: Context,
parent: ViewGroup,
@Surface.Rotation rotation: Int
): View {
val view = FaceScanningOverlay(
context,
alignedBound,
statusBarStateController,
keyguardUpdateMonitor)
view.id = viewId
view.visibility = View.INVISIBLE // only show this view when face scanning is happening
var heightLayoutParam = ViewGroup.LayoutParams.MATCH_PARENT
authController.faceAuthSensorLocation?.y?.let {
heightLayoutParam = (it * 3).toInt()
}
parent.addView(view, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
heightLayoutParam,
Gravity.TOP or Gravity.START))
return view
}
}
fun DisplayCutout.getBoundBaseOnCurrentRotation(): List<Int> {
return ArrayList<Int>().also {
if (!boundingRectLeft.isEmpty) {
it.add(BOUNDS_POSITION_LEFT)
}
if (!boundingRectTop.isEmpty) {
it.add(BOUNDS_POSITION_TOP)
}
if (!boundingRectRight.isEmpty) {
it.add(BOUNDS_POSITION_RIGHT)
}
if (!boundingRectBottom.isEmpty) {
it.add(BOUNDS_POSITION_BOTTOM)
}
}
}
fun Int.baseOnRotation0(@DisplayCutout.BoundsPosition currentRotation: Int): Int {
return when (currentRotation) {
Surface.ROTATION_0 -> this
Surface.ROTATION_90 -> when (this) {
BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_TOP
BOUNDS_POSITION_TOP -> BOUNDS_POSITION_RIGHT
BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_BOTTOM
else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_LEFT
}
Surface.ROTATION_270 -> when (this) {
BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_BOTTOM
BOUNDS_POSITION_TOP -> BOUNDS_POSITION_LEFT
BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_TOP
else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_RIGHT
}
else /* Surface.ROTATION_180 */ -> when (this) {
BOUNDS_POSITION_LEFT -> BOUNDS_POSITION_RIGHT
BOUNDS_POSITION_TOP -> BOUNDS_POSITION_BOTTOM
BOUNDS_POSITION_RIGHT -> BOUNDS_POSITION_LEFT
else /* BOUNDS_POSITION_BOTTOM */ -> BOUNDS_POSITION_TOP
}
}
}
private const val TAG = "FaceScanningProvider"