summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java214
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java5
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);
}