Create new UdfpsOverlay and UdfpsOverlayView

Prototype for UdfpsController refactor

Run `adb shell statusbar udpfs udpfsOverlay show` to view

Bug: 252897742
Test: N/A
Change-Id: I83953aeed6cadcaf35498888b1ca8a5d95658cba
This commit is contained in:
Austin Delgado
2022-09-08 20:18:14 +00:00
parent 44bdd7fdbd
commit 9b96b6f53c
9 changed files with 346 additions and 23 deletions

View File

@@ -792,7 +792,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(mScaleFactor);
mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
new UdfpsOverlayParams(mUdfpsBounds, mCachedDisplayInfo.getNaturalWidth(),
new UdfpsOverlayParams(mUdfpsBounds, new Rect(
0, mCachedDisplayInfo.getNaturalHeight() / 2,
mCachedDisplayInfo.getNaturalWidth(),
mCachedDisplayInfo.getNaturalHeight()),
mCachedDisplayInfo.getNaturalWidth(),
mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
mCachedDisplayInfo.rotation));
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {

View File

@@ -0,0 +1,206 @@
/*
* 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.biometrics
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Rect
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import android.os.Handler
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.Execution
import java.util.*
import java.util.concurrent.Executor
import javax.inject.Inject
private const val TAG = "UdfpsOverlay"
@SuppressLint("ClickableViewAccessibility")
@SysUISingleton
class UdfpsOverlay
@Inject
constructor(
private val context: Context,
private val execution: Execution,
private val windowManager: WindowManager,
private val fingerprintManager: FingerprintManager?,
private val handler: Handler,
private val biometricExecutor: Executor,
private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
private val fgExecutor: DelayableExecutor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
private val udfpsLogger: UdfpsLogger
) : CoreStartable {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsOverlayView? = null
private set
private var requestId: Long = 0
private var onFingerDown = false
val size = windowManager.maximumWindowMetrics.bounds
val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
private var params: UdfpsOverlayParams = UdfpsOverlayParams()
private val coreLayoutParams =
WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
0 /* flags set in computeLayoutParams() */,
PixelFormat.TRANSLUCENT
)
.apply {
title = TAG
fitInsetsTypes = 0
gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
inputFeatures = INPUT_FEATURE_SPY
}
fun onTouch(v: View, event: MotionEvent): Boolean {
val view = v as UdfpsOverlayView
return when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
onFingerDown = true
if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
alternateTouchProvider
.get()
.onPointerDown(
requestId,
event.x.toInt(),
event.y.toInt(),
event.touchMinor,
event.touchMajor
)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
}
}
view.configureDisplay {
biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
}
}
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
if (onFingerDown && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
alternateTouchProvider.get().onPointerUp(requestId)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
}
}
}
onFingerDown = false
if (view.isDisplayConfigured) {
view.unconfigureDisplay()
}
true
}
else -> false
}
}
fun show(requestId: Long): Boolean {
this.requestId = requestId
if (overlayView == null && alternateTouchProvider.isPresent) {
UdfpsOverlayView(context, null).let {
it.overlayParams = params
it.setUdfpsDisplayMode(
UdfpsDisplayMode(context, execution, authController, udfpsLogger)
)
it.setOnTouchListener { v, event -> onTouch(v, event) }
overlayView = it
}
windowManager.addView(overlayView, coreLayoutParams)
return true
}
return false
}
fun hide() {
overlayView?.apply {
windowManager.removeView(this)
setOnTouchListener(null)
}
overlayView = null
}
@Override
override fun start() {
fingerprintManager?.addAuthenticatorsRegisteredCallback(
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
override fun onAllAuthenticatorsRegistered(
sensors: List<FingerprintSensorPropertiesInternal>
) {
handler.post { handleAllFingerprintAuthenticatorsRegistered(sensors) }
}
}
)
}
private fun handleAllFingerprintAuthenticatorsRegistered(
sensors: List<FingerprintSensorPropertiesInternal>
) {
for (props in sensors) {
if (props.isAnyUdfpsType) {
udfpsProps.add(props)
}
}
// Setup param size
if (udfpsProps.isNotEmpty()) {
params =
UdfpsOverlayParams(
sensorBounds = udfpsProps[0].location.rect,
overlayBounds = Rect(0, size.height() / 2, size.width(), size.height()),
naturalDisplayWidth = size.width(),
naturalDisplayHeight = size.height(),
scaleFactor = 1f
)
}
}
}

View File

@@ -20,6 +20,7 @@ import android.view.Surface.Rotation
data class UdfpsOverlayParams(
val sensorBounds: Rect = Rect(),
val overlayBounds: Rect = Rect(),
val naturalDisplayWidth: Int = 0,
val naturalDisplayHeight: Int = 0,
val scaleFactor: Float = 1f,
@@ -40,4 +41,4 @@ data class UdfpsOverlayParams(
} else {
naturalDisplayHeight
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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.biometrics
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.widget.FrameLayout
private const val TAG = "UdfpsOverlayView"
class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
private val sensorRect = RectF()
var overlayParams = UdfpsOverlayParams()
private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
var overlayPaint = Paint()
var sensorPaint = Paint()
val centerPaint = Paint()
/** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
var isDisplayConfigured: Boolean = false
private set
init {
this.setWillNotDraw(false)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
overlayPaint.color = Color.argb(120, 255, 0, 0)
overlayPaint.style = Paint.Style.FILL
sensorPaint.color = Color.argb(150, 134, 204, 255)
sensorPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
canvas.drawCircle(
overlayParams.sensorBounds.exactCenterX(),
overlayParams.sensorBounds.exactCenterY(),
overlayParams.sensorBounds.width().toFloat() / 2,
centerPaint
)
}
fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
mUdfpsDisplayMode = udfpsDisplayMode
}
fun configureDisplay(onDisplayConfigured: Runnable) {
isDisplayConfigured = true
mUdfpsDisplayMode?.enable(onDisplayConfigured)
}
fun unconfigureDisplay() {
isDisplayConfigured = false
mUdfpsDisplayMode?.disable(null /* onDisabled */)
}
}

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
import android.content.Context
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -23,9 +24,9 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
import android.hardware.fingerprint.IUdfpsOverlayController
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.util.Log
import android.view.LayoutInflater
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -41,14 +42,17 @@ private const val SENSOR_ID = 0
*/
@SysUISingleton
class UdfpsShell @Inject constructor(
commandRegistry: CommandRegistry
commandRegistry: CommandRegistry,
private val udfpsOverlay: UdfpsOverlay
) : Command {
/**
* Set in [UdfpsController.java] constructor, used to show and hide the UDFPS overlay.
* TODO: inject after b/229290039 is resolved
*/
var udfpsOverlayController: IUdfpsOverlayController? = null
var udfpsOverlayController: UdfpsController.UdfpsOverlayController? = null
var context: Context? = null
var inflater: LayoutInflater? = null
init {
commandRegistry.registerCommand("udfps") { this }
@@ -57,6 +61,11 @@ class UdfpsShell @Inject constructor(
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.size == 1 && args[0] == "hide") {
hideOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
hideOverlay()
showUdfpsOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
hideUdfpsOverlay()
} else if (args.size == 2 && args[0] == "show") {
showOverlay(getEnrollmentReason(args[1]))
} else {
@@ -104,7 +113,17 @@ class UdfpsShell @Inject constructor(
)
}
private fun showUdfpsOverlay() {
Log.v(TAG, "showUdfpsOverlay")
udfpsOverlay.show(REQUEST_ID)
}
private fun hideUdfpsOverlay() {
Log.v(TAG, "hideUdfpsOverlay")
udfpsOverlay.hide()
}
private fun hideOverlay() {
udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
}
}
}

View File

@@ -25,6 +25,7 @@ import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsOverlay
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.globalactions.GlobalActionsComponent
@@ -218,6 +219,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
/** Inject into UdfpsOverlay. */
@Binds
@IntoMap
@ClassKey(UdfpsOverlay::class)
abstract fun bindUdfpsOverlay(sysui: UdfpsOverlay): CoreStartable
/** Inject into MediaTttSenderCoordinator. */
@Binds
@IntoMap

View File

@@ -162,6 +162,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
// Sensor that's in the top left corner of the display in natural orientation.
val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
overlayParams = UdfpsOverlayParams(
sensorBounds,
sensorBounds,
DISPLAY_WIDTH,
DISPLAY_HEIGHT,

View File

@@ -414,7 +414,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};
final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};
final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
for (int i1 = 0; i1 <= 1; ++i1) {
for (int i2 = 0; i2 <= 1; ++i2) {
@@ -422,8 +422,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
for (int i4 = 0; i4 <= 1; ++i4) {
for (int i5 = 0; i5 <= 1; ++i5) {
final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
sensorBounds[i1], displayWidth[i2], displayHeight[i3],
scaleFactor[i4], rotation[i5]);
sensorBounds[i1], sensorBounds[i1], displayWidth[i2],
displayHeight[i3], scaleFactor[i4], rotation[i5]);
if (newParams.equals(oldParams)) {
continue;
@@ -466,8 +466,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Initialize the overlay.
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
rotation));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, rotation));
// Show the overlay.
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
@@ -477,8 +477,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Update overlay with the same parameters.
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
rotation));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, rotation));
mFgExecutor.runAllReady();
// Ensure the overlay was not recreated.
@@ -525,8 +525,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_0
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_0));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
@@ -542,8 +542,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_90
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_90));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
@@ -558,8 +558,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_270
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_270));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
mBiometricsExecutor.runAllReady();
@@ -574,8 +574,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// Test ROTATION_180
reset(mAlternateTouchProvider);
mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_180));
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);

View File

@@ -68,7 +68,8 @@ class UdfpsViewTest : SysuiTestCase() {
view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
view.animationViewController = animationViewController
val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect
view.overlayParams = UdfpsOverlayParams(sensorBounds, 1920, 1080, 1f, Surface.ROTATION_0)
view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920,
1080, 1f, Surface.ROTATION_0)
view.setUdfpsDisplayModeProvider(hbmProvider)
ViewUtils.attachView(view)
}
@@ -133,7 +134,8 @@ class UdfpsViewTest : SysuiTestCase() {
@Test
fun isNotWithinSensorArea() {
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat()))
.isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
}