Add anti burn-in logic for UdfpsView

Bug: 164503336
Test: manual
Test: atest com.android.systemui

Change-Id: I011931663243910ef94844a5da930ee8047c2e22
This commit is contained in:
Kevin Chyn
2020-08-14 14:16:57 -07:00
parent dc1e4a97a6
commit c3a1833175
7 changed files with 120 additions and 28 deletions

View File

@@ -1036,6 +1036,11 @@
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
direction that elements aer moved to prevent burn-in on AOD-->
<dimen name="udfps_burn_in_offset_x">8dp</dimen>
<dimen name="udfps_burn_in_offset_y">8dp</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
<dimen name="bottom_padding">48dp</dimen>

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -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) {

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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,