Swipe up from nav bar in fallback recents to go home

- Extract swipe-up-to-home logic from OverviewWithoutFocusInputConsumer
  to TriggerSwipeUpTouchTracker util class
- Add FallbackNavBarTouchController for fallback recents to trigger home

Bug: 140845330
Change-Id: I846f1afdaf36529f348b0d3c9c479f01053a463b
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
new file mode 100644
index 0000000..6f919c1
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -0,0 +1,78 @@
+/*
+ * 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.fallback;
+
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+
+/**
+ * In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
+ */
+public class FallbackNavBarTouchController implements TouchController {
+
+    private final RecentsActivity mActivity;
+    @Nullable
+    private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+
+    public FallbackNavBarTouchController(RecentsActivity activity) {
+        mActivity = activity;
+        SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
+        if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
+            NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
+                    DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+            mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
+                    true /* disableHorizontalSwipe */, navBarPosition,
+                    null /* onInterceptTouch */, this::onSwipeUp);
+        } else {
+            mTriggerSwipeUpTracker = null;
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+        if (cameFromNavBar && mTriggerSwipeUpTracker != null) {
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                mTriggerSwipeUpTracker.init();
+            }
+            onControllerTouchEvent(ev);
+            return mTriggerSwipeUpTracker.interceptedTouch();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        if (mTriggerSwipeUpTracker != null) {
+            mTriggerSwipeUpTracker.onMotionEvent(ev);
+            return true;
+        }
+        return false;
+    }
+
+    private void onSwipeUp(boolean wasFling) {
+        mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 1820729..de5fd7c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -48,7 +48,10 @@
     }
 
     public void setup() {
-        mControllers = new TouchController[] { new RecentsTaskController(mActivity) };
+        mControllers = new TouchController[] {
+                new RecentsTaskController(mActivity),
+                new FallbackNavBarTouchController(mActivity),
+        };
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 875ec29..ca15ca1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,23 +15,12 @@
  */
 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_UP;
-
-import static com.android.launcher3.Utilities.squaredHypot;
-
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.PointF;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -39,31 +28,22 @@
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer {
 
     private final Context mContext;
-    private final RecentsAnimationDeviceState mDeviceState;
-    private final GestureState mGestureState;
     private final InputMonitorCompat mInputMonitor;
-    private final boolean mDisableHorizontalSwipe;
-    private final PointF mDownPos = new PointF();
-    private final float mSquaredTouchSlop;
-
-    private boolean mInterceptedTouch;
-    private VelocityTracker mVelocityTracker;
+    private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
 
     public OverviewWithoutFocusInputConsumer(Context context,
             RecentsAnimationDeviceState deviceState, GestureState gestureState,
             InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
         mContext = context;
-        mDeviceState = deviceState;
-        mGestureState = gestureState;
         mInputMonitor = inputMonitor;
-        mDisableHorizontalSwipe = disableHorizontalSwipe;
-        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
-        mVelocityTracker = VelocityTracker.obtain();
+        mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
+                deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
     }
 
     @Override
@@ -73,97 +53,31 @@
 
     @Override
     public boolean allowInterceptByParent() {
-        return !mInterceptedTouch;
-    }
-
-    private void endTouchTracking() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
+        return !mTriggerSwipeUpTracker.interceptedTouch();
     }
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            return;
-        }
+        mTriggerSwipeUpTracker.onMotionEvent(ev);
+    }
 
-        mVelocityTracker.addMovement(ev);
-        switch (ev.getActionMasked()) {
-            case ACTION_DOWN: {
-                mDownPos.set(ev.getX(), ev.getY());
-                break;
-            }
-            case ACTION_MOVE: {
-                if (!mInterceptedTouch) {
-                    float displacementX = ev.getX() - mDownPos.x;
-                    float displacementY = ev.getY() - mDownPos.y;
-                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
-                        if (mDisableHorizontalSwipe
-                                && Math.abs(displacementX) > Math.abs(displacementY)) {
-                            // Horizontal gesture is not allowed in this region
-                            endTouchTracking();
-                            break;
-                        }
-
-                        mInterceptedTouch = true;
-
-                        if (mInputMonitor != null) {
-                            mInputMonitor.pilferPointers();
-                        }
-                    }
-                }
-                break;
-            }
-
-            case ACTION_CANCEL:
-                endTouchTracking();
-                break;
-
-            case ACTION_UP: {
-                finishTouchTracking(ev);
-                endTouchTracking();
-                break;
-            }
+    private void onInterceptTouch() {
+        if (mInputMonitor != null) {
+            mInputMonitor.pilferPointers();
         }
     }
 
-    private void finishTouchTracking(MotionEvent ev) {
-        mVelocityTracker.computeCurrentVelocity(100);
-        float velocityX = mVelocityTracker.getXVelocity();
-        float velocityY = mVelocityTracker.getYVelocity();
-        float velocity = mDeviceState.getNavBarPosition().isRightEdge()
-                ? -velocityX
-                : mDeviceState.getNavBarPosition().isLeftEdge()
-                        ? velocityX
-                        : -velocityY;
-
-        final boolean triggerQuickstep;
-        int touch = Touch.FLING;
-        if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
-            triggerQuickstep = velocity > 0;
-        } else {
-            float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
-            float displacementY = ev.getY() - mDownPos.y;
-            triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
-            touch = Touch.SWIPE;
-        }
-
-        if (triggerQuickstep) {
-            mContext.startActivity(new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_HOME)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            ActiveGestureLog.INSTANCE.addLog("startQuickstep");
-            BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
-            int pageIndex = -1; // This number doesn't reflect workspace page index.
-                                // It only indicates that launcher client screen was shown.
-            int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
-            activity.getUserEventDispatcher().logActionOnContainer(
-                    touch, Direction.UP, containerType, pageIndex);
-            activity.getUserEventDispatcher().setPreviousHomeGesture(true);
-        } else {
-            // ignore
-        }
+    private void onSwipeUp(boolean wasFling) {
+        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
+        BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+        int pageIndex = -1; // This number doesn't reflect workspace page index.
+                            // It only indicates that launcher client screen was shown.
+        int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+        activity.getUserEventDispatcher().logActionOnContainer(
+                wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
+        activity.getUserEventDispatcher().setPreviousHomeGesture(true);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
new file mode 100644
index 0000000..c71258b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -0,0 +1,167 @@
+/*
+ * 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.util;
+
+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_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Tracks motion events to determine whether a gesture on the nav bar is a swipe up.
+ */
+public class TriggerSwipeUpTouchTracker {
+
+    private final PointF mDownPos = new PointF();
+    private final float mSquaredTouchSlop;
+    private final float mMinFlingVelocity;
+    private final boolean mDisableHorizontalSwipe;
+    private final NavBarPosition mNavBarPosition;
+    private final Runnable mOnInterceptTouch;
+    private final OnSwipeUpListener mOnSwipeUp;
+
+    private boolean mInterceptedTouch;
+    private VelocityTracker mVelocityTracker;
+
+    public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
+            NavBarPosition navBarPosition, Runnable onInterceptTouch,
+            OnSwipeUpListener onSwipeUp) {
+        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+        mNavBarPosition = navBarPosition;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+        mOnInterceptTouch = onInterceptTouch;
+        mOnSwipeUp = onSwipeUp;
+
+        init();
+    }
+
+    /**
+     * Reset some initial values to prepare for the next gesture.
+     */
+    public void init() {
+        mInterceptedTouch = false;
+        mVelocityTracker = VelocityTracker.obtain();
+    }
+
+    /**
+     * @return Whether we have passed the touch slop and are still tracking the gesture.
+     */
+    public boolean interceptedTouch() {
+        return mInterceptedTouch;
+    }
+
+    /**
+     * Track motion events to determine whether an atomic swipe up has occurred.
+     */
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                break;
+            }
+            case ACTION_MOVE: {
+                if (!mInterceptedTouch) {
+                    float displacementX = ev.getX() - mDownPos.x;
+                    float displacementY = ev.getY() - mDownPos.y;
+                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+                        if (mDisableHorizontalSwipe
+                                && Math.abs(displacementX) > Math.abs(displacementY)) {
+                            // Horizontal gesture is not allowed in this region
+                            endTouchTracking();
+                            break;
+                        }
+
+                        mInterceptedTouch = true;
+
+                        if (mOnInterceptTouch != null) {
+                            mOnInterceptTouch.run();
+                        }
+                    }
+                }
+                break;
+            }
+
+            case ACTION_CANCEL:
+                endTouchTracking();
+                break;
+
+            case ACTION_UP: {
+                onGestureEnd(ev);
+                endTouchTracking();
+                break;
+            }
+        }
+    }
+
+    private void endTouchTracking() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void onGestureEnd(MotionEvent ev) {
+        mVelocityTracker.computeCurrentVelocity(1000);
+        float velocityX = mVelocityTracker.getXVelocity();
+        float velocityY = mVelocityTracker.getYVelocity();
+        float velocity = mNavBarPosition.isRightEdge()
+                ? -velocityX
+                : mNavBarPosition.isLeftEdge()
+                        ? velocityX
+                        : -velocityY;
+
+        final boolean wasFling = Math.abs(velocity) >= mMinFlingVelocity;
+        final boolean isSwipeUp;
+        if (wasFling) {
+            isSwipeUp = velocity > 0;
+        } else {
+            float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
+            float displacementY = ev.getY() - mDownPos.y;
+            isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+        }
+
+        if (isSwipeUp && mOnSwipeUp != null) {
+            mOnSwipeUp.onSwipeUp(wasFling);
+        }
+    }
+
+    /**
+     * Callback when the gesture ends and was determined to be a swipe from the nav bar.
+     */
+    public interface OnSwipeUpListener {
+        /**
+         * Called on touch up if a swipe up was detected.
+         * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
+         */
+        void onSwipeUp(boolean wasFling);
+    }
+}