Add anti burn-in logic for UdfpsView
Bug: 164503336 Test: manual Test: atest com.android.systemui Change-Id: I011931663243910ef94844a5da930ee8047c2e22
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user