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

Unverified Commit 176787ee authored by Nico's avatar Nico Committed by Michael Bestas
Browse files

Implement edge long swipe gesture [1/3]

The gesture will activate if user executes edge swipe for a long
horizontal motion. The triggered action is configurable in Button
Settings.

Reference:
https://gerrit.dirtyunicorns.com/q/topic:%22back-longswipe-actions%22

Change-Id: Ie1bbe6645b6a00d346af60d6bb5e4d584997d6e9
parent cd755860
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -45,6 +45,9 @@ public interface NavigationEdgeBackPlugin extends Plugin {
    /** Sets the callback that should be invoked when a Back gesture is detected. */
    void setBackCallback(BackCallback callback);

    /** Specifies if the long swipe should be enabled or not. */
    void setLongSwipeEnabled(boolean enabled);

    /** Sets the base LayoutParams for the UI. */
    void setLayoutParams(WindowManager.LayoutParams layoutParams);

@@ -57,7 +60,7 @@ public interface NavigationEdgeBackPlugin extends Plugin {
    /** Callback to let the system react to the detected back gestures. */
    interface BackCallback {
        /** Indicates that a Back gesture was recognized and the system should go back. */
        void triggerBack();
        void triggerBack(boolean isLongPress);

        /** Indicates that the gesture was cancelled and the system should not go back. */
        void cancelBack();
+41 −6
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_

import static com.android.systemui.classifier.Classifier.BACK_GESTURE;

import static org.lineageos.internal.util.DeviceKeysConstants.Action;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -39,6 +41,7 @@ import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Choreographer;
import android.view.Display;
@@ -57,6 +60,7 @@ import android.view.WindowMetrics;

import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -80,6 +84,9 @@ import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.systemui.tuner.TunerService;

import lineageos.providers.LineageSettings;

import java.io.PrintWriter;
import java.util.ArrayDeque;
@@ -94,12 +101,16 @@ import javax.inject.Inject;
 * Utility class to handle edge swipes for back gesture
 */
public class EdgeBackGestureHandler extends CurrentUserTracker
        implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
        implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto>,
        TunerService.Tunable {

    private static final String TAG = "EdgeBackGestureHandler";
    private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
            "gestures.back_timeout", 250);

    private static final String KEY_EDGE_LONG_SWIPE_ACTION =
            "lineagesystem:" + LineageSettings.System.KEY_EDGE_LONG_SWIPE_ACTION;

    private static final int MAX_NUM_LOGGED_PREDICTIONS = 10;
    private static final int MAX_NUM_LOGGED_GESTURES = 10;

@@ -228,6 +239,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
    private boolean mIsEnabled;
    private boolean mIsNavBarShownTransiently;
    private boolean mIsBackGestureAllowed;
    private boolean mIsLongSwipeEnabled;
    private boolean mGestureBlockingActivityRunning;
    private boolean mIsInPipMode;

@@ -238,6 +250,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
    private int mLeftInset;
    private int mRightInset;
    private int mSysUiFlags;
    private float mLongSwipeWidth;

    // For Tf-Lite model.
    private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
@@ -259,12 +272,14 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
    private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
            new NavigationEdgeBackPlugin.BackCallback() {
                @Override
                public void triggerBack() {
                public void triggerBack(boolean isLongPress) {
                    // Notify FalsingManager that an intentional gesture has occurred.
                    // TODO(b/186519446): use a different method than isFalseTouch
                    mFalsingManager.isFalseTouch(BACK_GESTURE);
                    boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                    boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
                    boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK,
                            isLongPress ? KeyEvent.FLAG_LONG_PRESS : 0);
                    boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK,
                            isLongPress ? KeyEvent.FLAG_LONG_PRESS : 0);
                    if (DEBUG_MISSING_GESTURE) {
                        Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down=" + sendDown
                                + ", up=" + sendUp);
@@ -370,6 +385,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
        if (mMLEnableWidth > mEdgeWidthRight) mMLEnableWidth = mEdgeWidthRight;
        if (mMLEnableWidth > mEdgeWidthLeft) mMLEnableWidth = mEdgeWidthLeft;

        final TunerService tunerService = Dependency.get(TunerService.class);
        tunerService.addTunable(this, KEY_EDGE_LONG_SWIPE_ACTION);

        // Reduce the default touch slop to ensure that we can intercept the gesture
        // before the app starts to react to it.
        // TODO(b/130352502) Tune this value and extract into a constant
@@ -501,6 +519,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
            setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
            mPluginManager.addPluginListener(
                    this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
            updateLongSwipeWidth();
        }
        // Update the ML model resources.
        updateMLModelState();
@@ -511,6 +530,12 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
        setEdgeBackPlugin(plugin);
    }

    private void updateLongSwipeWidth() {
        if (mIsEnabled && mEdgeBackPlugin != null) {
            mEdgeBackPlugin.setLongSwipeEnabled(mIsLongSwipeEnabled);
        }
    }

    @Override
    public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
        setEdgeBackPlugin(new NavigationBarEdgePanel(mContext));
@@ -867,13 +892,23 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
        if (mEdgeBackPlugin != null) {
            mEdgeBackPlugin.setDisplaySize(mDisplaySize);
        }
        updateLongSwipeWidth();
    }

    @Override
    public void onTuningChanged(String key, String newValue) {
        if (KEY_EDGE_LONG_SWIPE_ACTION.equals(key)) {
            mIsLongSwipeEnabled = Action.fromIntSafe(TunerService.parseInteger(
                    newValue, 0)) != Action.NOTHING;
            updateLongSwipeWidth();
        }
    }

    private boolean sendEvent(int action, int code) {
    private boolean sendEvent(int action, int code, int flags) {
        long when = SystemClock.uptimeMillis();
        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                InputDevice.SOURCE_KEYBOARD);

        ev.setDisplayId(mContext.getDisplay().getDisplayId());
+43 −4
Original line number Diff line number Diff line
@@ -210,7 +210,9 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
    private boolean mDragSlopPassed;
    private boolean mArrowsPointLeft;
    private float mMaxTranslation;
    private float mLongSwipeThreshold;
    private boolean mTriggerBack;
    private boolean mTriggerLongSwipe;
    private float mPreviousTouchTranslation;
    private float mTotalTouchDelta;
    private float mVerticalTranslation;
@@ -226,6 +228,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
    private final Handler mHandler = new Handler();
    private final Runnable mFailsafeRunnable = this::onFailsafe;

    private boolean mIsLongSwipeEnabled;

    private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
            = new DynamicAnimation.OnAnimationEndListener() {
        @Override
@@ -423,6 +427,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
        mWindowManager.addView(this, mLayoutParams);
    }

    @Override
    public void setLongSwipeEnabled(boolean enabled) {
        mLongSwipeThreshold = enabled ? MathUtils.min(
                mDisplaySize.x * 0.5f, mLayoutParams.width * 2.5f) : 0.0f;
        mIsLongSwipeEnabled = mLongSwipeThreshold > 0;
        setTriggerLongSwipe(mIsLongSwipeEnabled && mTriggerLongSwipe, false /* animated */);
    }

    /**
     * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
     */
@@ -482,8 +494,10 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
                    Log.d(DEBUG_MISSING_GESTURE_TAG,
                            "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack);
                }
                if (mTriggerBack) {
                    triggerBack();
                if (mTriggerLongSwipe) {
                    triggerBack(true);
                } else if (mTriggerBack) {
                    triggerBack(false);
                } else {
                    cancelBack();
                }
@@ -522,6 +536,11 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
        float x = (polarToCartX(mCurrentAngle) * mArrowLength);
        float y = (polarToCartY(mCurrentAngle) * mArrowLength);
        Path arrowPath = calculatePath(x,y);
        if (mTriggerLongSwipe) {
            arrowPath.addPath(calculatePath(x,y),
                    mArrowThickness * 2.0f * (mIsLeftPanel ? 1 : -1), 0.0f);
        }

        if (mShowProtection) {
            canvas.drawPath(arrowPath, mProtectionPaint);
        }
@@ -616,8 +635,8 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
        return mCurrentTranslation;
    }

    private void triggerBack() {
        mBackCallback.triggerBack();
    private void triggerBack(boolean triggerLongSwipe) {
        mBackCallback.triggerBack(triggerLongSwipe);

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
@@ -695,6 +714,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
            Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false");
        }
        setTriggerBack(false /* triggerBack */, false /* animated */);
        setTriggerLongSwipe(false /* triggerLongSwipe */, false /* animated */);
        setDesiredTranslation(0, false /* animated */);
        setCurrentTranslation(0);
        updateAngle(false /* animate */);
@@ -719,6 +739,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
            }
        }
        mPreviousTouchTranslation = touchTranslation;
        boolean isLongSwipe = touchTranslation > mLongSwipeThreshold;

        // Apply a haptic on drag slop passed
        if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
@@ -781,6 +802,12 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
                    + ", x=" + x
                    + ", mStartX=" + mStartX);
        }

        if (mIsLongSwipeEnabled) {
            boolean triggerLongSwipe = triggerBack && isLongSwipe;
            setTriggerLongSwipe(triggerLongSwipe, true /* animated */);
        }

        setTriggerBack(triggerBack, true /* animated */);

        if (!mTriggerBack) {
@@ -869,6 +896,18 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
        }
    }

    private void setTriggerLongSwipe(boolean triggerLongSwipe, boolean animated) {
        if (mTriggerLongSwipe != triggerLongSwipe) {
            mTriggerLongSwipe = triggerLongSwipe;
            mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
            mAngleAnimation.cancel();
            updateAngle(animated);
            // Whenever the trigger back state changes the existing translation animation should be
            // cancelled
            mTranslationAnimation.cancel();
        }
    }

    private void updateAngle(boolean animated) {
        float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
        if (newAngle != mDesiredAngle) {
+31 −0
Original line number Diff line number Diff line
@@ -564,6 +564,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    private Action mAssistLongPressAction;
    private Action mAppSwitchPressAction;
    private Action mAppSwitchLongPressAction;
    private Action mEdgeLongSwipeAction;

    // support for activating the lock screen while the screen is on
    private HashSet<Integer> mAllowLockscreenWhenOnDisplays = new HashSet<>();
@@ -686,6 +687,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    private int mTorchTimeout;
    private PendingIntent mTorchOffPendingIntent;

    private boolean mLongSwipeDown;
    private static final int LONG_SWIPE_FLAGS = KeyEvent.FLAG_LONG_PRESS
            | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY;

    private class PolicyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
@@ -846,6 +851,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            resolver.registerContentObserver(LineageSettings.System.getUriFor(
                    LineageSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION), false, this,
                    UserHandle.USER_ALL);
            resolver.registerContentObserver(LineageSettings.System.getUriFor(
                    LineageSettings.System.KEY_EDGE_LONG_SWIPE_ACTION), false, this,
                    UserHandle.USER_ALL);
            resolver.registerContentObserver(LineageSettings.System.getUriFor(
                    LineageSettings.System.HOME_WAKE_SCREEN), false, this,
                    UserHandle.USER_ALL);
@@ -2339,6 +2347,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        mAppSwitchLongPressAction = Action.fromIntSafe(res.getInteger(
                org.lineageos.platform.internal.R.integer.config_longPressOnAppSwitchBehavior));

        mEdgeLongSwipeAction = Action.NOTHING;

        mHomeLongPressAction = Action.fromIntSafe(res.getInteger(
                org.lineageos.platform.internal.R.integer.config_longPressOnHomeBehavior));
        if (mHomeLongPressAction.ordinal() > Action.SLEEP.ordinal()) {
@@ -2383,6 +2393,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                LineageSettings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION,
                mAppSwitchLongPressAction);

        mEdgeLongSwipeAction = Action.fromSettings(resolver,
                LineageSettings.System.KEY_EDGE_LONG_SWIPE_ACTION,
                mEdgeLongSwipeAction);

        mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
        if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
@@ -3990,6 +4004,23 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        // Handle special keys.
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK: {
                boolean isLongSwipe = (event.getFlags() & LONG_SWIPE_FLAGS) == LONG_SWIPE_FLAGS;
                if (mLongSwipeDown && isLongSwipe && !down) {
                    // Trigger long swipe action
                    performKeyAction(mEdgeLongSwipeAction, event);
                    // Reset long swipe state
                    mLongSwipeDown = false;
                    // Don't pass back press to app
                    result &= ~ACTION_PASS_TO_USER;
                    break;
                }
                mLongSwipeDown = isLongSwipe && down;
                if (mLongSwipeDown) {
                    // Don't pass back press to app
                    result &= ~ACTION_PASS_TO_USER;
                    break;
                }

                if (down) {
                    mBackKeyHandled = false;
                } else {