From c3a1833175317ac16a2e6de098b1f079709503e2 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Fri, 14 Aug 2020 14:16:57 -0700 Subject: [PATCH] Add anti burn-in logic for UdfpsView Bug: 164503336 Test: manual Test: atest com.android.systemui Change-Id: I011931663243910ef94844a5da930ee8047c2e22 --- packages/SystemUI/res/values/dimens.xml | 5 ++ .../systemui/biometrics/AuthController.java | 26 ++++++--- .../systemui/biometrics/UdfpsController.java | 42 ++++++++++----- .../systemui/biometrics/UdfpsView.java | 54 +++++++++++++++++-- .../statusbar/phone/DozeServiceHost.java | 7 ++- .../biometrics/AuthControllerTest.java | 10 ++-- .../statusbar/phone/DozeServiceHostTest.java | 4 +- 7 files changed, 120 insertions(+), 28 deletions(-) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 122fcb21a9f43..765a9422585a9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1036,6 +1036,11 @@ 15dp + + 8dp + 8dp + 8dp 0dp 48dp diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index ade106fb2223b..3cd4fca0e8771 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -49,6 +49,8 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.systemui.SystemUI; +import com.android.systemui.doze.DozeReceiver; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import java.util.List; @@ -62,12 +64,13 @@ import javax.inject.Singleton; */ @Singleton public class AuthController extends SystemUI implements CommandQueue.Callbacks, - AuthDialogCallback { + AuthDialogCallback, DozeReceiver { - private static final String TAG = "BiometricPrompt/AuthController"; + private static final String TAG = "AuthController"; private static final boolean DEBUG = true; private final CommandQueue mCommandQueue; + private final StatusBarStateController mStatusBarStateController; private final Injector mInjector; // TODO: These should just be saved from onSaveState @@ -77,6 +80,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private Handler mHandler = new Handler(Looper.getMainLooper()); private WindowManager mWindowManager; + @Nullable private UdfpsController mUdfpsController; @VisibleForTesting IActivityTaskManager mActivityTaskManager; @@ -142,6 +146,13 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } }; + @Override + public void dozeTimeTick() { + if (mUdfpsController != null) { + mUdfpsController.dozeTimeTick(); + } + } + @Override public void onTryAgainPressed() { if (mReceiver == null) { @@ -251,14 +262,17 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } @Inject - public AuthController(Context context, CommandQueue commandQueue) { - this(context, commandQueue, new Injector()); + public AuthController(Context context, CommandQueue commandQueue, + StatusBarStateController statusBarStateController) { + this(context, commandQueue, statusBarStateController, new Injector()); } @VisibleForTesting - AuthController(Context context, CommandQueue commandQueue, Injector injector) { + AuthController(Context context, CommandQueue commandQueue, + StatusBarStateController statusBarStateController, Injector injector) { super(context); mCommandQueue = commandQueue; + mStatusBarStateController = statusBarStateController; mInjector = injector; IntentFilter filter = new IntentFilter(); @@ -280,7 +294,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, fpm.getSensorProperties(); for (FingerprintSensorProperties props : fingerprintSensorProperties) { if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) { - mUdfpsController = new UdfpsController(mContext); + mUdfpsController = new UdfpsController(mContext, mStatusBarStateController); break; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index fdd3b2603a893..82fb80892ab16 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import android.annotation.NonNull; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; @@ -39,6 +40,8 @@ import android.view.WindowManager; import com.android.internal.BrightnessSynchronizer; import com.android.systemui.R; +import com.android.systemui.doze.DozeReceiver; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import java.io.FileWriter; import java.io.IOException; @@ -47,7 +50,7 @@ import java.io.IOException; * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, * and coordinates triggering of the high-brightness mode (HBM). */ -class UdfpsController { +class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; // Gamma approximation for the sRGB color space. private static final float DISPLAY_GAMMA = 2.2f; @@ -62,6 +65,7 @@ class UdfpsController { private final String mHbmPath; private final String mHbmEnableCommand; private final String mHbmDisableCommand; + private final boolean mHbmSupported; // Brightness in nits in the high-brightness mode. private final float mHbmNits; // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a @@ -122,7 +126,8 @@ class UdfpsController { } }; - UdfpsController(Context context) { + UdfpsController(@NonNull Context context, + @NonNull StatusBarStateController statusBarStateController) { mFingerprintManager = context.getSystemService(FingerprintManager.class); mWindowManager = context.getSystemService(WindowManager.class); mContentResolver = context.getContentResolver(); @@ -135,7 +140,9 @@ class UdfpsController { mHbmEnableCommand = context.getResources().getString(R.string.udfps_hbm_enable_command); mHbmDisableCommand = context.getResources().getString(R.string.udfps_hbm_disable_command); - mView.setHbmSupported(!TextUtils.isEmpty(mHbmPath)); + mHbmSupported = !TextUtils.isEmpty(mHbmPath); + mView.setHbmSupported(mHbmSupported); + statusBarStateController.addCallback(mView); // This range only consists of the minimum and maximum values, which only cover // non-high-brightness mode. @@ -170,6 +177,11 @@ class UdfpsController { mIsOverlayShowing = false; } + @Override + public void dozeTimeTick() { + mView.dozeTimeTick(); + } + private void showUdfpsOverlay() { mHandler.post(() -> { if (!mIsOverlayShowing) { @@ -232,9 +244,11 @@ class UdfpsController { mView.setScrimAlpha(computeScrimOpacity()); mView.showScrimAndDot(); try { - FileWriter fw = new FileWriter(mHbmPath); - fw.write(mHbmEnableCommand); - fw.close(); + if (mHbmSupported) { + FileWriter fw = new FileWriter(mHbmPath); + fw.write(mHbmEnableCommand); + fw.close(); + } mFingerprintManager.onFingerDown(x, y, minor, major); } catch (IOException e) { mView.hideScrimAndDot(); @@ -246,13 +260,15 @@ class UdfpsController { mFingerprintManager.onFingerUp(); // Hiding the scrim before disabling HBM results in less noticeable flicker. mView.hideScrimAndDot(); - try { - FileWriter fw = new FileWriter(mHbmPath); - fw.write(mHbmDisableCommand); - fw.close(); - } catch (IOException e) { - mView.showScrimAndDot(); - Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage()); + if (mHbmSupported) { + try { + FileWriter fw = new FileWriter(mHbmPath); + fw.write(mHbmDisableCommand); + fw.close(); + } catch (IOException e) { + mView.showScrimAndDot(); + Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 69883eaa76a7c..36353fa969560 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -16,6 +16,8 @@ package com.android.systemui.biometrics; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -23,17 +25,22 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.view.View; import android.view.ViewTreeObserver; import com.android.systemui.R; +import com.android.systemui.doze.DozeReceiver; +import com.android.systemui.plugins.statusbar.StatusBarStateController; /** * A full screen view with a configurable illumination dot and scrim. */ -public class UdfpsView extends View { +public class UdfpsView extends View implements DozeReceiver, + StatusBarStateController.StateListener { private static final String TAG = "UdfpsView"; // Values in pixels. @@ -53,10 +60,17 @@ public class UdfpsView extends View { private final float mSensorRadius; private final float mSensorMarginBottom; private final float mSensorTouchAreaCoefficient; + private final int mMaxBurnInOffsetX; + private final int mMaxBurnInOffsetY; private final Rect mTouchableRegion; private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener; + // AOD anti-burn-in offsets + private float mInterpolatedDarkAmount; + private float mBurnInOffsetX; + private float mBurnInOffsetY; + private boolean mIsScrimShowing; private boolean mHbmSupported; private String mDebugMessage; @@ -85,6 +99,11 @@ public class UdfpsView extends View { a.recycle(); } + mMaxBurnInOffsetX = getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mScrimRect = new Rect(); mScrimPaint = new Paint(0 /* flags */); mScrimPaint.setColor(Color.BLACK); @@ -113,6 +132,29 @@ public class UdfpsView extends View { mIsScrimShowing = false; } + @Override + public void dozeTimeTick() { + updateAodPosition(); + } + + @Override + public void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateAodPosition(); + } + + private void updateAodPosition() { + mBurnInOffsetX = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) + - mMaxBurnInOffsetX, + mInterpolatedDarkAmount); + mBurnInOffsetY = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) + - 0.5f * mMaxBurnInOffsetY, + mInterpolatedDarkAmount); + postInvalidate(); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -142,14 +184,20 @@ public class UdfpsView extends View { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); + if (mIsScrimShowing && mHbmSupported) { // Only draw the scrim if HBM is supported. canvas.drawRect(mScrimRect, mScrimPaint); } - canvas.drawText(mDebugMessage, 0, 60, mDebugTextPaint); - + // Translation should affect everything but the scrim. + canvas.save(); + canvas.translate(mBurnInOffsetX, mBurnInOffsetY); + if (!TextUtils.isEmpty(mDebugMessage)) { + canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint); + } canvas.drawOval(mSensorRect, mSensorPaint); + canvas.restore(); } void setHbmSupported(boolean hbmSupported) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index de74d4e04921a..5aca0dfd677ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -31,6 +31,7 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.assist.AssistManager; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; @@ -88,6 +89,7 @@ public final class DozeServiceHost implements DozeHost { private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private final LockscreenLockIconController mLockscreenLockIconController; + private final AuthController mAuthController; private NotificationIconAreaController mNotificationIconAreaController; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private NotificationPanelViewController mNotificationPanel; @@ -110,7 +112,8 @@ public final class DozeServiceHost implements DozeHost { PulseExpansionHandler pulseExpansionHandler, NotificationShadeWindowController notificationShadeWindowController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, - LockscreenLockIconController lockscreenLockIconController) { + LockscreenLockIconController lockscreenLockIconController, + AuthController authController) { super(); mDozeLog = dozeLog; mPowerManager = powerManager; @@ -130,6 +133,7 @@ public final class DozeServiceHost implements DozeHost { mNotificationShadeWindowController = notificationShadeWindowController; mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; mLockscreenLockIconController = lockscreenLockIconController; + mAuthController = authController; } // TODO: we should try to not pass status bar in here if we can avoid it. @@ -297,6 +301,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void dozeTimeTick() { mNotificationPanel.dozeTimeTick(); + mAuthController.dozeTimeTick(); if (mAmbientIndicationContainer instanceof DozeReceiver) { ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index d4a94c5b9e66c..c8566c599108e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -54,6 +54,7 @@ import android.testing.TestableLooper.RunWithLooper; import com.android.internal.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import org.junit.Before; @@ -103,8 +104,8 @@ public class AuthControllerTest extends SysuiTestCase { when(mDialog1.isAllowDeviceCredentials()).thenReturn(false); when(mDialog2.isAllowDeviceCredentials()).thenReturn(false); - mAuthController = new TestableAuthController( - context, mock(CommandQueue.class), new MockInjector()); + mAuthController = new TestableAuthController(context, mock(CommandQueue.class), + mock(StatusBarStateController.class), new MockInjector()); mAuthController.start(); } @@ -502,8 +503,9 @@ public class AuthControllerTest extends SysuiTestCase { private int mBuildCount = 0; private PromptInfo mLastBiometricPromptInfo; - TestableAuthController(Context context, CommandQueue commandQueue, Injector injector) { - super(context, commandQueue, injector); + TestableAuthController(Context context, CommandQueue commandQueue, + StatusBarStateController statusBarStateController, Injector injector) { + super(context, commandQueue, statusBarStateController, injector); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index a5f4e51ea13c7..713a7c92e1cfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; +import com.android.systemui.biometrics.AuthController; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -90,6 +91,7 @@ public class DozeServiceHostTest extends SysuiTestCase { @Mock private View mAmbientIndicationContainer; @Mock private BiometricUnlockController mBiometricUnlockController; @Mock private LockscreenLockIconController mLockscreenLockIconController; + @Mock private AuthController mAuthController; @Before public void setup() { @@ -100,7 +102,7 @@ public class DozeServiceHostTest extends SysuiTestCase { mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor, mVisualStabilityManager, mPulseExpansionHandler, mNotificationShadeWindowController, mNotificationWakeUpCoordinator, - mLockscreenLockIconController); + mLockscreenLockIconController, mAuthController); mDozeServiceHost.initialize(mStatusBar, mNotificationIconAreaController, mStatusBarKeyguardViewManager, mNotificationShadeWindowViewController,