Remove HBM logic from the UDFPS overlay
This change removes the high-brightness mode logic, and provides hooks for the subclasses to implement this logic instead. This also changes the layout of the overlay from being a fullscreen window, to a window that's the exact size of the sensor. Bug: 177563105 Test: atest UdfpsControllerTest Change-Id: Ie5dd4ea862e07c862feb9ae09ed9c602b32849e1
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
mView.stopIllumination();
|
||||
}
|
||||
|
||||
private static float obtainDefaultBrightness(Context context) {
|
||||
return MathUtils.constrain(context.getDisplay().getBrightnessDefault(),
|
||||
PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
|
||||
@Override
|
||||
public void enableHbm(Surface surface) {
|
||||
// Do nothing. This method can be implemented for devices that require the high-brightness
|
||||
// mode for fingerprint illumination.
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 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 showScrimAndDot() {
|
||||
mShowScrimAndDot = true;
|
||||
invalidate();
|
||||
}
|
||||
void stopIllumination() {
|
||||
mIlluminationRequested = false;
|
||||
|
||||
void hideScrimAndDot() {
|
||||
mShowScrimAndDot = 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user