Enabling simple gesture navigation for 3P launcher
am: 700dc99d9b

Change-Id: I56cebba3a02f2ac81fa8d00c33b28a29ba350388
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 22ebe61..8f08f0d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -81,6 +81,7 @@
 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 @@
         } 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);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
new file mode 100644
index 0000000..d05ca2a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -0,0 +1,358 @@
+/*
+ * 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;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index a1e5d47..f5cf654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,6 +33,7 @@
     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 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
+            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 6bc543f..4c137d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -79,7 +79,7 @@
     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;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a73b8b..0738aff 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,7 +33,6 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
-import com.android.systemui.shared.system.QuickStepContract;
 import java.util.ArrayList;
 
 /**
@@ -58,7 +57,9 @@
     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 @@
 
         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 @@
             // 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 @@
                 .addCategory(overviewIntentCategory)
                 .setComponent(overviewComponent)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (mHomeIntent == null) {
+            mHomeIntent = mOverviewIntent;
+        }
     }
 
     /**
@@ -159,6 +170,20 @@
     }
 
     /**
+     * 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.
      *
      * @return the current activity control helper