Merge "Only trigger (un)lock icon on longpress" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
11bedc7218
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* in a 'clickable' state
|
||||
* @return whether to intercept the touch event
|
||||
* 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)) {
|
||||
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
return true;
|
||||
if (!onInterceptTouchEvent(event)) {
|
||||
cancelTouches();
|
||||
return false;
|
||||
}
|
||||
|
||||
mDownDetected = false;
|
||||
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);
|
||||
}
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user