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

Commit 700dc99d authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Enabling simple gesture navigation for 3P launcher

In case of 3P launcher, swipe-up will go to Launcher and there will be
no way to reach overview.

Bug: 135769778
Change-Id: Ib2c6bb1b13e6055d30b7360ec077b0a2fece66ff
parent a90531cd
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -614,6 +615,10 @@ public class TouchInteractionService extends Service implements
        } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
            return mResetGestureInputConsumer;
        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
            return new FallbackNoButtonInputConsumer(this, activityControl,
                    mInputMonitorCompat, mSwipeSharedState, mSwipeTouchRegion,
                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
        } else {
            return createOtherActivityInputConsumer(event, runningTaskInfo);
        }
+358 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.quickstep.inputconsumers;

import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;

import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.R;
import com.android.quickstep.ActivityControlHelper;
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.SwipeSharedState;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.RecentsAnimationListenerSet;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;

public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {

    private static final int STATE_NOT_FINISHED = 0;
    private static final int STATE_FINISHED_TO_HOME = 1;
    private static final int STATE_FINISHED_TO_APP = 2;

    private static final float PROGRESS_TO_END_GESTURE = -2;

    private final ActivityControlHelper mActivityControlHelper;
    private final InputMonitorCompat mInputMonitor;
    private final Context mContext;
    private final NavBarPosition mNavBarPosition;
    private final SwipeSharedState mSwipeSharedState;
    private final OverviewComponentObserver mOverviewComponentObserver;
    private final int mRunningTaskId;

    private final ClipAnimationHelper mClipAnimationHelper;
    private final TransformParams mTransformParams = new TransformParams();
    private final float mTransitionDragLength;
    private final DeviceProfile mDP;

    private final RectF mSwipeTouchRegion;
    private final boolean mDisableHorizontalSwipe;

    private final PointF mDownPos = new PointF();
    private final PointF mLastPos = new PointF();

    private int mActivePointerId = -1;
    // Slop used to determine when we say that the gesture has started.
    private boolean mPassedPilferInputSlop;

    private VelocityTracker mVelocityTracker;

    // Distance after which we start dragging the window.
    private final float mTouchSlop;

    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
    private float mStartDisplacement;
    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
    private float mProgress;

    private int mState = STATE_NOT_FINISHED;

    public FallbackNoButtonInputConsumer(Context context,
            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
            OverviewComponentObserver overviewComponentObserver,
            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
        mContext = context;
        mActivityControlHelper = activityControlHelper;
        mInputMonitor = inputMonitor;
        mOverviewComponentObserver = overviewComponentObserver;
        mRunningTaskId = runningTaskInfo.id;

        mSwipeSharedState = swipeSharedState;
        mSwipeTouchRegion = swipeTouchRegion;
        mDisableHorizontalSwipe = disableHorizontalSwipe;

        mNavBarPosition = new NavBarPosition(context);
        mVelocityTracker = VelocityTracker.obtain();

        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
                * ViewConfiguration.get(context).getScaledTouchSlop();

        mClipAnimationHelper = new ClipAnimationHelper(context);

        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
        Rect tempRect = new Rect();
        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
                mDP, context, tempRect);
        mClipAnimationHelper.updateTargetRect(tempRect);
    }

    @Override
    public int getType() {
        return TYPE_FALLBACK_NO_BUTTON;
    }

    @Override
    public void onMotionEvent(MotionEvent ev) {
        if (mVelocityTracker == null) {
            return;
        }

        mVelocityTracker.addMovement(ev);
        if (ev.getActionMasked() == ACTION_POINTER_UP) {
            mVelocityTracker.clear();
        }

        switch (ev.getActionMasked()) {
            case ACTION_DOWN: {
                mActivePointerId = ev.getPointerId(0);
                mDownPos.set(ev.getX(), ev.getY());
                mLastPos.set(mDownPos);
                break;
            }
            case ACTION_POINTER_DOWN: {
                if (!mPassedPilferInputSlop) {
                    // Cancel interaction in case of multi-touch interaction
                    int ptrIdx = ev.getActionIndex();
                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
                        forceCancelGesture(ev);
                    }
                }
                break;
            }
            case ACTION_POINTER_UP: {
                int ptrIdx = ev.getActionIndex();
                int ptrId = ev.getPointerId(ptrIdx);
                if (ptrId == mActivePointerId) {
                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
                    mDownPos.set(
                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
                    mActivePointerId = ev.getPointerId(newPointerIdx);
                }
                break;
            }
            case ACTION_MOVE: {
                int pointerIndex = ev.findPointerIndex(mActivePointerId);
                if (pointerIndex == INVALID_POINTER_ID) {
                    break;
                }
                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
                float displacement = getDisplacement(ev);

                if (!mPassedPilferInputSlop) {
                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
                            > Math.abs(mLastPos.y - mDownPos.y)) {
                        // Horizontal gesture is not allowed in this region
                        forceCancelGesture(ev);
                        break;
                    }

                    if (Math.abs(displacement) >= mTouchSlop) {
                        mPassedPilferInputSlop = true;

                        // Deferred gesture, start the animation and gesture tracking once
                        // we pass the actual touch slop
                        startTouchTrackingForWindowAnimation(displacement);
                    }
                } else {
                    updateDisplacement(displacement - mStartDisplacement);
                }
                break;
            }
            case ACTION_CANCEL:
            case ACTION_UP: {
                finishTouchTracking(ev);
                break;
            }
        }
    }

    private void startTouchTrackingForWindowAnimation(float displacement) {
        mStartDisplacement = Math.min(displacement, -mTouchSlop);

        RecentsAnimationListenerSet listenerSet =
                mSwipeSharedState.newRecentsAnimationListenerSet();
        listenerSet.addListener(this);
        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
        BackgroundExecutor.get().submit(
                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
                        homeIntent, null, listenerSet, null, null));

        ActivityManagerWrapper.getInstance().closeSystemWindows(
                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
        mInputMonitor.pilferPointers();
    }

    private void updateDisplacement(float displacement) {
        mProgress = displacement / mTransitionDragLength;
        mTransformParams.setProgress(mProgress);

        if (mSwipeAnimationTargetSet != null) {
            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
        }
    }

    private void forceCancelGesture(MotionEvent ev) {
        int action = ev.getAction();
        ev.setAction(ACTION_CANCEL);
        finishTouchTracking(ev);
        ev.setAction(action);
    }

    /**
     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
     * the animation can still be running.
     */
    private void finishTouchTracking(MotionEvent ev) {
        if (ev.getAction() == ACTION_CANCEL) {
            mState = STATE_FINISHED_TO_APP;
        } else {
            mVelocityTracker.computeCurrentVelocity(1000,
                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
            float velocity = mNavBarPosition.isRightEdge() ? velocityX
                    : mNavBarPosition.isLeftEdge() ? -velocityX
                            : velocityY;
            float flingThreshold = mContext.getResources()
                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
            boolean isFling = Math.abs(velocity) > flingThreshold;

            boolean goingHome;
            if (!isFling) {
                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
            } else {
                goingHome = velocity < 0;
            }

            if (goingHome) {
                mState = STATE_FINISHED_TO_HOME;
            } else {
                mState = STATE_FINISHED_TO_APP;
            }
        }

        if (mSwipeAnimationTargetSet != null) {
            finishAnimationTargetSet();
        }
    }

    private void finishAnimationTargetSet() {
        if (mState == STATE_FINISHED_TO_APP) {
            mSwipeAnimationTargetSet.finishController(false, null, false);
        } else {
            if (mProgress < PROGRESS_TO_END_GESTURE) {
                mSwipeAnimationTargetSet.finishController(true, null, true);
            } else {
                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
                if (duration < 0) {
                    duration = MIN_SWIPE_DURATION;
                }

                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
                anim.addUpdateListener(a -> {
                    float p = (Float) anim.getAnimatedValue();
                    mTransformParams.setProgress(p);
                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
                });
                anim.setDuration(duration);
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mSwipeAnimationTargetSet.finishController(true, null, true);
                    }
                });
                anim.start();
            }
        }
    }

    @Override
    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
        mSwipeAnimationTargetSet = targetSet;
        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);

        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
        if (runningTaskTarget != null) {
            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
        }
        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);

        overviewStackBounds
                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);

        if (mState != STATE_NOT_FINISHED) {
            finishAnimationTargetSet();
        }
    }

    @Override
    public void onRecentsAnimationCanceled() { }

    private float getDisplacement(MotionEvent ev) {
        if (mNavBarPosition.isRightEdge()) {
            return ev.getX() - mDownPos.x;
        } else if (mNavBarPosition.isLeftEdge()) {
            return mDownPos.x - ev.getX();
        } else {
            return ev.getY() - mDownPos.y;
        }
    }

    @Override
    public boolean allowInterceptByParent() {
        return !mPassedPilferInputSlop;
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ public interface InputConsumer {
    int TYPE_SCREEN_PINNED = 1 << 6;
    int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
    int TYPE_RESET_GESTURE = 1 << 8;
    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;

    String[] NAMES = new String[] {
           "TYPE_NO_OP",                    // 0
@@ -44,6 +45,7 @@ public interface InputConsumer {
            "TYPE_SCREEN_PINNED",           // 6
            "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
            "TYPE_RESET_GESTURE",           // 8
            "TYPE_FALLBACK_NO_BUTTON",      // 9
    };

    InputConsumer NO_OP = () -> TYPE_NO_OP;
+1 −1
Original line number Diff line number Diff line
@@ -79,7 +79,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
    private static final String UP_EVT = "OtherActivityInputConsumer.UP";

    // TODO: Move to quickstep contract
    private static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
    public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;

    private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
    private final RunningTaskInfo mRunningTask;
+26 −1
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.content.pm.ResolveInfo;

import com.android.systemui.shared.system.PackageManagerWrapper;

import com.android.systemui.shared.system.QuickStepContract;
import java.util.ArrayList;

/**
@@ -58,7 +57,9 @@ public final class OverviewComponentObserver {
    private String mUpdateRegisteredPackage;
    private ActivityControlHelper mActivityControlHelper;
    private Intent mOverviewIntent;
    private Intent mHomeIntent;
    private int mSystemUiStateFlags;
    private boolean mIsHomeAndOverviewSame;

    public OverviewComponentObserver(Context context) {
        mContext = context;
@@ -93,11 +94,14 @@ public final class OverviewComponentObserver {

        final String overviewIntentCategory;
        ComponentName overviewComponent;
        mHomeIntent = null;

        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
                (defaultHome == null || mMyHomeComponent.equals(defaultHome))) {
            // User default home is same as out home app. Use Overview integrated in Launcher.
            overviewComponent = mMyHomeComponent;
            mActivityControlHelper = new LauncherActivityControllerHelper();
            mIsHomeAndOverviewSame = true;
            overviewIntentCategory = Intent.CATEGORY_HOME;

            if (mUpdateRegisteredPackage != null) {
@@ -109,8 +113,12 @@ public final class OverviewComponentObserver {
            // The default home app is a different launcher. Use the fallback Overview instead.
            overviewComponent = new ComponentName(mContext, RecentsActivity.class);
            mActivityControlHelper = new FallbackActivityControllerHelper();
            mIsHomeAndOverviewSame = false;
            overviewIntentCategory = Intent.CATEGORY_DEFAULT;

            mHomeIntent = new Intent(Intent.ACTION_MAIN)
                    .addCategory(Intent.CATEGORY_HOME)
                    .setComponent(defaultHome);
            // User's default home app can change as a result of package updates of this app (such
            // as uninstalling the app or removing the "Launcher" feature in an update).
            // Listen for package updates of this app (and remove any previously attached
@@ -135,6 +143,9 @@ public final class OverviewComponentObserver {
                .addCategory(overviewIntentCategory)
                .setComponent(overviewComponent)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (mHomeIntent == null) {
            mHomeIntent = mOverviewIntent;
        }
    }

    /**
@@ -158,6 +169,20 @@ public final class OverviewComponentObserver {
        return mOverviewIntent;
    }

    /**
     * Get the current intent for going to the home activity.
     */
    public Intent getHomeIntent() {
        return mHomeIntent;
    }

    /**
     * Returns true if home and overview are same activity.
     */
    public boolean isHomeAndOverviewSame() {
        return mIsHomeAndOverviewSame;
    }

    /**
     * Get the current activity control helper for managing interactions to the overview activity.
     *