diff options
3 files changed, 160 insertions, 115 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index f1deb3026865..60cc57df20e1 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -35,13 +35,14 @@ import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.media.AudioAttributes; import android.os.Process; +import android.os.VibrationEffect; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.Log; import android.util.MathUtils; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -83,6 +84,7 @@ import javax.inject.Inject; */ @StatusBarComponent.StatusBarScope public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { + private static final String TAG = "LockIconViewController"; private static final float sDefaultDensity = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); @@ -91,6 +93,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); + private static final long LONG_PRESS_TIMEOUT = 150L; // milliseconds @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewController mKeyguardViewController; @@ -112,6 +115,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Nullable private final Vibrator mVibrator; @Nullable private final AuthRippleController mAuthRippleController; + // Tracks the velocity of a touch to help filter out the touches that move too fast. + private VelocityTracker mVelocityTracker; + // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. + private int mActivePointerId = -1; + private VibrationEffect mTick; + private boolean mIsDozing; private boolean mIsBouncerShowing; private boolean mRunningFPS; @@ -122,6 +131,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; private Runnable mOnGestureDetectedRunnable; + private Runnable mLongPressCancelRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -181,7 +191,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setImageDrawable(mIcon); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mLockedLabel = resources.getString(R.string.accessibility_lock_icon); - dumpManager.registerDumpable("LockIconViewController", this); + dumpManager.registerDumpable(TAG, this); } @Override @@ -320,7 +330,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme getResources().getString(R.string.accessibility_enter_hint)); public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(v, info); - if (isClickable()) { + if (isActionable()) { if (mShowLockIcon) { info.addAction(mAccessibilityAuthenticateHint); } else if (mShowUnlockIcon) { @@ -475,7 +485,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onKeyguardVisibilityChanged(boolean showing) { // reset mIsBouncerShowing state in case it was preemptively set - // onAffordanceClick + // onLongPress mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); updateVisibility(); } @@ -569,104 +579,79 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } }; - private final GestureDetector mGestureDetector = - new GestureDetector(new SimpleOnGestureListener() { - public boolean onDown(MotionEvent e) { - if (!isClickable()) { - mDownDetected = false; - return false; - } - - // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or - // MotionEvent.ACTION_UP (see #onTouchEvent) - if (mVibrator != null && !mDownDetected) { - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onDown", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - - mDownDetected = true; - return true; - } - - public void onLongPress(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return; - } - - if (onAffordanceClick() && mVibrator != null) { - // only vibrate if the click went through and wasn't intercepted by falsing - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lockIcon-onLongPress", - VIBRATION_SONIFICATION_ATTRIBUTES); - } - } + /** + * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true. + * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon + * area for {@link #LONG_PRESS_TIMEOUT} ms. + * + * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}. + */ + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { + if (!onInterceptTouchEvent(event)) { + cancelTouches(); + return false; + } - public boolean onSingleTapUp(MotionEvent e) { - if (!wasClickableOnDownEvent()) { - return false; + mOnGestureDetectedRunnable = onGestureDetectedRunnable; + switch(event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_HOVER_ENTER: + if (mVibrator != null && !mDownDetected) { + if (mTick == null) { + mTick = UdfpsController.lowTick(getContext(), true, + LONG_PRESS_TIMEOUT); } - onAffordanceClick(); - return true; + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + mTick, + "lock-icon-tick", + VIBRATION_SONIFICATION_ATTRIBUTES); } - public boolean onFling(MotionEvent e1, MotionEvent e2, - float velocityX, float velocityY) { - if (!wasClickableOnDownEvent()) { - return false; - } - onAffordanceClick(); - return true; + // 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); + if (mVelocityTracker == null) { + // 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. + 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(); } - - private boolean wasClickableOnDownEvent() { - return mDownDetected; + mVelocityTracker.addMovement(event); + + mDownDetected = true; + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: + mVelocityTracker.addMovement(event); + // Compute pointer velocity in pixels per second. + mVelocityTracker.computeCurrentVelocity(1000); + float velocity = UdfpsController.computePointerSpeed(mVelocityTracker, + mActivePointerId); + if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS + && UdfpsController.exceedsVelocityThreshold(velocity)) { + Log.v(TAG, "lock icon long-press rescheduled due to " + + "high pointer velocity=" + velocity); + mLongPressCancelRunnable.run(); + mLongPressCancelRunnable = mExecutor.executeDelayed( + this::onLongPress, LONG_PRESS_TIMEOUT); } - - /** - * Whether we tried to launch the affordance. - * - * If falsing intercepts the click, returns false. - */ - private boolean onAffordanceClick() { - if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return false; - } - - // pre-emptively set to true to hide view - mIsBouncerShowing = true; - if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { - mAuthRippleController.showRipple(FINGERPRINT); - } - updateVisibility(); - if (mOnGestureDetectedRunnable != null) { - mOnGestureDetectedRunnable.run(); - } - mKeyguardViewController.showBouncer(/* scrim */ true); - return true; - } - }); - - /** - * Send touch events to this view and handles it if the touch is within this view and we are - * in a 'clickable' state - * @return whether to intercept the touch event - */ - public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { - if (onInterceptTouchEvent(event)) { - mOnGestureDetectedRunnable = onGestureDetectedRunnable; - mGestureDetector.onTouchEvent(event); - return true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_HOVER_EXIT: + cancelTouches(); + break; } - mDownDetected = false; - return false; + return true; } /** @@ -674,7 +659,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * bounds. */ public boolean onInterceptTouchEvent(MotionEvent event) { - if (!inLockIconArea(event) || !isClickable()) { + if (!inLockIconArea(event) || !isActionable()) { return false; } @@ -685,13 +670,48 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } + private void onLongPress() { + cancelTouches(); + if (mFalsingManager.isFalseTouch(LOCK_ICON)) { + Log.v(TAG, "lock icon long-press rejected by the falsing manager."); + return; + } + + // pre-emptively set to true to hide view + mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } + updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } + mKeyguardViewController.showBouncer(/* scrim */ true); + } + + + private void cancelTouches() { + mDownDetected = false; + if (mLongPressCancelRunnable != null) { + mLongPressCancelRunnable.run(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + if (mVibrator != null) { + mVibrator.cancel(); + } + } + + private boolean inLockIconArea(MotionEvent event) { return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) && (mView.getVisibility() == View.VISIBLE || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE)); } - private boolean isClickable() { + private boolean isActionable() { return mUdfpsSupported || mShowUnlockIcon; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 09f2af4a86da..9808045d4ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -103,6 +103,7 @@ import kotlin.Unit; public class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; + private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds // Minimum required delay between consecutive touch logs in milliseconds. private static final long MIN_TOUCH_LOG_INTERVAL = 50; @@ -164,8 +165,7 @@ public class UdfpsController implements DozeReceiver { private boolean mAttemptedToDismissKeyguard; private Set<Callback> mCallbacks = new HashSet<>(); - // by default, use low tick - private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; + private static final int DEFAULT_TICK = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; private final VibrationEffect mTick; @VisibleForTesting @@ -327,12 +327,23 @@ public class UdfpsController implements DozeReceiver { } } - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + /** + * Calculate the pointer speed given a velocity tracker and the pointer id. + * This assumes that the velocity tracker has already been passed all relevant motion events. + */ + public 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)); } + /** + * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. + */ + public static boolean exceedsVelocityThreshold(float velocity) { + return velocity > 750f; + } + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -467,7 +478,7 @@ public class UdfpsController implements DozeReceiver { final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float minor = event.getTouchMinor(idx); final float major = event.getTouchMajor(idx); - final boolean exceedsVelocityThreshold = v > 750f; + final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); final String touchInfo = String.format( "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", minor, major, v, exceedsVelocityThreshold); @@ -575,7 +586,7 @@ public class UdfpsController implements DozeReceiver { mConfigurationController = configurationController; mSystemClock = systemClock; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mTick = lowTick(); + mTick = lowTick(context, false /* useShortRampup */, DEFAULT_VIBRATION_DURATION); mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -610,32 +621,43 @@ public class UdfpsController implements DozeReceiver { udfpsHapticsSimulator.setUdfpsController(this); } - private VibrationEffect lowTick() { - boolean useLowTickDefault = mContext.getResources() + /** + * Returns the continuous low tick effect that starts playing on the udfps finger-down event. + */ + public static VibrationEffect lowTick( + Context context, + boolean useShortRampUp, + long duration + ) { + boolean useLowTickDefault = context.getResources() .getBoolean(R.bool.config_udfpsUseLowTick); + int primitiveTick = DEFAULT_TICK; if (Settings.Global.getFloat( - mContext.getContentResolver(), + context.getContentResolver(), "tick-low", useLowTickDefault ? 1 : 0) == 0) { - mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; + primitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; } float tickIntensity = Settings.Global.getFloat( - mContext.getContentResolver(), + context.getContentResolver(), "tick-intensity", - mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); + context.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); int tickDelay = Settings.Global.getInt( - mContext.getContentResolver(), + context.getContentResolver(), "tick-delay", - mContext.getResources().getInteger(R.integer.config_udfpsTickDelay)); + context.getResources().getInteger(R.integer.config_udfpsTickDelay)); VibrationEffect.Composition composition = VibrationEffect.startComposition(); - composition.addPrimitive(mPrimitiveTick, tickIntensity, 0); - int primitives = 1000 / tickDelay; + composition.addPrimitive(primitiveTick, tickIntensity, 0); + int primitives = (int) (duration / tickDelay); float[] rampUp = new float[]{.48f, .58f, .69f, .83f}; + if (useShortRampUp) { + rampUp = new float[]{.5f, .7f}; + } for (int i = 0; i < rampUp.length; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay); + composition.addPrimitive(primitiveTick, tickIntensity * rampUp[i], tickDelay); } for (int i = rampUp.length; i < primitives; i++) { - composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay); + composition.addPrimitive(primitiveTick, tickIntensity, tickDelay); } return composition.compose(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java index 71edbc06840e..d17eadd163fc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java @@ -19,6 +19,7 @@ package com.android.systemui.classifier; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE; import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import android.provider.DeviceConfig; @@ -71,7 +72,9 @@ class DiagonalClassifier extends FalsingClassifier { return Result.passed(0); } - if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) { + if (interactionType == LEFT_AFFORDANCE + || interactionType == RIGHT_AFFORDANCE + || interactionType == LOCK_ICON) { return Result.passed(0); } |