diff options
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 |