summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java227
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java263
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java42
3 files changed, 184 insertions, 348 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index b373cff489f6..008e8f506e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -22,29 +22,22 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RectF;
-import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
-import android.util.MathUtils;
-import android.util.Spline;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.BrightnessSynchronizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -53,10 +46,6 @@ import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.settings.SystemSettings;
-
-import java.io.FileWriter;
-import java.io.IOException;
import javax.inject.Inject;
@@ -73,16 +62,13 @@ import javax.inject.Inject;
*/
@SuppressWarnings("deprecation")
@SysUISingleton
-public class UdfpsController implements DozeReceiver {
+public class UdfpsController implements UdfpsView.HbmCallback, DozeReceiver {
private static final String TAG = "UdfpsController";
- // Gamma approximation for the sRGB color space.
- private static final float DISPLAY_GAMMA = 2.2f;
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
private final Context mContext;
private final FingerprintManager mFingerprintManager;
private final WindowManager mWindowManager;
- private final SystemSettings mSystemSettings;
private final DelayableExecutor mFgExecutor;
private final StatusBarStateController mStatusBarStateController;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -90,23 +76,6 @@ public class UdfpsController implements DozeReceiver {
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
private final WindowManager.LayoutParams mCoreLayoutParams;
private final UdfpsView mView;
- // Debugfs path to control the high-brightness mode.
- 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 mMaxNits;
- // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
- // brightness in nits.
- private final Spline mBacklightToNitsSpline;
- // A spline mapping from a value in nits to a backlight value of a hypothetical panel whose
- // maximum backlight value corresponds to our panel's high-brightness mode.
- // The output is normalized to the range [0, 1.0].
- private Spline mNitsToHbmBacklightSpline;
- // Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
- // actual brightness value cannot be retrieved.
- private final float mDefaultBrightness;
// Indicates whether the overlay is currently showing. Even if it has been requested, it might
// not be showing.
private boolean mIsOverlayShowing;
@@ -152,7 +121,7 @@ public class UdfpsController implements DozeReceiver {
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
UdfpsView view = (UdfpsView) v;
- final boolean isFingerDown = view.isShowScrimAndDot();
+ final boolean isFingerDown = view.isIlluminationRequested();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
@@ -183,9 +152,7 @@ public class UdfpsController implements DozeReceiver {
@Main Resources resources,
LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
- DisplayManager displayManager,
WindowManager windowManager,
- SystemSettings systemSettings,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
@NonNull ScrimController scrimController) {
@@ -194,7 +161,6 @@ public class UdfpsController implements DozeReceiver {
// fingerprint manager should never be null.
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
- mSystemSettings = systemSettings;
mFgExecutor = fgExecutor;
mStatusBarStateController = statusBarStateController;
@@ -211,72 +177,18 @@ public class UdfpsController implements DozeReceiver {
PixelFormat.TRANSLUCENT);
mCoreLayoutParams.setTitle(TAG);
mCoreLayoutParams.setFitInsetsTypes(0);
+ mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mCoreLayoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
mView.setSensorProperties(mSensorProps);
+ mView.setHbmCallback(this);
- mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
- mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
- mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);
-
- mHbmSupported = !TextUtils.isEmpty(mHbmPath);
- mView.setHbmSupported(mHbmSupported);
scrimController.addScrimChangedListener(mView);
statusBarStateController.addCallback(mView);
- // This range only consists of the minimum and maximum values, which only cover
- // non-high-brightness mode.
- float[] nitsRange = toFloatArray(resources.obtainTypedArray(
- com.android.internal.R.array.config_screenBrightnessNits));
- if (nitsRange.length < 2) {
- throw new IllegalArgumentException(
- String.format("nitsRange.length: %d. Must be >= 2", nitsRange.length));
- }
-
- // The last value of this range corresponds to the high-brightness mode.
- float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
- if (nitsAutoBrightnessValues.length < 2) {
- throw new IllegalArgumentException(
- String.format("nitsAutoBrightnessValues.length: %d. Must be >= 2",
- nitsAutoBrightnessValues.length));
- }
-
- mMaxNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
- float[] hbmNitsRange = nitsRange.clone();
- hbmNitsRange[hbmNitsRange.length - 1] = mMaxNits;
-
- // This range only consists of the minimum and maximum backlight values, which only apply
- // in non-high-brightness mode.
- float[] normalizedBacklightRange = normalizeBacklightRange(
- resources.getIntArray(
- com.android.internal.R.array.config_screenBrightnessBacklight));
- if (normalizedBacklightRange.length < 2) {
- throw new IllegalArgumentException(
- String.format("normalizedBacklightRange.length: %d. Must be >= 2",
- normalizedBacklightRange.length));
- }
- if (normalizedBacklightRange.length != nitsRange.length) {
- throw new IllegalArgumentException(
- "normalizedBacklightRange.length != nitsRange.length");
- }
-
- mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
- mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
- mDefaultBrightness = obtainDefaultBrightness(mContext);
-
- // TODO(b/160025856): move to the "dump" method.
- Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0],
- nitsRange[nitsRange.length - 1]));
- Log.v(TAG, String.format("ctor | mHbmNitsRange: [%f, %f]", hbmNitsRange[0],
- hbmNitsRange[hbmNitsRange.length - 1]));
- Log.v(TAG, String.format("ctor | mNormalizedBacklightRange: [%f, %f]",
- normalizedBacklightRange[0],
- normalizedBacklightRange[normalizedBacklightRange.length - 1]));
-
mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
mIsOverlayShowing = false;
}
@@ -331,13 +243,33 @@ public class UdfpsController implements DozeReceiver {
}
private WindowManager.LayoutParams computeLayoutParams() {
+ // Default dimensions assume portrait mode.
+ mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
+ mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
+ mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius;
+ mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius;
+
Point p = new Point();
// Gets the size based on the current rotation of the display.
mContext.getDisplay().getRealSize(p);
- mCoreLayoutParams.width = p.x;
- mCoreLayoutParams.x = p.x;
- mCoreLayoutParams.height = p.y;
- mCoreLayoutParams.y = p.y;
+
+ // Transform dimensions if the device is in landscape mode.
+ switch (mContext.getDisplay().getRotation()) {
+ case Surface.ROTATION_90:
+ mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
+ mCoreLayoutParams.y =
+ p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
+ break;
+
+ case Surface.ROTATION_270:
+ mCoreLayoutParams.x =
+ p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
+ mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius;
+ break;
+
+ default:
+ // Do nothing to stay in portrait mode.
+ }
return mCoreLayoutParams;
}
@@ -402,36 +334,10 @@ public class UdfpsController implements DozeReceiver {
});
}
- // Returns a value in the range of [0, 255].
- private int computeScrimOpacity() {
- // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
- float backlightSetting = mSystemSettings.getFloatForUser(
- Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
- UserHandle.USER_CURRENT);
-
- // Constrain the backlight setting to [0.0f, 1.0f].
- float backlightValue = MathUtils.constrain(backlightSetting,
- PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX);
-
- // Interpolate the backlight value to nits.
- float nits = mBacklightToNitsSpline.interpolate(backlightValue);
-
- // Interpolate nits to a backlight value for a panel with enabled HBM.
- float interpolatedHbmBacklightValue = mNitsToHbmBacklightSpline.interpolate(nits);
-
- float gammaCorrectedHbmBacklightValue = (float) Math.pow(interpolatedHbmBacklightValue,
- 1.0f / DISPLAY_GAMMA);
- float scrimOpacity = PowerManager.BRIGHTNESS_MAX - gammaCorrectedHbmBacklightValue;
-
- // Interpolate the opacity value from [0.0f, 1.0f] to [0, 255].
- return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity);
- }
-
/**
* Request fingerprint scan.
*
- * This is intented to be called in response to a sensor that triggers an AOD interrupt for the
+ * This is intended to be called in response to a sensor that triggers an AOD interrupt for the
* fingerprint sensor.
*/
void onAodInterrupt(int screenX, int screenY, float major, float minor) {
@@ -451,7 +357,7 @@ public class UdfpsController implements DozeReceiver {
/**
* Cancel fingerprint scan.
*
- * This is intented to be called after the fingerprint scan triggered by the AOD interrupt
+ * This is intended to be called after the fingerprint scan triggered by the AOD interrupt
* either succeeds or fails.
*/
void onCancelAodInterrupt() {
@@ -466,65 +372,28 @@ public class UdfpsController implements DozeReceiver {
onFingerUp();
}
- protected void onFingerDown(int x, int y, float minor, float major) {
- if (mHbmSupported) {
- try {
- FileWriter fw = new FileWriter(mHbmPath);
- fw.write(mHbmEnableCommand);
- fw.close();
- } catch (IOException e) {
- mView.hideScrimAndDot();
- Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
- }
- }
- mView.setScrimAlpha(computeScrimOpacity());
- mView.setRunAfterShowingScrimAndDot(() -> {
- mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
- });
- mView.showScrimAndDot();
+ // This method can be called from the UI thread.
+ private void onFingerDown(int x, int y, float minor, float major) {
+ mView.setOnIlluminatedRunnable(
+ () -> mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
+ mView.startIllumination();
}
- protected void onFingerUp() {
+ // This method can be called from the UI thread.
+ private void onFingerUp() {
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
- // Hiding the scrim before disabling HBM results in less noticeable flicker.
- mView.hideScrimAndDot();
- 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());
- }
- }
- }
-
- private static float obtainDefaultBrightness(Context context) {
- return MathUtils.constrain(context.getDisplay().getBrightnessDefault(),
- PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+ mView.stopIllumination();
}
- private static float[] toFloatArray(TypedArray array) {
- final int n = array.length();
- float[] vals = new float[n];
- for (int i = 0; i < n; i++) {
- vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
- }
- array.recycle();
- return vals;
- }
-
- private static float[] normalizeBacklightRange(int[] backlight) {
- final int n = backlight.length;
- float[] normalizedBacklight = new float[n];
- for (int i = 0; i < n; i++) {
- normalizedBacklight[i] = BrightnessSynchronizer.brightnessIntToFloat(backlight[i]);
- }
- return normalizedBacklight;
+ @Override
+ public void enableHbm(Surface surface) {
+ // Do nothing. This method can be implemented for devices that require the high-brightness
+ // mode for fingerprint illumination.
}
- protected UdfpsView getView() {
- return mView;
+ @Override
+ public void disableHbm(Surface surface) {
+ // Do nothing. This method can be implemented for devices that require the high-brightness
+ // mode for fingerprint illumination.
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 96ecc7bdb017..4c05b886f8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -28,7 +28,7 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
+import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.text.TextUtils;
@@ -37,7 +37,6 @@ import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import android.view.ViewTreeObserver;
import com.android.systemui.R;
import com.android.systemui.doze.DozeReceiver;
@@ -48,68 +47,64 @@ import com.android.systemui.statusbar.phone.ScrimController;
* A full screen view with a configurable illumination dot and scrim.
*/
public class UdfpsView extends SurfaceView implements DozeReceiver,
- StatusBarStateController.StateListener, ScrimController.ScrimChangedListener {
+ StatusBarStateController.StateListener, ScrimController.ScrimChangedListener {
private static final String TAG = "UdfpsView";
- // Values in pixels.
+ /**
+ * 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.
+ */
+ interface HbmCallback {
+ /**
+ * UdfpsView will call this to enable the HBM before drawing the illumination dot.
+ *
+ * @param surface A valid surface for which the HBM should be enabled.
+ */
+ void enableHbm(@NonNull Surface surface);
+
+ /**
+ * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+ *
+ * @param surface A valid surface for which the HBM should be disabled.
+ */
+ void disableHbm(@NonNull Surface surface);
+ }
+
+ /**
+ * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
+ * several abstract methods that are not used here but require implementation.
+ */
+ private interface SimpleDrawable {
+ void draw(Canvas canvas);
+ }
+
+ // Radius in pixels.
private static final float SENSOR_SHADOW_RADIUS = 2.0f;
private static final int DEBUG_TEXT_SIZE_PX = 32;
- @NonNull private final Rect mScrimRect;
- @NonNull private final Paint mScrimPaint;
- @NonNull private final Paint mDebugTextPaint;
-
+ @NonNull private final SurfaceHolder mHolder;
@NonNull private final RectF mSensorRect;
@NonNull private final Paint mSensorPaint;
- private final float mSensorTouchAreaCoefficient;
+ @NonNull private final Paint mDebugTextPaint;
+ @NonNull private final SimpleDrawable mIlluminationDotDrawable;
+ @NonNull private final SimpleDrawable mClearSurfaceDrawable;
- // Stores rounded up values from mSensorRect. Necessary for APIs that only take Rect (not RecF).
- @NonNull private final Rect mTouchableRegion;
- // mInsetsListener is used to set the touchable region for our window. Our window covers the
- // whole screen, and by default its touchable region is the whole screen. We use
- // mInsetsListener to restrict the touchable region and allow the touches outside of the sensor
- // to propagate to the rest of the UI.
- @NonNull private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
@Nullable private UdfpsAnimation mUdfpsAnimation;
+ @Nullable private HbmCallback mHbmCallback;
+ @Nullable private Runnable mOnIlluminatedRunnable;
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
- private boolean mShowScrimAndDot;
- private boolean mIsHbmSupported;
+ private final float mSensorTouchAreaCoefficient;
@Nullable private String mDebugMessage;
+ private boolean mIlluminationRequested;
private int mStatusBarState;
private boolean mNotificationShadeExpanded;
private int mNotificationPanelAlpha;
- // Runnable that will be run after the illumination dot and scrim are shown.
- // The runnable is reset to null after it's executed once.
- @Nullable private Runnable mRunAfterShowingScrimAndDot;
-
- @NonNull private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
- @Override
- public void surfaceCreated(@NonNull SurfaceHolder holder) {
- Log.d(TAG, "Surface created");
- // SurfaceView sets this to true by default. We must set it to false to allow
- // onDraw to be called
- setWillNotDraw(false);
- }
-
- @Override
- public void surfaceChanged(@NonNull SurfaceHolder holder, int format,
- int width, int height) {
-
- }
-
- @Override
- public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
- Log.d(TAG, "Surface destroyed");
- // Must not draw when the surface is destroyed
- setWillNotDraw(true);
- }
- };
-
public UdfpsView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -126,18 +121,13 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
a.recycle();
}
- getHolder().addCallback(mSurfaceCallback);
- getHolder().setFormat(PixelFormat.TRANSLUCENT);
-
- mScrimRect = new Rect();
- mScrimPaint = new Paint(0 /* flags */);
- mScrimPaint.setColor(Color.BLACK);
+ mHolder = getHolder();
+ mHolder.setFormat(PixelFormat.RGBA_8888);
mSensorRect = new RectF();
mSensorPaint = new Paint(0 /* flags */);
mSensorPaint.setAntiAlias(true);
- mSensorPaint.setColor(Color.WHITE);
- mSensorPaint.setShadowLayer(SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
+ mSensorPaint.setARGB(255, 255, 255, 255);
mSensorPaint.setStyle(Paint.Style.FILL);
mDebugTextPaint = new Paint();
@@ -145,16 +135,13 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
- mTouchableRegion = new Rect();
- // When the device is rotated, it's important that mTouchableRegion is updated before
- // this listener is called. This listener is usually called shortly after onLayout.
- mInsetsListener = internalInsetsInfo -> {
- internalInsetsInfo.setTouchableInsets(
- ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- internalInsetsInfo.touchableRegion.set(mTouchableRegion);
- };
+ mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
+ mClearSurfaceDrawable = canvas -> canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- mShowScrimAndDot = false;
+ mIlluminationRequested = false;
+ // SurfaceView sets this to true by default. We must set it to false to allow
+ // onDraw to be called.
+ setWillNotDraw(false);
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
@@ -165,6 +152,20 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
mUdfpsAnimation = animation;
}
+ /**
+ * Sets a callback that can be used to enable and disable the high-brightness mode (HBM).
+ */
+ void setHbmCallback(@Nullable HbmCallback callback) {
+ mHbmCallback = callback;
+ }
+
+ /**
+ * Sets a runnable that will be run when the first illumination frame reaches the panel.
+ * The runnable is reset to null after it is executed once.
+ */
+ void setOnIlluminatedRunnable(Runnable runnable) {
+ mOnIlluminatedRunnable = runnable;
+ }
@Override
public void dozeTimeTick() {
@@ -189,50 +190,13 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
postInvalidate();
}
- // The "h" and "w" are the display's height and width relative to its current rotation.
- protected void updateSensorRect(int h, int w) {
- // mSensorProps coordinates assume portrait mode.
- mSensorRect.set(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
- mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
- mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
- mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
-
- // Transform mSensorRect if the device is in landscape mode.
- switch (mContext.getDisplay().getRotation()) {
- case Surface.ROTATION_90:
- //noinspection SuspiciousNameCombination
- mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
- h - mSensorRect.left);
- break;
- case Surface.ROTATION_270:
- //noinspection SuspiciousNameCombination
- mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
- mSensorRect.right);
- break;
- default:
- // Do nothing to stay in portrait mode.
- }
-
- if (mUdfpsAnimation != null) {
- mUdfpsAnimation.onSensorRectUpdated(new RectF(mSensorRect));
- }
- }
-
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- // Always re-compute the layout regardless of whether "changed" is true. It is usually false
- // when the device goes from landscape to seascape and vice versa, but mSensorRect and
- // its dependencies need to be recalculated to stay at the same physical location on the
- // screen.
- final int w = getLayoutParams().width;
- final int h = getLayoutParams().height;
- mScrimRect.set(0 /* left */, 0 /* top */, w, h);
- updateSensorRect(h, w);
- // Update mTouchableRegion with the rounded up values from mSensorRect. After "onLayout"
- // is finished, mTouchableRegion will be used by mInsetsListener to compute the touch
- // insets.
- mSensorRect.roundOut(mTouchableRegion);
+ mSensorRect.set(0, 0, 2 * mSensorProps.sensorRadius, 2 * mSensorProps.sensorRadius);
+ if (mUdfpsAnimation != null) {
+ mUdfpsAnimation.onSensorRectUpdated(new RectF(mSensorRect));
+ }
}
@Override
@@ -242,8 +206,6 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
// Retrieve the colors each time, since it depends on day/night mode
updateColor();
-
- getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
}
private void updateColor() {
@@ -256,56 +218,51 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.v(TAG, "onDetachedFromWindow");
- getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
}
+ /**
+ * Immediately draws the provided drawable on this SurfaceView's surface.
+ */
+ private void drawImmediately(@NonNull SimpleDrawable drawable) {
+ Canvas canvas = null;
+ try {
+ canvas = mHolder.lockCanvas();
+ drawable.draw(canvas);
+ } finally {
+ // Make sure the surface is never left in a bad state.
+ if (canvas != null) {
+ mHolder.unlockCanvasAndPost(canvas);
+ }
+ }
+ }
+
+ /**
+ * This onDraw will not execute if setWillNotDraw(true) is called.
+ */
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
-
- if (mShowScrimAndDot && mIsHbmSupported) {
- // Only draw the scrim if HBM is supported.
- canvas.drawRect(mScrimRect, mScrimPaint);
- }
-
- if (!TextUtils.isEmpty(mDebugMessage)) {
- canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
- }
-
- if (mShowScrimAndDot) {
- // draw dot (white circle)
- canvas.drawOval(mSensorRect, mSensorPaint);
- } else {
+ if (!mIlluminationRequested) {
+ if (!TextUtils.isEmpty(mDebugMessage)) {
+ canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
+ }
if (mUdfpsAnimation != null) {
final int alpha = shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255;
mUdfpsAnimation.setAlpha(alpha);
mUdfpsAnimation.draw(canvas);
}
}
-
- if (mShowScrimAndDot && mRunAfterShowingScrimAndDot != null) {
- post(mRunAfterShowingScrimAndDot);
- mRunAfterShowingScrimAndDot = null;
- }
}
RectF getSensorRect() {
return new RectF(mSensorRect);
}
- void setHbmSupported(boolean value) {
- mIsHbmSupported = value;
- }
-
void setDebugMessage(String message) {
mDebugMessage = message;
postInvalidate();
}
- void setRunAfterShowingScrimAndDot(Runnable runnable) {
- mRunAfterShowingScrimAndDot = runnable;
- }
-
boolean isValidTouch(float x, float y, float pressure) {
// The X and Y coordinates of the sensor's center.
final float cx = mSensorRect.centerX();
@@ -332,21 +289,41 @@ public class UdfpsView extends SurfaceView implements DozeReceiver,
|| mStatusBarState == FULLSCREEN_USER_SWITCHER;
}
- void setScrimAlpha(int alpha) {
- mScrimPaint.setAlpha(alpha);
+ boolean isIlluminationRequested() {
+ return mIlluminationRequested;
}
- boolean isShowScrimAndDot() {
- return mShowScrimAndDot;
- }
+ void startIllumination() {
+ mIlluminationRequested = true;
- void showScrimAndDot() {
- mShowScrimAndDot = true;
- invalidate();
+ // Disable onDraw to prevent overriding the illumination dot with the regular UI.
+ setWillNotDraw(true);
+
+ if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+ mHbmCallback.enableHbm(mHolder.getSurface());
+ }
+ drawImmediately(mIlluminationDotDrawable);
+
+ if (mOnIlluminatedRunnable != null) {
+ // No framework API can reliably tell when a frame reaches the panel. A timeout is the
+ // safest solution. The frame should be displayed within 3 refresh cycles, which on a
+ // 60 Hz panel equates to 50 milliseconds.
+ postDelayed(mOnIlluminatedRunnable, 50 /* delayMillis */);
+ mOnIlluminatedRunnable = null;
+ }
}
- void hideScrimAndDot() {
- mShowScrimAndDot = false;
+ void stopIllumination() {
+ mIlluminationRequested = false;
+
+ if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+ mHbmCallback.disableHbm(mHolder.getSurface());
+ }
+ // It may be necessary to clear the surface for the HBM changes to apply.
+ drawImmediately(mClearSurfaceDrawable);
+
+ // Enable onDraw to allow the regular UI to be drawn.
+ setWillNotDraw(false);
invalidate();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index dd145e419321..72dd4421e9ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.biometrics.SensorProperties;
-import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -47,7 +46,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -86,15 +84,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private FingerprintManager mFingerprintManager;
@Mock
- private DisplayManager mDisplayManager;
- @Mock
private WindowManager mWindowManager;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private ScrimController mScrimController;
- private FakeSettings mSystemSettings;
private FakeExecutor mFgExecutor;
// Stuff for configuring mocks
@@ -109,7 +104,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
private IUdfpsOverlayController mOverlayController;
@Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
- @Captor private ArgumentCaptor<Runnable> mRunAfterShowingScrimAndDotCaptor;
+ @Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;
@Before
public void setUp() {
@@ -122,16 +117,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */));
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
- mSystemSettings = new FakeSettings();
mFgExecutor = new FakeExecutor(new FakeSystemClock());
mUdfpsController = new UdfpsController(
mContext,
mResources,
mLayoutInflater,
mFingerprintManager,
- mDisplayManager,
mWindowManager,
- mSystemSettings,
mStatusBarStateController,
mFgExecutor,
mScrimController);
@@ -183,7 +175,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void fingerDown() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
- when(mUdfpsView.isShowScrimAndDot()).thenReturn(false);
+ when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);
// GIVEN that the overlay is showing
@@ -195,12 +187,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
event.recycle();
- // THEN the scrim and dot is shown
- verify(mUdfpsView).showScrimAndDot();
- // AND a runnable that passes the event to FingerprintManager is set on the view
- verify(mUdfpsView).setRunAfterShowingScrimAndDot(
- mRunAfterShowingScrimAndDotCaptor.capture());
- mRunAfterShowingScrimAndDotCaptor.getValue().run();
+ // THEN illumination begins
+ verify(mUdfpsView).startIllumination();
+ // AND onIlluminatedRunnable that notifies FingerprintManager is set
+ verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+ mOnIlluminatedRunnableCaptor.getValue().run();
verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
eq(0), eq(0f), eq(0f));
}
@@ -213,12 +204,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.runAllReady();
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
- // THEN the scrim and dot is shown
- verify(mUdfpsView).showScrimAndDot();
- // AND a runnable that passes the event to FingerprintManager is set on the view
- verify(mUdfpsView).setRunAfterShowingScrimAndDot(
- mRunAfterShowingScrimAndDotCaptor.capture());
- mRunAfterShowingScrimAndDotCaptor.getValue().run();
+ // THEN illumination begins
+ verify(mUdfpsView).startIllumination();
+ // AND onIlluminatedRunnable that notifies FingerprintManager is set
+ verify(mUdfpsView).setOnIlluminatedRunnable(mOnIlluminatedRunnableCaptor.capture());
+ mOnIlluminatedRunnableCaptor.getValue().run();
verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
eq(0), eq(3f) /* minor */, eq(2f) /* major */);
}
@@ -232,8 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// WHEN it is cancelled
mUdfpsController.onCancelAodInterrupt();
- // THEN the scrim and dot is hidden
- verify(mUdfpsView).hideScrimAndDot();
+ // THEN the illumination is hidden
+ verify(mUdfpsView).stopIllumination();
}
@Test
@@ -246,8 +236,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the scrim and dot is hidden
- verify(mUdfpsView).hideScrimAndDot();
+ // THEN the illumination is hidden
+ verify(mUdfpsView).stopIllumination();
}
@Test