Proxying touch events from RecentsTouchConsumer to Launcher

While swipe-up animation is running, the user can quickly start
another touch gesture. In that case we keep the recents transtion active
and proxy all touch events to launcher.

Bug: 110901700
Change-Id: Ie3b448dfea00473082dc9143423d3596504a3fcc
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 0eead88..1d1b7da 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -109,7 +110,7 @@
                 return false;
             }
         }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+        if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, TYPE_ACCESSIBLE) != null) {
             return false;
         }
         return true;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index cfd4119..9a920c8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 
@@ -79,7 +80,7 @@
             // If we are already animating from a previous state, we can intercept.
             return true;
         }
-        if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+        if (AbstractFloatingView.getTopOpenViewWithType(mActivity, TYPE_ACCESSIBLE) != null) {
             return false;
         }
         return isRecentsInteractive();
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
index 2fe7a11..16214dd 100644
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -169,12 +169,4 @@
 
         callback.run();
     }
-
-    public float getTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
-        if (!(app.isNotInRecents
-                || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
-            return 0;
-        }
-        return expectedAlpha;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index b0313fc..eea3971 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -15,14 +15,23 @@
  */
 package com.android.quickstep;
 
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 
 import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
 
 /**
  * Wrapper around RecentsAnimationController to help with some synchronization
@@ -43,6 +52,27 @@
     private final ExecutorService mExecutorService =
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
+    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+    private InputConsumerController mInputConsumer =
+            InputConsumerController.getRecentsAnimationInputConsumer();
+    private final Supplier<TouchConsumer> mTouchProxySupplier;
+
+    private boolean mInputConsumerUnregistered;
+    private boolean mTouchProxyEnabled;
+
+    private TouchConsumer mTouchConsumer;
+    private boolean mTouchInProgress;
+    private boolean mInputConsumerUnregisterPending;
+
+    private boolean mFinishPending;
+
+    public RecentsAnimationWrapper(Supplier<TouchConsumer> touchProxySupplier) {
+        // Register the input consumer on the UI thread, to ensure that it runs after any pending
+        // unregister calls
+        mTouchProxySupplier = touchProxySupplier;
+        mMainThreadExecutor.execute(mInputConsumer::registerInputConsumer);
+    }
+
     public synchronized void setController(
             RecentsAnimationControllerCompat controller, RemoteAnimationTargetSet targetSet) {
         TraceHelper.partitionSection("RecentsController", "Set controller " + controller);
@@ -77,21 +107,37 @@
      *                         on the background thread.
      */
     public void finish(boolean toHome, Runnable onFinishComplete) {
-        mExecutorService.submit(() -> {
-            RecentsAnimationControllerCompat controller = mController;
-            mController = null;
-            TraceHelper.endSection("RecentsController",
-                    "Finish " + controller + ", toHome=" + toHome);
-            if (controller != null) {
-                controller.setInputConsumerEnabled(false);
-                controller.finish(toHome);
+        if (!toHome) {
+            mExecutorService.submit(() -> finishBg(false, onFinishComplete));
+        }
+
+        mMainThreadExecutor.execute(() -> {
+            if (mTouchInProgress) {
+                mFinishPending = true;
+                // Execute the callback
                 if (onFinishComplete != null) {
                     onFinishComplete.run();
                 }
+            } else {
+                mExecutorService.submit(() -> finishBg(true, onFinishComplete));
             }
         });
     }
 
+    protected void finishBg(boolean toHome, Runnable onFinishComplete) {
+        RecentsAnimationControllerCompat controller = mController;
+        mController = null;
+        TraceHelper.endSection("RecentsController", "Finish " + controller + ", toHome=" + toHome);
+        if (controller != null) {
+            controller.setInputConsumerEnabled(false);
+            controller.finish(toHome);
+
+            if (onFinishComplete != null) {
+                onFinishComplete.run();
+            }
+        }
+    }
+
     public void enableInputConsumer() {
         mInputConsumerEnabled = true;
         if (mInputConsumerEnabled) {
@@ -106,6 +152,54 @@
         }
     }
 
+    public void unregisterInputConsumer() {
+        mMainThreadExecutor.execute(this::unregisterInputConsumerUi);
+    }
+
+    private void unregisterInputConsumerUi() {
+        if (mTouchProxyEnabled && mTouchInProgress) {
+            mInputConsumerUnregisterPending = true;
+        } else {
+            mInputConsumerUnregistered = true;
+            mInputConsumer.unregisterInputConsumer();
+        }
+    }
+
+    public void enableTouchProxy() {
+        mMainThreadExecutor.execute(this::enableTouchProxyUi);
+    }
+
+    private void enableTouchProxyUi() {
+        if (!mInputConsumerUnregistered) {
+            mTouchProxyEnabled = true;
+            mInputConsumer.setTouchListener(this::onInputConsumerTouch);
+        }
+    }
+
+    private boolean onInputConsumerTouch(MotionEvent ev) {
+        int action = ev.getAction();
+        if (action == ACTION_DOWN) {
+            mTouchInProgress = true;
+            mTouchConsumer = mTouchProxySupplier.get();
+        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+            // Finish any pending actions
+            mTouchInProgress = false;
+            if (mInputConsumerUnregisterPending) {
+                mInputConsumerUnregisterPending = false;
+                mInputConsumer.unregisterInputConsumer();
+            }
+            if (mFinishPending) {
+                mFinishPending = false;
+                mExecutorService.submit(() -> finishBg(true, null));
+            }
+        }
+        if (mTouchConsumer != null) {
+            mTouchConsumer.accept(ev);
+        }
+
+        return true;
+    }
+
     public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
         if (mBehindSystemBars == behindSystemBars) {
             return;
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 4cecffa..42e9aee 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -29,6 +29,8 @@
 @FunctionalInterface
 public interface TouchConsumer extends Consumer<MotionEvent> {
 
+    TouchConsumer NO_OP = (ev) -> {};
+
     @IntDef(flag = true, value = {
             INTERACTION_NORMAL,
             INTERACTION_QUICK_SCRUB
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c555bc6..5a1f523 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,7 +21,9 @@
 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 com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import static com.android.systemui.shared.system.ActivityManagerWrapper
+        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 
 import android.annotation.TargetApi;
@@ -33,7 +35,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
@@ -79,7 +80,7 @@
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
         @Override
-        public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
+        public void onPreMotionEvent(@HitTarget int downHitTarget) {
             TraceHelper.beginSection("SysUiBinder");
             setupTouchConsumer(downHitTarget);
             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
@@ -155,8 +156,6 @@
         }
     };
 
-    private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
-
     private static boolean sConnected = false;
 
     public static boolean isConnected() {
@@ -185,7 +184,7 @@
         mMainThreadExecutor = new MainThreadExecutor();
         mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
-        mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mEventQueue = new MotionEventQueue(mMainThreadChoreographer, TouchConsumer.NO_OP);
         mOverviewInteractionState = OverviewInteractionState.getInstance(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.get(this);
@@ -229,10 +228,11 @@
         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
 
         if (runningTaskInfo == null && !forceToLauncher) {
-            return mNoOpTouchConsumer;
+            return TouchConsumer.NO_OP;
         } else if (forceToLauncher ||
                 runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) {
-            return getOverviewConsumer();
+            return OverviewTouchConsumer.newInstance(
+                    mOverviewCommandHelper.getActivityControlHelper(), false);
         } else {
             if (tracker == null) {
                 tracker = VelocityTracker.obtain();
@@ -245,16 +245,16 @@
         }
     }
 
-    private TouchConsumer getOverviewConsumer() {
-        ActivityControlHelper activityHelper = mOverviewCommandHelper.getActivityControlHelper();
-        BaseDraggingActivity activity = activityHelper.getCreatedActivity();
-        if (activity == null) {
-            return mNoOpTouchConsumer;
+    private void initBackgroundChoreographer() {
+        if (sRemoteUiThread == null) {
+            sRemoteUiThread = new HandlerThread("remote-ui");
+            sRemoteUiThread.start();
         }
-        return new OverviewTouchConsumer(activityHelper, activity);
+        new Handler(sRemoteUiThread.getLooper()).post(() ->
+                mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance());
     }
 
-    private static class OverviewTouchConsumer<T extends BaseDraggingActivity>
+    public static class OverviewTouchConsumer<T extends BaseDraggingActivity>
             implements TouchConsumer {
 
         private final ActivityControlHelper<T> mActivityHelper;
@@ -265,6 +265,8 @@
         private final int mTouchSlop;
         private final QuickScrubController mQuickScrubController;
 
+        private final boolean mStartingInActivityBounds;
+
         private boolean mTrackingStarted = false;
         private boolean mInvalidated = false;
 
@@ -272,11 +274,13 @@
         private boolean mStartPending = false;
         private boolean mEndPending = false;
 
-        OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity) {
+        OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
+                boolean startingInActivityBounds) {
             mActivityHelper = activityHelper;
             mActivity = activity;
             mTarget = activity.getDragLayer();
             mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop();
+            mStartingInActivityBounds = startingInActivityBounds;
 
             mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
                     .getQuickScrubController();
@@ -289,6 +293,10 @@
             }
             int action = ev.getActionMasked();
             if (action == ACTION_DOWN) {
+                if (mStartingInActivityBounds) {
+                    startTouchTracking(ev, false /* updateLocationOffset */);
+                    return;
+                }
                 mTrackingStarted = false;
                 mDownPos.set(ev.getX(), ev.getY());
             } else if (!mTrackingStarted) {
@@ -301,13 +309,13 @@
                         break;
                     case ACTION_CANCEL:
                     case ACTION_UP:
-                        startTouchTracking(ev);
+                        startTouchTracking(ev, true /* updateLocationOffset */);
                         break;
                     case ACTION_MOVE: {
                         float displacement = ev.getY() - mDownPos.y;
                         if (Math.abs(displacement) >= mTouchSlop) {
                             // Start tracking only when mTouchSlop is crossed.
-                            startTouchTracking(ev);
+                            startTouchTracking(ev, true /* updateLocationOffset */);
                         }
                     }
                 }
@@ -322,8 +330,10 @@
             }
         }
 
-        private void startTouchTracking(MotionEvent ev) {
-            mTarget.getLocationOnScreen(mLocationOnScreen);
+        private void startTouchTracking(MotionEvent ev, boolean updateLocationOffset) {
+            if (updateLocationOffset) {
+                mTarget.getLocationOnScreen(mLocationOnScreen);
+            }
 
             // Send down touch event
             MotionEvent down = MotionEvent.obtain(ev);
@@ -336,7 +346,7 @@
 
         private void sendEvent(MotionEvent ev) {
             int flags = ev.getEdgeFlags();
-            ev.setEdgeFlags(flags | EDGE_NAV_BAR);
+            ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
             ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
             if (!mTrackingStarted) {
                 mTarget.onInterceptTouchEvent(ev);
@@ -411,14 +421,13 @@
             mQuickScrubController.onQuickScrubProgress(progress);
         }
 
-    }
-
-    private void initBackgroundChoreographer() {
-        if (sRemoteUiThread == null) {
-            sRemoteUiThread = new HandlerThread("remote-ui");
-            sRemoteUiThread.start();
+        public static TouchConsumer newInstance(
+                ActivityControlHelper activityHelper, boolean startingInActivityBounds) {
+            BaseDraggingActivity activity = activityHelper.getCreatedActivity();
+            if (activity == null) {
+                return TouchConsumer.NO_OP;
+            }
+            return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds);
         }
-        new Handler(sRemoteUiThread.getLooper()).post(() ->
-                mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance());
     }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 0ce522a..d3d38af 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -73,6 +73,7 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.TouchInteractionService.OverviewTouchConsumer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.TransformedRect;
@@ -80,7 +81,6 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -220,10 +220,8 @@
 
     private @InteractionType int mInteractionType = INTERACTION_NORMAL;
 
-    private InputConsumerController mInputConsumer =
-            InputConsumerController.getRecentsAnimationInputConsumer();
-
-    private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
+    private final RecentsAnimationWrapper mRecentsAnimationWrapper =
+            new RecentsAnimationWrapper(this::createNewTouchProxyHandler);
 
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
@@ -247,9 +245,6 @@
                 .createActivityInitListener(this::onActivityInit);
 
         initStateCallbacks();
-        // Register the input consumer on the UI thread, to ensure that it runs after any pending
-        // unregister calls
-        executeOnUiThread(mInputConsumer::registerInputConsumer);
     }
 
     private void initStateCallbacks() {
@@ -695,6 +690,18 @@
         }
     }
 
+    @UiThread
+    private TouchConsumer createNewTouchProxyHandler() {
+        mCurrentShift.finishAnimation();
+        if (mLauncherTransitionController != null) {
+            mLauncherTransitionController.getAnimationPlayer().end();
+        }
+        // Hide the task view, if not already hidden
+        setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+
+        return OverviewTouchConsumer.newInstance(mActivityControlHelper, true);
+    }
+
     private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
         float velocityPxPerMs = endVelocity / 1000;
         long duration = MAX_SWIPE_DURATION;
@@ -737,6 +744,10 @@
                 }
             }
         }
+        if (goingToHome) {
+            mRecentsAnimationWrapper.enableTouchProxy();
+        }
+
         animateToProgress(startShift, endShift, duration, interpolator, goingToHome);
     }
 
@@ -831,7 +842,7 @@
         }
 
         mActivityInitListener.unregister();
-        mInputConsumer.unregisterInputConsumer();
+        mRecentsAnimationWrapper.unregisterInputConsumer();
         mTaskSnapshot = null;
     }
 
@@ -1059,7 +1070,7 @@
         mLongSwipeController = mActivityControlHelper.getLongSwipeController(
                 mActivity, mRunningTaskId);
         onLongSwipeDisplacementUpdated();
-        setTargetAlphaProvider(mLongSwipeController::getTargetAlpha);
+        setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
     }
 
     private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
@@ -1089,4 +1100,12 @@
     private void preloadAssistData() {
         RecentsModel.getInstance(mContext).preloadAssistData(mRunningTaskId, mAssistData);
     }
+
+    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
+        if (!(app.isNotInRecents
+                || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
+            return 0;
+        }
+        return expectedAlpha;
+    }
 }
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 4f03bf0..3223837 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -85,7 +85,8 @@
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
 
-    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE;
+    public static final int TYPE_ACCESSIBLE = TYPE_ALL
+            & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_QUICKSTEP_PREVIEW;
 
     protected boolean mIsOpen;