Merge "Refactor UDFPS display logic and remove illumination" into tm-qpr-dev am: 1ecaa4d68e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19949884

Change-Id: Ib162b02b868f3befdf8336295c2535ecfeec0ba4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Ilya Matyukhin
2022-09-20 00:22:06 +00:00
committed by Automerger Merge Worker
21 changed files with 174 additions and 275 deletions

View File

@@ -315,17 +315,17 @@ public interface BiometricFingerprintConstants {
int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
/**
* Whether the FingerprintAcquired message is a signal to turn off HBM
* Whether the FingerprintAcquired message is a signal to disable the UDFPS display mode.
* We want to disable the UDFPS mode as soon as possible to conserve power and provide better
* UX. For example, prolonged high-brightness illumination of optical sensors can be unpleasant
* to the user, can cause long term display burn-in, and can drain the battery faster.
*/
static boolean shouldTurnOffHbm(@FingerprintAcquired int acquiredInfo) {
static boolean shouldDisableUdfpsDisplayMode(@FingerprintAcquired int acquiredInfo) {
switch (acquiredInfo) {
case FINGERPRINT_ACQUIRED_START:
// Authentication just began
// Keep the UDFPS mode because the authentication just began.
return false;
case FINGERPRINT_ACQUIRED_GOOD:
// Good image captured. Turn off HBM. Success/Reject comes after, which is when
// hideUdfpsOverlay will be called.
return true;
case FINGERPRINT_ACQUIRED_PARTIAL:
case FINGERPRINT_ACQUIRED_INSUFFICIENT:
case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
@@ -334,11 +334,12 @@ public interface BiometricFingerprintConstants {
case FINGERPRINT_ACQUIRED_IMMOBILE:
case FINGERPRINT_ACQUIRED_TOO_BRIGHT:
case FINGERPRINT_ACQUIRED_VENDOR:
// Bad image captured. Turn off HBM. Matcher will not run, so there's no need to
// keep HBM on.
// Disable the UDFPS mode because the image capture has finished. The overlay
// can be hidden later, once the authentication result arrives.
return true;
case FINGERPRINT_ACQUIRED_UNKNOWN:
default:
// Keep the UDFPS mode in case of an unknown message.
return false;
}
}

View File

@@ -4834,9 +4834,6 @@
-->
</array>
<!-- How long it takes for the HW to start illuminating after the illumination is requested. -->
<integer name="config_udfps_illumination_transition_ms">50</integer>
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>

View File

@@ -2695,7 +2695,6 @@
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_sfps_enroll_stage_thresholds" />

View File

@@ -291,7 +291,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
});
mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination);
mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
}

View File

@@ -264,7 +264,7 @@ class AuthRippleController @Inject constructor(
acquireInfo: Int
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
BiometricFingerprintConstants.shouldTurnOffHbm(acquireInfo) &&
BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquireInfo) &&
acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
// received an 'acquiredBad' message, so immediately retract
mView.retractDwellRipple()

View File

@@ -54,13 +54,13 @@ public abstract class UdfpsAnimationView extends FrameLayout {
getDrawable().onSensorRectUpdated(bounds);
}
void onIlluminationStarting() {
getDrawable().setIlluminationShowing(true);
void onDisplayConfiguring() {
getDrawable().setDisplayConfigured(true);
getDrawable().invalidateSelf();
}
void onIlluminationStopped() {
getDrawable().setIlluminationShowing(false);
void onDisplayUnconfigured() {
getDrawable().setDisplayConfigured(false);
getDrawable().invalidateSelf();
}

View File

@@ -31,10 +31,10 @@ import java.io.PrintWriter
/**
* Handles:
* 1. registering for listeners when its view is attached and unregistering on view detached
* 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide
* 2. pausing UDFPS when FingerprintManager may still be running but we temporarily want to hide
* the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
* 3. sending events to its view including:
* - illumination events
* - enabling and disabling of the UDFPS display mode
* - sensor position changes
* - doze time event
*/
@@ -167,19 +167,20 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
}
/**
* Udfps has started illuminating and the fingerprint manager is working on authenticating.
* The display began transitioning into the UDFPS mode and the fingerprint manager started
* authenticating.
*/
fun onIlluminationStarting() {
view.onIlluminationStarting()
fun onDisplayConfiguring() {
view.onDisplayConfiguring()
view.postInvalidate()
}
/**
* Udfps has stopped illuminating and the fingerprint manager is no longer attempting to
* authenticate.
* The display transitioned away from the UDFPS mode and the fingerprint manager stopped
* authenticating.
*/
fun onIlluminationStopped() {
view.onIlluminationStopped()
fun onDisplayUnconfigured() {
view.onDisplayUnconfigured()
view.postInvalidate()
}
@@ -197,4 +198,4 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
* Called when a view should announce an accessibility event.
*/
open fun doAnnounceForAccessibility(str: String) {}
}
}

View File

@@ -86,7 +86,7 @@ import kotlin.Unit;
/**
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and coordinates triggering of the high-brightness mode (HBM).
* and toggles the UDFPS display mode.
*
* Note that the current architecture is designed so that a single {@link UdfpsController}
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
@@ -123,7 +123,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Nullable private final UdfpsHbmProvider mHbmProvider;
@Nullable private final UdfpsDisplayModeProvider mUdfpsDisplayMode;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
@NonNull private final UnlockedScreenOffAnimationController
@@ -135,7 +135,6 @@ public class UdfpsController implements DozeReceiver {
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting int mSensorId;
private boolean mHalControlsIllumination;
@VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -147,10 +146,9 @@ public class UdfpsController implements DozeReceiver {
private int mActivePointerId = -1;
// The timestamp of the most recent touch log.
private long mTouchLogTime;
// Sensor has a capture (good or bad) for this touch. Do not need to illuminate for this
// particular touch event anymore. In other words, do not illuminate until user lifts and
// touches the sensor area again.
// TODO: We should probably try to make touch/illumination things more of a FSM
// Sensor has a capture (good or bad) for this touch. No need to enable the UDFPS display mode
// anymore for this particular touch event. In other words, do not enable the UDFPS mode until
// the user touches the sensor area again.
private boolean mAcquiredReceived;
// The current request from FingerprintService. Null if no current request.
@@ -211,8 +209,8 @@ public class UdfpsController implements DozeReceiver {
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
mSystemClock, mKeyguardStateController,
mUnlockedScreenOffAnimationController, mHalControlsIllumination,
mHbmProvider, requestId, reason, callback,
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator)));
}
@@ -236,7 +234,7 @@ public class UdfpsController implements DozeReceiver {
int sensorId,
@BiometricFingerprintConstants.FingerprintAcquired int acquiredInfo
) {
if (BiometricFingerprintConstants.shouldTurnOffHbm(acquiredInfo)) {
if (BiometricFingerprintConstants.shouldDisableUdfpsDisplayMode(acquiredInfo)) {
boolean acquiredGood = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD;
mFgExecutor.execute(() -> {
if (mOverlay == null) {
@@ -247,7 +245,7 @@ public class UdfpsController implements DozeReceiver {
mAcquiredReceived = true;
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
view.stopIllumination(); // turn off HBM
view.unconfigureDisplay();
}
if (acquiredGood) {
mOverlay.onAcquiredGood();
@@ -292,7 +290,7 @@ public class UdfpsController implements DozeReceiver {
/**
* Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
*
* @param sensorId sensor for which the overlay is getting updated.
* @param sensorId sensor for which the overlay is getting updated.
* @param overlayParams See {@link UdfpsOverlayParams}.
*/
public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
@@ -321,11 +319,6 @@ public class UdfpsController implements DozeReceiver {
mAuthControllerUpdateUdfpsLocation = r;
}
// TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this.
public void setHalControlsIllumination(boolean value) {
mHalControlsIllumination = value;
}
/**
* Calculate the pointer speed given a velocity tracker and the pointer id.
* This assumes that the velocity tracker has already been passed all relevant motion events.
@@ -369,8 +362,8 @@ public class UdfpsController implements DozeReceiver {
}
/**
* @param x coordinate
* @param y coordinate
* @param x coordinate
* @param y coordinate
* @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else,
* calculate from the display dimensions in portrait orientation
*/
@@ -423,7 +416,7 @@ public class UdfpsController implements DozeReceiver {
}
final UdfpsView udfpsView = mOverlay.getOverlayView();
final boolean isIlluminationRequested = udfpsView.isIlluminationRequested();
final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
boolean handled = false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_OUTSIDE:
@@ -507,7 +500,7 @@ public class UdfpsController implements DozeReceiver {
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
if (!isIlluminationRequested && !mAcquiredReceived
if (!isDisplayConfigured && !mAcquiredReceived
&& !exceedsVelocityThreshold) {
final float scale = mOverlayParams.getScaleFactor();
@@ -598,7 +591,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull VibratorHelper vibrator,
@NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
@NonNull UdfpsShell udfpsShell,
@NonNull Optional<UdfpsHbmProvider> hbmProvider,
@NonNull Optional<UdfpsDisplayModeProvider> udfpsDisplayMode,
@NonNull KeyguardStateController keyguardStateController,
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
@@ -630,7 +623,7 @@ public class UdfpsController implements DozeReceiver {
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mHbmProvider = hbmProvider.orElse(null);
mUdfpsDisplayMode = udfpsDisplayMode.orElse(null);
screenLifecycle.addObserver(mScreenObserver);
mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
mConfigurationController = configurationController;
@@ -804,15 +797,14 @@ public class UdfpsController implements DozeReceiver {
}
/**
* Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
* user explicitly lifts their finger. Generally, this should be called whenever udfps fails
* or errors.
* Cancel UDFPS affordances - ability to hide the UDFPS overlay before the user explicitly
* lifts their finger. Generally, this should be called on errors in the authentication flow.
*
* The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
* ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
* This should be called when authentication either succeeds or fails. Failing to cancel the
* scan will leave the screen in high brightness mode and will show the HbmSurfaceView until
* the user lifts their finger.
* scan will leave the display in the UDFPS mode until the user lifts their finger. On optical
* sensors, this can result in illumination persisting for longer than necessary.
*/
void onCancelUdfps() {
if (mOverlay != null && mOverlay.getOverlayView() != null) {
@@ -874,7 +866,7 @@ public class UdfpsController implements DozeReceiver {
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
view.startIllumination(() -> {
view.configureDisplay(() -> {
if (mAlternateTouchProvider != null) {
mBiometricExecutor.execute(() -> {
mAlternateTouchProvider.onUiReady();
@@ -914,8 +906,8 @@ public class UdfpsController implements DozeReceiver {
}
}
mOnFingerDown = false;
if (view.isIlluminationRequested()) {
view.stopIllumination();
if (view.isDisplayConfigured()) {
view.unconfigureDisplay();
}
}

View File

@@ -77,8 +77,7 @@ class UdfpsControllerOverlay(
private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
private val halControlsIllumination: Boolean,
private var hbmProvider: UdfpsHbmProvider,
private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
val requestId: Long,
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
@@ -102,8 +101,8 @@ class UdfpsControllerOverlay(
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 or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
@@ -140,8 +139,7 @@ class UdfpsControllerOverlay(
R.layout.udfps_view, null, false
) as UdfpsView).apply {
overlayParams = params
halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination
setHbmProvider(hbmProvider)
setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
val animation = inflateUdfpsAnimation(this, controller)
if (animation != null) {
animation.init()
@@ -250,8 +248,8 @@ class UdfpsControllerOverlay(
val wasShowing = isShowing
overlayView?.apply {
if (isIlluminationRequested) {
stopIllumination()
if (isDisplayConfigured) {
unconfigureDisplay()
}
windowManager.removeView(this)
setOnTouchListener(null)

View File

@@ -0,0 +1,48 @@
/*
* 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.Nullable;
/**
* Interface for toggling the optimal display mode for the under-display fingerprint sensor
* (UDFPS). For example, the implementation might change the refresh rate and activate a
* high-brightness mode.
*/
public interface UdfpsDisplayModeProvider {
/**
* Enables the optimal display mode for UDFPS. The mode will persist until
* {@link #disable(Runnable)} is called.
*
* This call must be made from the UI thread. The callback, if provided, will also be invoked
* from the UI thread.
*
* @param onEnabled A runnable that will be executed once the mode is enabled.
*/
void enable(@Nullable Runnable onEnabled);
/**
* Disables the mode that was enabled by {@link #enable(Runnable)}.
*
* The call must be made from the UI thread. The callback, if provided, will also be invoked
* from the UI thread.
*
* @param onDisabled A runnable that will be executed once mode is disabled.
*/
void disable(@Nullable Runnable onDisabled);
}

View File

@@ -51,7 +51,7 @@ abstract class UdfpsDrawable(
invalidateSelf()
}
var isIlluminationShowing: Boolean = false
var isDisplayConfigured: Boolean = false
set(showing) {
if (field == showing) {
return

View File

@@ -197,7 +197,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
@Override
public void draw(@NonNull Canvas canvas) {
if (isIlluminationShowing()) {
if (isDisplayConfigured()) {
return;
}

View File

@@ -23,7 +23,7 @@ import android.graphics.Canvas
*/
class UdfpsFpDrawable(context: Context) : UdfpsDrawable(context) {
override fun draw(canvas: Canvas) {
if (isIlluminationShowing) {
if (isDisplayConfigured) {
return
}
fingerprintDrawable.draw(canvas)

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2021 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.Nullable;
/**
* Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
* enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
* illumination is no longer necessary.
*/
public interface UdfpsHbmProvider {
/**
* UdfpsView will call this to enable the HBM when the fingerprint illumination is needed.
*
* This method is a no-op when some type of HBM is already enabled.
*
* This method must be called from the UI thread. The callback, if provided, will also be
* invoked from the UI thread.
*
* @param onHbmEnabled A runnable that will be executed once HBM is enabled.
*
* TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
* This only makes sense now because vendor code may rely on the side effects of enableHbm.
*/
void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled);
/**
* UdfpsView will call this to disable HBM when illumination is no longer needed.
*
* This method will disable HBM if HBM is enabled. Otherwise, if HBM is already disabled,
* this method is a no-op.
*
* The call must be made from the UI thread. The callback, if provided, will also be invoked
* from the UI thread.
*
* @param onHbmDisabled A runnable that will be executed once HBM is disabled.
*/
void disableHbm(@Nullable Runnable onHbmDisabled);
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2021 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.Nullable;
/**
* Interface that should be implemented by UI's that need to coordinate user touches,
* views/animations, and modules that start/stop display illumination.
*/
interface UdfpsIlluminator {
/**
* @param hbmProvider Invoked when HBM should be enabled or disabled.
*/
void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider);
/**
* Invoked when illumination should start.
* @param onIlluminatedRunnable Invoked when the display has been illuminated.
*/
void startIllumination(@Nullable Runnable onIlluminatedRunnable);
/**
* Invoked when illumination should end.
*/
void stopIllumination();
}

View File

@@ -101,11 +101,11 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
@Override
void onIlluminationStarting() {
void onDisplayConfiguring() {
}
@Override
void onIlluminationStopped() {
void onDisplayUnconfigured() {
}
@Override

View File

@@ -36,12 +36,12 @@ private const val TAG = "UdfpsView"
class UdfpsView(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs), DozeReceiver, UdfpsIlluminator {
) : FrameLayout(context, attrs), DozeReceiver {
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
// overlayParams.sensorBounds
private val sensorRect = RectF()
private var hbmProvider: UdfpsHbmProvider? = null
private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
private val debugTextPaint = Paint().apply {
isAntiAlias = true
color = Color.BLUE
@@ -56,19 +56,12 @@ class UdfpsView(
a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
}
private val onIlluminatedDelayMs = context.resources.getInteger(
com.android.internal.R.integer.config_udfps_illumination_transition_ms
).toLong()
/** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
var animationViewController: UdfpsAnimationViewController<*>? = null
/** Parameters that affect the position and size of the overlay. */
var overlayParams = UdfpsOverlayParams()
/** Whether the HAL is responsible for enabling and disabling of LHBM. */
var halControlsIllumination: Boolean = true
/** Debug message. */
var debugMessage: String? = null
set(value) {
@@ -76,12 +69,12 @@ class UdfpsView(
postInvalidate()
}
/** When [startIllumination] has been called but not stopped via [stopIllumination]. */
var isIlluminationRequested: Boolean = false
/** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
var isDisplayConfigured: Boolean = false
private set
override fun setHbmProvider(provider: UdfpsHbmProvider?) {
hbmProvider = provider
fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) {
mUdfpsDisplayMode = udfpsDisplayModeProvider
}
// Don't propagate any touch events to the child views.
@@ -124,7 +117,7 @@ class UdfpsView(
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (!isIlluminationRequested) {
if (!isDisplayConfigured) {
if (!debugMessage.isNullOrEmpty()) {
canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint)
}
@@ -147,36 +140,15 @@ class UdfpsView(
!(animationViewController?.shouldPauseAuth() ?: false)
}
/**
* Start and run [onIlluminatedRunnable] when the first illumination frame reaches the panel.
*/
override fun startIllumination(onIlluminatedRunnable: Runnable?) {
isIlluminationRequested = true
animationViewController?.onIlluminationStarting()
doIlluminate(onIlluminatedRunnable)
fun configureDisplay(onDisplayConfigured: Runnable) {
isDisplayConfigured = true
animationViewController?.onDisplayConfiguring()
mUdfpsDisplayMode?.enable(onDisplayConfigured)
}
private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
// TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
// This only makes sense now because vendor code may rely on the side effects of enableHbm.
hbmProvider?.enableHbm(halControlsIllumination) {
if (onIlluminatedRunnable != null) {
if (halControlsIllumination) {
onIlluminatedRunnable.run()
} else {
// No framework API can reliably tell when a frame reaches the panel. A timeout
// is the safest solution.
postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
}
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
}
}
}
override fun stopIllumination() {
isIlluminationRequested = false
animationViewController?.onIlluminationStopped()
hbmProvider?.disableHbm(null /* onHbmDisabled */)
fun unconfigureDisplay() {
isDisplayConfigured = false
animationViewController?.onDisplayUnconfigured()
mUdfpsDisplayMode?.disable(null /* onDisabled */)
}
}

View File

@@ -30,7 +30,7 @@ import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
import com.android.systemui.biometrics.UdfpsHbmProvider;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -197,7 +197,7 @@ public abstract class SystemUIModule {
abstract CentralSurfaces optionalCentralSurfaces();
@BindsOptionalOf
abstract UdfpsHbmProvider optionalUdfpsHbmProvider();
abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider();
@BindsOptionalOf
abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();

View File

@@ -17,23 +17,13 @@
package com.android.systemui.biometrics
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.biometrics.BiometricOverlayConstants.*
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.Surface
import android.view.*
import android.view.Surface.Rotation
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
@@ -65,7 +55,6 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
private const val HAL_CONTROLS_ILLUMINATION = true
private const val REQUEST_ID = 2L
// Dimensions for the current display resolution.
@@ -95,8 +84,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var hbmProvider: UdfpsHbmProvider
@Mock private lateinit var unlockedScreenOffAnimationController:
UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -130,8 +120,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator
)
block()
}
@@ -246,7 +237,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
verify(udfpsView).setHbmProvider(eq(hbmProvider))
verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode))
verify(udfpsView).animationViewController = any()
verify(udfpsView).addView(any())
@@ -351,12 +342,12 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
}
@Test
fun stopIlluminatingOnHide() = withReason(REASON_AUTH_BP) {
whenever(udfpsView.isIlluminationRequested).thenReturn(true)
fun unconfigureDisplayOnHide() = withReason(REASON_AUTH_BP) {
whenever(udfpsView.isDisplayConfigured).thenReturn(true)
controllerOverlay.show(udfpsController, overlayParams)
controllerOverlay.hide()
verify(udfpsView).stopIllumination()
verify(udfpsView).unconfigureDisplay()
}
@Test

View File

@@ -125,7 +125,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
@Mock
private UdfpsHbmProvider mHbmProvider;
private UdfpsDisplayModeProvider mDisplayModeProvider;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -193,7 +193,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
private IUdfpsOverlayController mOverlayController;
@Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
@Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
@Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;
@Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
@Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
private ScreenLifecycle.Observer mScreenObserver;
@@ -256,7 +256,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mVibrator,
mUdfpsHapticsSimulator,
mUdfpsShell,
Optional.of(mHbmProvider),
Optional.of(mDisplayModeProvider),
mKeyguardStateController,
mDisplayManager,
mHandler,
@@ -512,7 +512,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
final float expectedMajor = touchMajor / scaleFactor;
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// Show the overlay.
@@ -590,7 +590,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void fingerDown() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
@@ -617,12 +617,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
anyFloat(), anyFloat());
verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
// AND illumination begins
verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
// AND display configuration begins
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
// AND onIlluminatedRunnable notifies FingerprintManager about onUiReady
mOnIlluminatedRunnableCaptor.getValue().run();
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
mOnDisplayConfiguredCaptor.getValue().run();
mBiometricsExecutor.runAllReady();
InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
inOrder.verify(mAlternateTouchProvider).onUiReady();
@@ -640,10 +640,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
mFgExecutor.runAllReady();
// THEN illumination begins
// AND onIlluminatedRunnable that notifies FingerprintManager is set
verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
mOnIlluminatedRunnableCaptor.getValue().run();
// THEN display configuration begins
// AND onDisplayConfigured notifies FingerprintManager about onUiReady
verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
mOnDisplayConfiguredCaptor.getValue().run();
mBiometricsExecutor.runAllReady();
verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -661,11 +661,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.runAllReady();
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it is cancelled
mUdfpsController.onCancelUdfps();
// THEN the illumination is hidden
verify(mUdfpsView).stopIllumination();
// THEN the display is unconfigured
verify(mUdfpsView).unconfigureDisplay();
}
@Test
@@ -678,12 +678,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
// THEN the illumination is hidden
verify(mUdfpsView).stopIllumination();
// THEN the display is unconfigured
verify(mUdfpsView).unconfigureDisplay();
}
@Test
@@ -698,8 +698,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN no illumination because screen is off
verify(mUdfpsView, never()).startIllumination(any());
// THEN display doesn't get configured because it's off
verify(mUdfpsView, never()).configureDisplay(any());
}
@Test
@@ -715,14 +715,14 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN no illumination because screen is off
verify(mUdfpsView, never()).startIllumination(any());
// THEN display doesn't get configured because it's off
verify(mUdfpsView, never()).configureDisplay(any());
}
@Test
public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN that the overlay is showing and a11y touch exploration enabled
@@ -757,7 +757,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// GIVEN that the overlay is showing and a11y touch exploration NOT enabled

View File

@@ -36,13 +36,12 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.nullable
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
@@ -57,7 +56,7 @@ class UdfpsViewTest : SysuiTestCase() {
var rule = MockitoJUnit.rule()
@Mock
lateinit var hbmProvider: UdfpsHbmProvider
lateinit var hbmProvider: UdfpsDisplayModeProvider
@Mock
lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
@@ -66,13 +65,11 @@ class UdfpsViewTest : SysuiTestCase() {
@Before
fun setup() {
context.setTheme(R.style.Theme_AppCompat)
context.orCreateTestableResources.addOverride(
com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
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.setHbmProvider(hbmProvider)
view.setUdfpsDisplayModeProvider(hbmProvider)
ViewUtils.attachView(view)
}
@@ -143,27 +140,27 @@ class UdfpsViewTest : SysuiTestCase() {
@Test
fun startAndStopIllumination() {
val onDone: Runnable = mock()
view.startIllumination(onDone)
view.configureDisplay(onDone)
val illuminator = withArgCaptor<Runnable> {
verify(hbmProvider).enableHbm(anyBoolean(), capture())
verify(hbmProvider).enable(capture())
}
assertThat(view.isIlluminationRequested).isTrue()
verify(animationViewController).onIlluminationStarting()
verify(animationViewController, never()).onIlluminationStopped()
assertThat(view.isDisplayConfigured).isTrue()
verify(animationViewController).onDisplayConfiguring()
verify(animationViewController, never()).onDisplayUnconfigured()
verify(onDone, never()).run()
// fake illumination event
illuminator.run()
waitForLooper()
verify(onDone).run()
verify(hbmProvider, never()).disableHbm(any())
verify(hbmProvider, never()).disable(any())
view.stopIllumination()
assertThat(view.isIlluminationRequested).isFalse()
verify(animationViewController).onIlluminationStopped()
verify(hbmProvider).disableHbm(nullable(Runnable::class.java))
view.unconfigureDisplay()
assertThat(view.isDisplayConfigured).isFalse()
verify(animationViewController).onDisplayUnconfigured()
verify(hbmProvider).disable(nullable(Runnable::class.java))
}
private fun waitForLooper() = TestableLooper.get(this).processAllMessages()