Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 11bedc72 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 53210252 1dd8b3f4
Loading
Loading
Loading
Loading
+117 −97
Original line number Diff line number Diff line
@@ -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,74 +579,102 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
        }
    };

    private final GestureDetector mGestureDetector =
            new GestureDetector(new SimpleOnGestureListener() {
                public boolean onDown(MotionEvent e) {
                    if (!isClickable()) {
                        mDownDetected = false;
    /**
     * 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;
        }

                    // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
                    // MotionEvent.ACTION_UP (see #onTouchEvent)
        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(),
                                UdfpsController.EFFECT_CLICK,
                                "lockIcon-onDown",
                            mTick,
                            "lock-icon-tick",
                            VIBRATION_SONIFICATION_ATTRIBUTES);
                }

                    mDownDetected = true;
                    return true;
                }

                public void onLongPress(MotionEvent e) {
                    if (!wasClickableOnDownEvent()) {
                        return;
                // 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);

                    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);
                    }
                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;
        }

                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()) {
    /**
     * Intercepts the touch if the onDown event and current event are within this lock icon view's
     * bounds.
     */
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!inLockIconArea(event) || !isActionable()) {
            return false;
        }
                    onAffordanceClick();

        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return true;
        }

                private boolean wasClickableOnDownEvent() {
        return mDownDetected;
    }

                /**
                 * Whether we tried to launch the affordance.
                 *
                 * If falsing intercepts the click, returns false.
                 */
                private boolean onAffordanceClick() {
    private void onLongPress() {
        cancelTouches();
        if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
                        return false;
            Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
            return;
        }

        // pre-emptively set to true to hide view
@@ -649,49 +687,31 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
            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;
        }

    private void cancelTouches() {
        mDownDetected = false;
        return false;
        if (mLongPressCancelRunnable != null) {
            mLongPressCancelRunnable.run();
        }

    /**
     * Intercepts the touch if the onDown event and current event are within this lock icon view's
     * bounds.
     */
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!inLockIconArea(event) || !isClickable()) {
            return false;
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }

        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return true;
        if (mVibrator != null) {
            mVibrator.cancel();
        }

        return mDownDetected;
    }


    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;
    }

+39 −17
Original line number Diff line number Diff line
@@ -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();
    }
+4 −1
Original line number Diff line number Diff line
@@ -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);
        }