Merge "Only trigger (un)lock icon on longpress" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-12-06 15:26:48 +00:00
committed by Android (Google) Code Review
3 changed files with 163 additions and 118 deletions

View File

@@ -35,13 +35,14 @@ import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.os.Process; import android.os.Process;
import android.os.VibrationEffect;
import android.os.Vibrator; import android.os.Vibrator;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils; import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
@@ -83,6 +84,7 @@ import javax.inject.Inject;
*/ */
@StatusBarComponent.StatusBarScope @StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable { public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
private static final String TAG = "LockIconViewController";
private static final float sDefaultDensity = private static final float sDefaultDensity =
(float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
@@ -91,6 +93,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build(); .build();
private static final long LONG_PRESS_TIMEOUT = 150L; // milliseconds
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardViewController mKeyguardViewController; @NonNull private final KeyguardViewController mKeyguardViewController;
@@ -112,6 +115,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Nullable private final Vibrator mVibrator; @Nullable private final Vibrator mVibrator;
@Nullable private final AuthRippleController mAuthRippleController; @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 mIsDozing;
private boolean mIsBouncerShowing; private boolean mIsBouncerShowing;
private boolean mRunningFPS; private boolean mRunningFPS;
@@ -122,6 +131,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mUserUnlockedWithBiometric; private boolean mUserUnlockedWithBiometric;
private Runnable mCancelDelayedUpdateVisibilityRunnable; private Runnable mCancelDelayedUpdateVisibilityRunnable;
private Runnable mOnGestureDetectedRunnable; private Runnable mOnGestureDetectedRunnable;
private Runnable mLongPressCancelRunnable;
private boolean mUdfpsSupported; private boolean mUdfpsSupported;
private float mHeightPixels; private float mHeightPixels;
@@ -181,7 +191,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mView.setImageDrawable(mIcon); mView.setImageDrawable(mIcon);
mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
mLockedLabel = resources.getString(R.string.accessibility_lock_icon); mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this); dumpManager.registerDumpable(TAG, this);
} }
@Override @Override
@@ -320,7 +330,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
getResources().getString(R.string.accessibility_enter_hint)); getResources().getString(R.string.accessibility_enter_hint));
public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(v, info); super.onInitializeAccessibilityNodeInfo(v, info);
if (isClickable()) { if (isActionable()) {
if (mShowLockIcon) { if (mShowLockIcon) {
info.addAction(mAccessibilityAuthenticateHint); info.addAction(mAccessibilityAuthenticateHint);
} else if (mShowUnlockIcon) { } else if (mShowUnlockIcon) {
@@ -475,7 +485,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override @Override
public void onKeyguardVisibilityChanged(boolean showing) { public void onKeyguardVisibilityChanged(boolean showing) {
// reset mIsBouncerShowing state in case it was preemptively set // reset mIsBouncerShowing state in case it was preemptively set
// onAffordanceClick // onLongPress
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
updateVisibility(); 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);
}
}
public boolean onSingleTapUp(MotionEvent e) {
if (!wasClickableOnDownEvent()) {
return false;
}
onAffordanceClick();
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
if (!wasClickableOnDownEvent()) {
return false;
}
onAffordanceClick();
return true;
}
private boolean wasClickableOnDownEvent() {
return mDownDetected;
}
/**
* 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 * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true.
* in a 'clickable' state * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon
* @return whether to intercept the touch event * 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) { public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (onInterceptTouchEvent(event)) { if (!onInterceptTouchEvent(event)) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable; cancelTouches();
mGestureDetector.onTouchEvent(event); return false;
return true;
} }
mDownDetected = false; mOnGestureDetectedRunnable = onGestureDetectedRunnable;
return false; 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);
}
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
mTick,
"lock-icon-tick",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
// 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();
}
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);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_HOVER_EXIT:
cancelTouches();
break;
}
return true;
} }
/** /**
@@ -674,7 +659,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
* bounds. * bounds.
*/ */
public boolean onInterceptTouchEvent(MotionEvent event) { public boolean onInterceptTouchEvent(MotionEvent event) {
if (!inLockIconArea(event) || !isClickable()) { if (!inLockIconArea(event) || !isActionable()) {
return false; return false;
} }
@@ -685,13 +670,48 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
return mDownDetected; 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) { private boolean inLockIconArea(MotionEvent event) {
return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE && (mView.getVisibility() == View.VISIBLE
|| (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE)); || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE));
} }
private boolean isClickable() { private boolean isActionable() {
return mUdfpsSupported || mShowUnlockIcon; return mUdfpsSupported || mShowUnlockIcon;
} }

View File

@@ -103,6 +103,7 @@ import kotlin.Unit;
public class UdfpsController implements DozeReceiver { public class UdfpsController implements DozeReceiver {
private static final String TAG = "UdfpsController"; private static final String TAG = "UdfpsController";
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; 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. // Minimum required delay between consecutive touch logs in milliseconds.
private static final long MIN_TOUCH_LOG_INTERVAL = 50; private static final long MIN_TOUCH_LOG_INTERVAL = 50;
@@ -164,8 +165,7 @@ public class UdfpsController implements DozeReceiver {
private boolean mAttemptedToDismissKeyguard; private boolean mAttemptedToDismissKeyguard;
private Set<Callback> mCallbacks = new HashSet<>(); private Set<Callback> mCallbacks = new HashSet<>();
// by default, use low tick private static final int DEFAULT_TICK = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
private final VibrationEffect mTick; private final VibrationEffect mTick;
@VisibleForTesting @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 vx = tracker.getXVelocity(pointerId);
final float vy = tracker.getYVelocity(pointerId); final float vy = tracker.getYVelocity(pointerId);
return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); 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() { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -467,7 +478,7 @@ public class UdfpsController implements DozeReceiver {
final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
final float minor = event.getTouchMinor(idx); final float minor = event.getTouchMinor(idx);
final float major = event.getTouchMajor(idx); final float major = event.getTouchMajor(idx);
final boolean exceedsVelocityThreshold = v > 750f; final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v);
final String touchInfo = String.format( final String touchInfo = String.format(
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold); minor, major, v, exceedsVelocityThreshold);
@@ -575,7 +586,7 @@ public class UdfpsController implements DozeReceiver {
mConfigurationController = configurationController; mConfigurationController = configurationController;
mSystemClock = systemClock; mSystemClock = systemClock;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mTick = lowTick(); mTick = lowTick(context, false /* useShortRampup */, DEFAULT_VIBRATION_DURATION);
mSensorProps = findFirstUdfps(); mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists // At least one UDFPS sensor exists
@@ -610,32 +621,43 @@ public class UdfpsController implements DozeReceiver {
udfpsHapticsSimulator.setUdfpsController(this); 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); .getBoolean(R.bool.config_udfpsUseLowTick);
int primitiveTick = DEFAULT_TICK;
if (Settings.Global.getFloat( if (Settings.Global.getFloat(
mContext.getContentResolver(), context.getContentResolver(),
"tick-low", useLowTickDefault ? 1 : 0) == 0) { "tick-low", useLowTickDefault ? 1 : 0) == 0) {
mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; primitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK;
} }
float tickIntensity = Settings.Global.getFloat( float tickIntensity = Settings.Global.getFloat(
mContext.getContentResolver(), context.getContentResolver(),
"tick-intensity", "tick-intensity",
mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); context.getResources().getFloat(R.dimen.config_udfpsTickIntensity));
int tickDelay = Settings.Global.getInt( int tickDelay = Settings.Global.getInt(
mContext.getContentResolver(), context.getContentResolver(),
"tick-delay", "tick-delay",
mContext.getResources().getInteger(R.integer.config_udfpsTickDelay)); context.getResources().getInteger(R.integer.config_udfpsTickDelay));
VibrationEffect.Composition composition = VibrationEffect.startComposition(); VibrationEffect.Composition composition = VibrationEffect.startComposition();
composition.addPrimitive(mPrimitiveTick, tickIntensity, 0); composition.addPrimitive(primitiveTick, tickIntensity, 0);
int primitives = 1000 / tickDelay; int primitives = (int) (duration / tickDelay);
float[] rampUp = new float[]{.48f, .58f, .69f, .83f}; float[] rampUp = new float[]{.48f, .58f, .69f, .83f};
if (useShortRampUp) {
rampUp = new float[]{.5f, .7f};
}
for (int i = 0; i < rampUp.length; i++) { 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++) { for (int i = rampUp.length; i < primitives; i++) {
composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay); composition.addPrimitive(primitiveTick, tickIntensity, tickDelay);
} }
return composition.compose(); return composition.compose();
} }

View File

@@ -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_HORIZONTAL_ANGLE_RANGE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_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.LEFT_AFFORDANCE;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE; import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
@@ -71,7 +72,9 @@ class DiagonalClassifier extends FalsingClassifier {
return Result.passed(0); 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); return Result.passed(0);
} }