Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +84 −20 Original line number Diff line number Diff line Loading @@ -28,11 +28,13 @@ import android.graphics.RectF; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.SystemClock; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.VelocityTracker; import android.view.WindowManager; import androidx.annotation.NonNull; Loading Loading @@ -66,6 +68,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; // Minimum required delay between consecutive touch logs in milliseconds. private static final long MIN_TOUCH_LOG_INTERVAL = 50; private final Context mContext; private final FingerprintManager mFingerprintManager; @NonNull private final LayoutInflater mInflater; Loading @@ -78,6 +83,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; private final WindowManager.LayoutParams mCoreLayoutParams; // Tracks the velocity of a touch to help filter out the touches that move too fast. @Nullable private VelocityTracker mVelocityTracker; // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. private int mActivePointerId; // The timestamp of the most recent touch log. private long mTouchLogTime; @Nullable private UdfpsView mView; // Indicates whether the overlay has been requested. private boolean mIsOverlayRequested; Loading Loading @@ -136,12 +148,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override public void onStateChanged(int newState) { Loading @@ -149,33 +159,87 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } }; private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { final float vx = tracker.getXVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId); return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); } @SuppressLint("ClickableViewAccessibility") private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> { UdfpsView view = (UdfpsView) v; final boolean isFingerDown = view.isIlluminationRequested(); switch (event.getAction()) { private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> { UdfpsView udfpsView = (UdfpsView) view; final boolean isFingerDown = udfpsView.isIlluminationRequested(); boolean handled = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // To simplify the lifecycle of the velocity tracker, make sure it's never null // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } else { // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new // ACTION_DOWN, in that case we should just reuse the old instance. mVelocityTracker.clear(); } // TODO: move isWithinSensorArea to UdfpsController. if (udfpsView.isWithinSensorArea(event.getX(), event.getY())) { // The pointer that causes ACTION_DOWN is always at index 0. // We need to persist its ID to track it during ACTION_MOVE that could include // data for many other pointers because of multi-touch support. mActivePointerId = event.getPointerId(0); mVelocityTracker.addMovement(event); handled = true; } break; case MotionEvent.ACTION_MOVE: final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(), event.getPressure()); if (!isFingerDown && isValidTouch) { onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(), event.getTouchMajor()); } else if (isFingerDown && !isValidTouch) { final int idx = event.findPointerIndex(mActivePointerId); if (idx == event.getActionIndex()) { final float x = event.getX(idx); final float y = event.getY(idx); if (udfpsView.isWithinSensorArea(x, y)) { mVelocityTracker.addMovement(event); // Compute pointer velocity in pixels per second. mVelocityTracker.computeCurrentVelocity(1000); // Compute pointer speed from X and Y velocities. final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float minor = event.getTouchMinor(idx); final float major = event.getTouchMajor(idx); final String touchInfo = String.format("minor: %.1f, major: %.1f, v: %.1f", minor, major, v); final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; if (!isFingerDown) { onFingerDown((int) x, (int) y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); } } else if (isFingerDown) { Log.v(TAG, "onTouch | finger outside"); onFingerUp(); } return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } if (isFingerDown) { Log.v(TAG, "onTouch | finger up"); onFingerUp(); } return true; break; default: return false; // Do nothing. } return handled; }; @Inject Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +8 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; Loading Loading @@ -92,6 +93,12 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mIlluminationRequested = false; } // Don't propagate any touch events to the child views. @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } @Override protected void onFinishInflate() { mHbmSurfaceView = findViewById(R.id.hbm_view); Loading Loading @@ -179,7 +186,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin postInvalidate(); } boolean isValidTouch(float x, float y, float pressure) { boolean isWithinSensorArea(float x, float y) { // The X and Y coordinates of the sensor's center. final PointF translation = mAnimationView.getTouchTranslation(); final float cx = mSensorRect.centerX() + translation.x; Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +7 −4 Original line number Diff line number Diff line Loading @@ -179,7 +179,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void fingerDown() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isIlluminationRequested()).thenReturn(false); when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, Loading @@ -187,9 +187,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); event.recycle(); MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); downEvent.recycle(); MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); moveEvent.recycle(); // THEN illumination begins // AND onIlluminatedRunnable that notifies FingerprintManager is set verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); Loading Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +84 −20 Original line number Diff line number Diff line Loading @@ -28,11 +28,13 @@ import android.graphics.RectF; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.SystemClock; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.VelocityTracker; import android.view.WindowManager; import androidx.annotation.NonNull; Loading Loading @@ -66,6 +68,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; // Minimum required delay between consecutive touch logs in milliseconds. private static final long MIN_TOUCH_LOG_INTERVAL = 50; private final Context mContext; private final FingerprintManager mFingerprintManager; @NonNull private final LayoutInflater mInflater; Loading @@ -78,6 +83,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; private final WindowManager.LayoutParams mCoreLayoutParams; // Tracks the velocity of a touch to help filter out the touches that move too fast. @Nullable private VelocityTracker mVelocityTracker; // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. private int mActivePointerId; // The timestamp of the most recent touch log. private long mTouchLogTime; @Nullable private UdfpsView mView; // Indicates whether the overlay has been requested. private boolean mIsOverlayRequested; Loading Loading @@ -136,12 +148,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } } @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = @VisibleForTesting final StatusBar.ExpansionChangedListener mStatusBarExpansionListener = (expansion, expanded) -> mView.onExpansionChanged(expansion, expanded); @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = @VisibleForTesting final StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override public void onStateChanged(int newState) { Loading @@ -149,33 +159,87 @@ public class UdfpsController implements DozeReceiver, HbmCallback { } }; private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { final float vx = tracker.getXVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId); return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); } @SuppressLint("ClickableViewAccessibility") private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> { UdfpsView view = (UdfpsView) v; final boolean isFingerDown = view.isIlluminationRequested(); switch (event.getAction()) { private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> { UdfpsView udfpsView = (UdfpsView) view; final boolean isFingerDown = udfpsView.isIlluminationRequested(); boolean handled = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // To simplify the lifecycle of the velocity tracker, make sure it's never null // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } else { // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new // ACTION_DOWN, in that case we should just reuse the old instance. mVelocityTracker.clear(); } // TODO: move isWithinSensorArea to UdfpsController. if (udfpsView.isWithinSensorArea(event.getX(), event.getY())) { // The pointer that causes ACTION_DOWN is always at index 0. // We need to persist its ID to track it during ACTION_MOVE that could include // data for many other pointers because of multi-touch support. mActivePointerId = event.getPointerId(0); mVelocityTracker.addMovement(event); handled = true; } break; case MotionEvent.ACTION_MOVE: final boolean isValidTouch = view.isValidTouch(event.getX(), event.getY(), event.getPressure()); if (!isFingerDown && isValidTouch) { onFingerDown((int) event.getX(), (int) event.getY(), event.getTouchMinor(), event.getTouchMajor()); } else if (isFingerDown && !isValidTouch) { final int idx = event.findPointerIndex(mActivePointerId); if (idx == event.getActionIndex()) { final float x = event.getX(idx); final float y = event.getY(idx); if (udfpsView.isWithinSensorArea(x, y)) { mVelocityTracker.addMovement(event); // Compute pointer velocity in pixels per second. mVelocityTracker.computeCurrentVelocity(1000); // Compute pointer speed from X and Y velocities. final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float minor = event.getTouchMinor(idx); final float major = event.getTouchMajor(idx); final String touchInfo = String.format("minor: %.1f, major: %.1f, v: %.1f", minor, major, v); final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime; if (!isFingerDown) { onFingerDown((int) x, (int) y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); mTouchLogTime = SystemClock.elapsedRealtime(); } } else if (isFingerDown) { Log.v(TAG, "onTouch | finger outside"); onFingerUp(); } return true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } if (isFingerDown) { Log.v(TAG, "onTouch | finger up"); onFingerUp(); } return true; break; default: return false; // Do nothing. } return handled; }; @Inject Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +8 −1 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; Loading Loading @@ -92,6 +93,12 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin mIlluminationRequested = false; } // Don't propagate any touch events to the child views. @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } @Override protected void onFinishInflate() { mHbmSurfaceView = findViewById(R.id.hbm_view); Loading Loading @@ -179,7 +186,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin postInvalidate(); } boolean isValidTouch(float x, float y, float pressure) { boolean isWithinSensorArea(float x, float y) { // The X and Y coordinates of the sensor's center. final PointF translation = mAnimationView.getTouchTranslation(); final float cx = mSensorRect.centerX() + translation.x; Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +7 −4 Original line number Diff line number Diff line Loading @@ -179,7 +179,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void fingerDown() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isIlluminationRequested()).thenReturn(false); when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, Loading @@ -187,9 +187,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); event.recycle(); MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); downEvent.recycle(); MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); moveEvent.recycle(); // THEN illumination begins // AND onIlluminatedRunnable that notifies FingerprintManager is set verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture()); Loading