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

Commit 5bb0d517 authored by Nico's avatar Nico Committed by Bruno Martins
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 0208b9bd
Loading
Loading
Loading
Loading
+50 −3
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.NAVIGATION_BAR_TRANSIENT;

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

import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
@@ -60,6 +62,9 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.tuner.TunerService;

import lineageos.providers.LineageSettings;

import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -67,12 +72,15 @@ import java.util.concurrent.Executor;
/**
 * Utility class to handle edge swipes for back gesture
 */
public class EdgeBackGestureHandler implements DisplayListener {
public class EdgeBackGestureHandler implements DisplayListener, 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 final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
        @Override
        public void onListenerRegistered(IPinnedStackController controller) {
@@ -157,6 +165,7 @@ public class EdgeBackGestureHandler implements DisplayListener {
    private boolean mIsGesturalModeEnabled;
    private boolean mIsEnabled;
    private boolean mIsInTransientImmersiveStickyState;
    private boolean mIsLongSwipeEnabled;

    private InputMonitor mInputMonitor;
    private InputEventReceiver mInputEventReceiver;
@@ -169,6 +178,7 @@ public class EdgeBackGestureHandler implements DisplayListener {
    private RegionSamplingHelper mRegionSamplingHelper;
    private int mLeftInset;
    private int mRightInset;
    private float mLongSwipeWidth;

    public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) {
        final Resources res = context.getResources();
@@ -178,6 +188,9 @@ public class EdgeBackGestureHandler implements DisplayListener {
        mWm = context.getSystemService(WindowManager.class);
        mOverviewProxyService = overviewProxyService;

        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
@@ -301,6 +314,7 @@ public class EdgeBackGestureHandler implements DisplayListener {
            mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
            mEdgePanelLp.windowAnimations = 0;
            mEdgePanel.setLayoutParams(mEdgePanelLp);
            updateLongSwipeWidth();
            mWm.addView(mEdgePanel, mEdgePanelLp);
            mRegionSamplingHelper = new RegionSamplingHelper(mEdgePanel,
                    new RegionSamplingHelper.SamplingCallback() {
@@ -418,11 +432,19 @@ public class EdgeBackGestureHandler implements DisplayListener {
            boolean isUp = action == MotionEvent.ACTION_UP;
            if (isUp) {
                boolean performAction = mEdgePanel.shouldTriggerBack();
                if (performAction) {
                boolean performLongSwipe = mEdgePanel.shouldTriggerLongSwipe();
                if (performLongSwipe) {
                    // Perform long swipe action
                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK,
                            KeyEvent.FLAG_LONG_PRESS);
                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK,
                            KeyEvent.FLAG_LONG_PRESS);
                } else if (performAction) {
                    // Perform back
                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
                }
                performAction = performAction || performLongSwipe;
                mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x,
                        (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                int backtype = performAction ? (mInRejectedExclusion
@@ -460,6 +482,17 @@ public class EdgeBackGestureHandler implements DisplayListener {
        mEdgePanel.adjustRectToBoundingBox(mSamplingRect);
    }

    private void updateLongSwipeWidth() {
        if (mIsEnabled && mEdgePanel != null) {
            if (mIsLongSwipeEnabled) {
                mLongSwipeWidth = MathUtils.min(mDisplaySize.x * 0.5f, mEdgePanelLp.width * 2.5f);
                mEdgePanel.setLongSwipeThreshold(mLongSwipeWidth);
            } else {
                mEdgePanel.setLongSwipeThreshold(0.0f);
            }
        }
    }

    @Override
    public void onDisplayAdded(int displayId) { }

@@ -477,13 +510,27 @@ public class EdgeBackGestureHandler implements DisplayListener {
        mContext.getSystemService(DisplayManager.class)
                .getDisplay(mDisplayId)
                .getRealSize(mDisplaySize);
        updateLongSwipeWidth();
    }

    @Override
    public void onTuningChanged(String key, String newValue) {
        if (KEY_EDGE_LONG_SWIPE_ACTION.equals(key)) {
            mIsLongSwipeEnabled = newValue != null
                    && Action.fromIntSafe(Integer.parseInt(newValue)) != Action.NOTHING;
            updateLongSwipeWidth();
        }
    }

    private void sendEvent(int action, int code) {
        sendEvent(action, code, 0);
    }

    private void 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);

        // Bubble controller will give us a valid display id if it should get the back event
+39 −0
Original line number Diff line number Diff line
@@ -179,7 +179,9 @@ public class NavigationBarEdgePanel extends View {
    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;
@@ -192,6 +194,8 @@ public class NavigationBarEdgePanel extends View {
    private long mVibrationTime;
    private int mScreenSize;

    private boolean mIsLongSwipeEnabled;

    private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
            = new DynamicAnimation.OnAnimationEndListener() {
        @Override
@@ -328,6 +332,10 @@ public class NavigationBarEdgePanel extends View {
        return mTriggerBack;
    }

    public boolean shouldTriggerLongSwipe() {
        return mTriggerLongSwipe;
    }

    public void setIsDark(boolean isDark, boolean animate) {
        mIsDark = isDark;
        updateIsDark(animate);
@@ -342,6 +350,12 @@ public class NavigationBarEdgePanel extends View {
        mIsLeftPanel = isLeftPanel;
    }

    public void setLongSwipeThreshold(float longSwipeThreshold) {
        mLongSwipeThreshold = longSwipeThreshold;
        mIsLongSwipeEnabled = mLongSwipeThreshold > 0;
        setTriggerLongSwipe(mIsLongSwipeEnabled && mTriggerLongSwipe, false /* animated */);
    }

    /**
     * Adjust the rect to conform the the actual visible bounding box of the arrow.
     *
@@ -436,6 +450,11 @@ public class NavigationBarEdgePanel extends View {
        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);
        }
@@ -586,6 +605,7 @@ public class NavigationBarEdgePanel extends View {
        mTranslationAnimation.setSpring(mRegularTranslationSpring);
        // Reset the arrow to the side
        setTriggerBack(false /* triggerBack */, false /* animated */);
        setTriggerLongSwipe(false /* triggerLongSwipe */, false /* animated */);
        setDesiredTranslation(0, false /* animated */);
        setCurrentTranslation(0);
        updateAngle(false /* animate */);
@@ -609,6 +629,7 @@ public class NavigationBarEdgePanel extends View {
            }
        }
        mPreviousTouchTranslation = touchTranslation;
        boolean isLongSwipe = touchTranslation > mLongSwipeThreshold;

        // Apply a haptic on drag slop passed
        if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) {
@@ -660,6 +681,12 @@ public class NavigationBarEdgePanel extends View {
        if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
            triggerBack = false;
        }

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

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

        if (!mTriggerBack) {
@@ -730,6 +757,18 @@ public class NavigationBarEdgePanel extends View {
        }
    }

    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
@@ -582,6 +582,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<>();
@@ -732,6 +733,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 LineageHardwareManager mLineageHardware;

    private class PolicyHandler extends Handler {
@@ -932,6 +937,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);
@@ -2310,6 +2318,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()) {
@@ -2354,6 +2364,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 (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
@@ -4311,6 +4325,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) {
                    interceptBackKeyDown();
                } else {