From 5bb0f5a1f62a021d854462e2e2886ad743e49f52 Mon Sep 17 00:00:00 2001 From: Ilya Matyukhin Date: Thu, 4 Mar 2021 10:51:06 -0800 Subject: [PATCH] Refine the onTouch logic and log touch speed Bug: 180668977 Test: atest UdfpsControllerTest Test: on device Change-Id: I097eaf6f627adfc4c916672dd034d0a63a57b32b --- .../systemui/biometrics/UdfpsController.java | 106 ++++++++++++++---- .../systemui/biometrics/UdfpsView.java | 9 +- .../biometrics/UdfpsControllerTest.java | 11 +- 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 852cdcbe20fd5..3296cb867d16f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -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; @@ -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; @@ -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; @@ -136,46 +148,98 @@ 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) { - mView.onStateChanged(newState); + mView.onStateChanged(newState); } - }; + }; + + 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: - 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) { - onFingerUp(); + // 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(); } - return true; + // 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 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(); + } + } + 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 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 75a362131de1d..a52bddc1dcd57 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -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; @@ -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); @@ -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; 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 fb778e813adf7..f84aa5940e618 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -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, @@ -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());