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

Commit 397468da authored by Tracy Zhou's avatar Tracy Zhou
Browse files

Support back gesture on trackpad

TODO: change back animation API to consider trackpad events

Bug: 255697805
Test: https://recall.googleplex.com/projects/3388b17c-d22f-46f8-b140-a102690377b4/sessions/3f751251-07f2-4cce-b577-474746bf4eab
Change-Id: I50963a3a495c9f2069bdc9e2a8606bb403b51f1c
parent 0bf138d6
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.plugins;

import android.view.MotionEvent;

/** Handles both trackpad and touch events and report displacements in both axis's. */
public interface MotionEventsHandlerBase {

    void onMotionEvent(MotionEvent ev);

    float getDisplacementX(MotionEvent ev);

    float getDisplacementY(MotionEvent ev);

    String dump();
}
+3 −0
Original line number Diff line number Diff line
@@ -48,6 +48,9 @@ public interface NavigationEdgeBackPlugin extends Plugin {
    /** Sets the base LayoutParams for the UI. */
    void setLayoutParams(WindowManager.LayoutParams layoutParams);

    /** Sets the motion events handler for the plugin. */
    default void setMotionEventsHandler(MotionEventsHandlerBase motionEventsHandler) {}

    /** Updates the UI based on the motion events passed in device coordinates. */
    void onMotionEvent(MotionEvent motionEvent);

+5 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.MotionEventsHandlerBase
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -498,6 +499,10 @@ class BackPanelController private constructor(
        windowManager.addView(mView, layoutParams)
    }

    override fun setMotionEventsHandler(motionEventsHandler: MotionEventsHandlerBase?) {
        TODO("Not yet implemented")
    }

    private fun isFlung() = velocityTracker!!.run {
        computeCurrentVelocity(1000)
        abs(xVelocity) > MIN_FLING_VELOCITY
+25 −7
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.navigationbar.gestural;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;

import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.getTrackpadScale;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;

import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -241,6 +243,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
    private boolean mIsBackGestureAllowed;
    private boolean mGestureBlockingActivityRunning;
    private boolean mIsNewBackAffordanceEnabled;
    private boolean mIsTrackpadGestureBackEnabled;
    private boolean mIsButtonForceVisible;

    private InputMonitor mInputMonitor;
@@ -269,6 +272,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
    private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);

    private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
    private final MotionEventsHandler mMotionEventsHandler;

    private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
            new NavigationEdgeBackPlugin.BackCallback() {
@@ -401,6 +405,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack

        mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
                mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
        mMotionEventsHandler = new MotionEventsHandler(featureFlags, getTrackpadScale(context));

        updateCurrentUserResources();
    }
@@ -578,6 +583,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack

            // Add a nav bar panel window
            mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
            mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
            resetEdgeBackPlugin();
            mPluginManager.addPluginListener(
                    this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -611,6 +617,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
        }
        mEdgeBackPlugin = edgeBackPlugin;
        mEdgeBackPlugin.setBackCallback(mBackCallback);
        mEdgeBackPlugin.setMotionEventsHandler(mMotionEventsHandler);
        mEdgeBackPlugin.setLayoutParams(createLayoutParams());
        updateDisplaySize();
    }
@@ -854,6 +861,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
    }

    private void onMotionEvent(MotionEvent ev) {
        mMotionEventsHandler.onMotionEvent(ev);
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            if (DEBUG_MISSING_GESTURE) {
@@ -863,15 +871,24 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
            // Verify if this is in within the touch region and we aren't in immersive mode, and
            // either the bouncer is showing or the notification panel is hidden
            mInputEventReceiver.setBatchingEnabled(false);
            boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
            if (isTrackpadEvent) {
                // TODO: show the back arrow based on the direction of the swipe.
                mIsOnLeftEdge = false;
            } else {
                mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
            }
            mMLResults = 0;
            mLogGesture = false;
            mInRejectedExclusion = false;
            boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets
            // Trackpad back gestures don't have zones, so we don't need to check if the down event
            // is within insets.
            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
                    && (isTrackpadEvent || isWithinInsets)
                    && !mGestureBlockingActivityRunning
                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
                    && (isTrackpadEvent || isWithinTouchRegion((int) ev.getX(), (int) ev.getY()));
            if (mAllowGesture) {
                mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                mEdgeBackPlugin.onMotionEvent(ev);
@@ -885,8 +902,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack

            // For debugging purposes, only log edge points
            (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
                    "Gesture [%d,alw=%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
                    System.currentTimeMillis(), mAllowGesture, mIsOnLeftEdge,
                    "Gesture [%d,alw=%B,%B,%B,%B,%B,disp=%s,wl=%d,il=%d,wr=%d,ir=%d,excl=%s]",
                    System.currentTimeMillis(), isTrackpadEvent, mAllowGesture, mIsOnLeftEdge,
                    mIsBackGestureAllowed,
                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisplaySize,
                    mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
@@ -920,8 +937,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
                        mLogGesture = false;
                        return;
                    }
                    float dx = Math.abs(ev.getX() - mDownPoint.x);
                    float dy = Math.abs(ev.getY() - mDownPoint.y);
                    float dx = Math.abs(mMotionEventsHandler.getDisplacementX(ev));
                    float dy = Math.abs(mMotionEventsHandler.getDisplacementY(ev));
                    if (dy > dx && dy > mTouchSlop) {
                        if (mAllowGesture) {
                            logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
@@ -1055,6 +1072,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
        pw.println("  mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
        pw.println("  mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
        pw.println("  mEdgeBackPlugin=" + mEdgeBackPlugin);
        pw.println("  mMotionEventsHandler=" + mMotionEventsHandler);
        if (mEdgeBackPlugin != null) {
            mEdgeBackPlugin.dump(pw);
        }
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.navigationbar.gestural;

import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;

import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;

import android.graphics.PointF;
import android.view.MotionEvent;

import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.MotionEventsHandlerBase;

/** Handles both trackpad and touch events and report displacements in both axis's. */
public class MotionEventsHandler implements MotionEventsHandlerBase {

    private final boolean mIsTrackpadGestureBackEnabled;
    private final int mScale;

    private final PointF mDownPos = new PointF();
    private final PointF mLastPos = new PointF();
    private float mCurrentTrackpadOffsetX = 0;
    private float mCurrentTrackpadOffsetY = 0;

    public MotionEventsHandler(FeatureFlags featureFlags, int scale) {
        mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
        mScale = scale;
    }

    @Override
    public void onMotionEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                onActionDown(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                onActionMove(ev);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                onActionUp(ev);
                break;
            default:
                break;
        }
    }

    private void onActionDown(MotionEvent ev) {
        reset();
        if (!isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
            mDownPos.set(ev.getX(), ev.getY());
            mLastPos.set(mDownPos);
        }
    }

    private void onActionMove(MotionEvent ev) {
        updateMovements(ev);
    }

    private void onActionUp(MotionEvent ev) {
        updateMovements(ev);
    }

    private void updateMovements(MotionEvent ev) {
        if (isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
            mCurrentTrackpadOffsetX += ev.getAxisValue(AXIS_GESTURE_X_OFFSET) * mScale;
            mCurrentTrackpadOffsetY += ev.getAxisValue(AXIS_GESTURE_Y_OFFSET) * mScale;
        } else {
            mLastPos.set(ev.getX(), ev.getY());
        }
    }

    private void reset() {
        mDownPos.set(0, 0);
        mLastPos.set(0, 0);
        mCurrentTrackpadOffsetX = 0;
        mCurrentTrackpadOffsetY = 0;
    }

    @Override
    public float getDisplacementX(MotionEvent ev) {
        return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetX
                : mLastPos.x - mDownPos.x;
    }

    @Override
    public float getDisplacementY(MotionEvent ev) {
        return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetY
                : mLastPos.y - mDownPos.y;
    }

    @Override
    public String dump() {
        return "mDownPos: " + mDownPos + ", mLastPos: " + mLastPos + ", mCurrentTrackpadOffsetX: "
                + mCurrentTrackpadOffsetX + ", mCurrentTrackpadOffsetY: " + mCurrentTrackpadOffsetY;
    }
}
Loading