Merge "Sending notification and widget changes to a listner so that popups outside Launcher can also listen for changes" into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index b61268b..3664c97 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -30,8 +30,7 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
-
-import java.util.function.Supplier;
+import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
 /**
  * Helper class for batching input events
@@ -49,29 +48,25 @@
             ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_SCRUB_END =
             ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
-    private static final int ACTION_RESET =
+    private static final int ACTION_NEW_GESTURE =
             ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
             ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_QUICK_STEP =
             ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
-    private static final int ACTION_COMMAND =
-            ACTION_VIRTUAL | (7 << ACTION_POINTER_INDEX_SHIFT);
-    private static final int ACTION_SWITCH_CONSUMER =
-            ACTION_VIRTUAL | (8 << ACTION_POINTER_INDEX_SHIFT);
 
     private final InputEventDispatcher mDispatcher;
     private final InputEventReceiver mReceiver;
-
-    private final Object mConsumerParamsLock = new Object();
-    private Supplier[] mConsumerParams = new Supplier[2];
+    private final ConsumerFactory mConsumerFactory;
 
     private TouchConsumer mConsumer;
 
-    public MotionEventQueue(Looper looper, Choreographer choreographer) {
+    public MotionEventQueue(Looper looper, Choreographer choreographer,
+            ConsumerFactory consumerFactory) {
         Pair<InputEventDispatcher, InputEventReceiver> pair = InputChannelCompat.createPair(
                 "sysui-callbacks", looper, choreographer, this::onInputEvent);
 
+        mConsumerFactory = consumerFactory;
         mConsumer = TouchConsumer.NO_OP;
         mDispatcher = pair.first;
         mReceiver = pair.second;
@@ -93,9 +88,12 @@
                 case ACTION_QUICK_SCRUB_END:
                     mConsumer.onQuickScrubEnd();
                     break;
-                case ACTION_RESET:
-                    mConsumer.reset();
+                case ACTION_NEW_GESTURE: {
+                    boolean useSharedState = mConsumer.isActive();
+                    mConsumer.onConsumerAboutToBeSwitched();
+                    mConsumer = mConsumerFactory.newConsumer(event.getSource(), useSharedState);
                     break;
+                }
                 case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
                     mConsumer.onShowOverviewFromAltTab();
                     mConsumer.onQuickScrubStart();
@@ -103,16 +101,6 @@
                 case ACTION_QUICK_STEP:
                     mConsumer.onQuickStep(event);
                     break;
-                case ACTION_COMMAND:
-                    mConsumer.onCommand(event.getSource());
-                    break;
-                case ACTION_SWITCH_CONSUMER:
-                    synchronized (mConsumerParamsLock) {
-                        int index = event.getSource();
-                        mConsumer = (TouchConsumer) mConsumerParams[index].get();
-                        mConsumerParams[index] = null;
-                    }
-                    break;
                 default:
                     Log.e(TAG, "Invalid virtual event: " + event.getAction());
             }
@@ -156,41 +144,26 @@
         queue(event);
     }
 
-    public void reset() {
-        queueVirtualAction(ACTION_RESET, 0);
+    public void onNewGesture(@HitTarget int downHitTarget) {
+        queueVirtualAction(ACTION_NEW_GESTURE, downHitTarget);
     }
 
-    public void onCommand(int command) {
-        queueVirtualAction(ACTION_COMMAND, command);
-    }
-
-    public void switchConsumer(Supplier<TouchConsumer> consumer) {
-        int index = -1;
-        synchronized (mConsumerParamsLock) {
-            // Find a null index
-            for (int i = 0; i < mConsumerParams.length; i++) {
-                if (mConsumerParams[i] == null) {
-                    index = i;
-                    break;
-                }
-            }
-            if (index < 0) {
-                index = mConsumerParams.length;
-                final Supplier[] newValues = new Supplier[index + 1];
-                System.arraycopy(mConsumerParams, 0, newValues, 0, index);
-                mConsumerParams = newValues;
-            }
-            mConsumerParams[index] = consumer;
+    /**
+     * To be called by the consumer when it's no longer active.
+     */
+    public void onConsumerInactive(TouchConsumer caller) {
+        if (mConsumer == caller) {
+            mConsumer = TouchConsumer.NO_OP;
         }
-        queueVirtualAction(ACTION_SWITCH_CONSUMER, index);
-    }
-
-    public TouchConsumer getConsumer() {
-        return mConsumer;
     }
 
     public void dispose() {
         mDispatcher.dispose();
         mReceiver.dispose();
     }
+
+    public interface ConsumerFactory {
+
+        TouchConsumer newConsumer(@HitTarget int downHitTarget, boolean useSharedState);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 5801c10..84f16cb 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -37,7 +36,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.util.SparseArray;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -45,24 +43,23 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 /**
  * Touch consumer for handling events originating from an activity other than Launcher
@@ -73,16 +70,15 @@
     public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN";
     private static final String UP_EVT = "OtherActivityTouchConsumer.UP";
 
-    private final SparseArray<RecentsAnimationState> mAnimationStates = new SparseArray<>();
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
     private final ActivityControlHelper mActivityControlHelper;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final OverviewCallbacks mOverviewCallbacks;
     private final TaskOverlayFactory mTaskOverlayFactory;
     private final TouchInteractionLog mTouchInteractionLog;
     private final InputConsumerController mInputConsumer;
+    private final SwipeSharedState mSwipeSharedState;
 
     private final MotionEventQueue mEventQueue;
     private final MotionPauseDetector mMotionPauseDetector;
@@ -99,18 +95,13 @@
     private WindowTransformSwipeHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
-    private boolean mCanGestureBeContinued;
-
-    private boolean mIsGoingToLauncher;
-    private RecentsAnimationState mRecentsAnimationState;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
-            MainThreadExecutor mainThreadExecutor,
             @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
             TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
             TouchInteractionLog touchInteractionLog, MotionEventQueue eventQueue,
-            @Nullable RecentsAnimationState recentsAnimationStateToReuse) {
+            SwipeSharedState swipeSharedState) {
         super(base);
 
         mRunningTask = runningTaskInfo;
@@ -122,14 +113,13 @@
         mVelocityTracker = VelocityTracker.obtain();
 
         mActivityControlHelper = activityControl;
-        mMainThreadExecutor = mainThreadExecutor;
         mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
         mOverviewCallbacks = overviewCallbacks;
         mTaskOverlayFactory = taskOverlayFactory;
         mTouchInteractionLog = touchInteractionLog;
         mTouchInteractionLog.setTouchConsumer(this);
         mInputConsumer = inputConsumer;
-        mRecentsAnimationState = recentsAnimationStateToReuse;
+        mSwipeSharedState = swipeSharedState;
     }
 
     @Override
@@ -156,8 +146,8 @@
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                // If mRecentsAnimationState != null, we are continuing the previous gesture.
-                mPassedInitialSlop = mRecentsAnimationState != null;
+                // If active listener isn't null, we are continuing the previous gesture.
+                mPassedInitialSlop = mSwipeSharedState.getActiveListener() != null;
                 mQuickStepDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
 
                 // Start the window animation on down to give more time for launcher to draw if the
@@ -262,30 +252,23 @@
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         mTouchInteractionLog.startRecentsAnimation();
 
-        // Create the shared handler
-        boolean reuseOldAnimState = mRecentsAnimationState != null;
-        if (reuseOldAnimState) {
-            mRecentsAnimationState.changeParent(this);
-        } else {
-            mRecentsAnimationState = new RecentsAnimationState(this);
-        }
+        RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                mRecentsAnimationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                reuseOldAnimState, mInputConsumer, mTouchInteractionLog);
+                mRunningTask, this, touchTimeMs, mActivityControlHelper,
+                listenerSet != null, mInputConsumer, mTouchInteractionLog);
 
         // Preload the plan
         mRecentsModel.getTasks(null);
         mInteractionHandler = handler;
-        handler.setGestureEndCallback(mEventQueue::reset);
+        handler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
         handler.initWhenReady();
 
         TraceHelper.beginSection("RecentsController");
 
-        if (reuseOldAnimState) {
-            handler.onRecentsAnimationStart(mRecentsAnimationState.mController,
-                    mRecentsAnimationState.mTargets, mRecentsAnimationState.mHomeContentInsets,
-                    mRecentsAnimationState.mMinimizedHomeBounds);
+        if (listenerSet != null) {
+            listenerSet.addListener(handler);
+            mSwipeSharedState.applyActiveRecentsAnimationState(handler);
         } else {
             AssistDataReceiver assistDataReceiver = !mTaskOverlayFactory.needAssist() ? null :
                     new AssistDataReceiver() {
@@ -300,18 +283,13 @@
                         }
                     };
 
+            RecentsAnimationListenerSet newListenerSet =
+                    mSwipeSharedState.newRecentsAnimationListenerSet();
+            newListenerSet.addListener(handler);
             BackgroundExecutor.get().submit(
                     () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                            mHomeIntent, assistDataReceiver, mRecentsAnimationState, null, null));
-        }
-
-    }
-
-    @Override
-    public void onCommand(int command) {
-        RecentsAnimationState state = mAnimationStates.get(command);
-        if (state != null) {
-            state.execute();
+                            mHomeIntent, assistDataReceiver, newListenerSet,
+                            null, null));
         }
     }
 
@@ -335,7 +313,8 @@
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
-            reset();
+            onConsumerAboutToBeSwitched();
+            onInteractionGestureFinished();
 
             // Also clean up in case the system has handled the UP and canceled the animation before
             // we had a chance to start the recents animation. In such a case, we will not receive
@@ -347,15 +326,35 @@
     }
 
     @Override
-    public void reset() {
-        // Clean up the old interaction handler
+    public void onConsumerAboutToBeSwitched() {
+        Preconditions.assertUIThread();
         if (mInteractionHandler != null) {
-            final WindowTransformSwipeHandler handler = mInteractionHandler;
-            mInteractionHandler = null;
-            WindowTransformSwipeHandler.GestureEndTarget endTarget = handler.mGestureEndTarget;
-            mIsGoingToLauncher = endTarget != null && endTarget.isLauncher;
-            mCanGestureBeContinued = endTarget != null && endTarget.canBeContinued;
-            mMainThreadExecutor.execute(mCanGestureBeContinued ? handler::cancel : handler::reset);
+            // The consumer is being switched while we are active. Set up the shared state to be
+            // used by the next animation
+            removeListener();
+            GestureEndTarget endTarget = mInteractionHandler.mGestureEndTarget;
+            mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
+            mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
+            if (mSwipeSharedState.canGestureBeContinued) {
+                mInteractionHandler.cancel();
+            } else {
+                mInteractionHandler.reset();
+            }
+        }
+    }
+
+    @UiThread
+    private void onInteractionGestureFinished() {
+        Preconditions.assertUIThread();
+        removeListener();
+        mInteractionHandler = null;
+        mEventQueue.onConsumerInactive(this);
+    }
+
+    private void removeListener() {
+        RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
+        if (listenerSet != null) {
+            listenerSet.removeListener(mInteractionHandler);
         }
     }
 
@@ -419,81 +418,7 @@
     }
 
     @Override
-    public boolean forceToLauncherConsumer() {
-        return mIsGoingToLauncher;
-    }
-
-    @Override
-    public @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
-        return mCanGestureBeContinued ? mRecentsAnimationState : null;
-    }
-
-    @Override
-    public boolean deferNextEventToMainThread() {
-        // TODO: Consider also check if the eventQueue is using mainThread of not.
+    public boolean isActive() {
         return mInteractionHandler != null;
     }
-
-    public static class RecentsAnimationState implements RecentsAnimationListener {
-
-        private static final String ANIMATION_START_EVT = "RecentsAnimationState.onAnimationStart";
-        private final int id;
-
-        private OtherActivityTouchConsumer mParent;
-
-        private RecentsAnimationControllerCompat mController;
-        private RemoteAnimationTargetSet mTargets;
-        private Rect mHomeContentInsets;
-        private Rect mMinimizedHomeBounds;
-        private boolean mCancelled;
-
-        public RecentsAnimationState(OtherActivityTouchConsumer parent) {
-            mParent = parent;
-            id = mParent.mAnimationStates.size();
-            mParent.mAnimationStates.put(id, this);
-        }
-
-        @Override
-        public void onAnimationStart(
-                RecentsAnimationControllerCompat controller,
-                RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
-                Rect minimizedHomeBounds) {
-            RaceConditionTracker.onEvent(ANIMATION_START_EVT, ENTER);
-            mController = controller;
-            mTargets = new RemoteAnimationTargetSet(apps, MODE_CLOSING);
-            mHomeContentInsets = homeContentInsets;
-            mMinimizedHomeBounds = minimizedHomeBounds;
-            mParent.mEventQueue.onCommand(id);
-            RaceConditionTracker.onEvent(ANIMATION_START_EVT, EXIT);
-        }
-
-        @Override
-        public void onAnimationCanceled() {
-            mCancelled = true;
-            mParent.mEventQueue.onCommand(id);
-        }
-
-        public void execute() {
-            WindowTransformSwipeHandler handler = mParent.mInteractionHandler;
-            if (handler == null || handler.id != id) {
-                if (!mCancelled && mController != null) {
-                    TraceHelper.endSection("RecentsController", "Finishing no handler");
-                    mController.finish(false /* toHome */);
-                }
-            } else if (mCancelled) {
-                TraceHelper.endSection("RecentsController",
-                        "Cancelled: " + handler);
-                handler.onRecentsAnimationCanceled();
-            } else {
-                TraceHelper.partitionSection("RecentsController", "Received");
-                handler.onRecentsAnimationStart(mController, mTargets,
-                        mHomeContentInsets, mMinimizedHomeBounds);
-            }
-        }
-
-        public void changeParent(OtherActivityTouchConsumer newParent) {
-            mParent = newParent;
-            mParent.mAnimationStates.put(id, this);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/SwipeSharedState.java b/quickstep/src/com/android/quickstep/SwipeSharedState.java
new file mode 100644
index 0000000..15914ba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SwipeSharedState.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+
+/**
+ * Utility class used to store state information shared across multiple transitions.
+ */
+public class SwipeSharedState implements SwipeAnimationListener {
+
+    private RecentsAnimationListenerSet mRecentsAnimationListener;
+    private SwipeAnimationTargetSet mLastAnimationTarget;
+    private boolean mLastAnimationCancelled = false;
+
+    public boolean canGestureBeContinued;
+    public boolean goingToLauncher;
+
+    @Override
+    public final void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        mLastAnimationTarget = targetSet;
+        mLastAnimationCancelled = false;
+    }
+
+    @Override
+    public final void onRecentsAnimationCanceled() {
+        mLastAnimationTarget = null;
+        mLastAnimationCancelled = true;
+    }
+
+    private void clearListenerState() {
+        if (mRecentsAnimationListener != null) {
+            mRecentsAnimationListener.removeListener(this);
+        }
+        mRecentsAnimationListener = null;
+        mLastAnimationTarget = null;
+        mLastAnimationCancelled = false;
+    }
+
+    public RecentsAnimationListenerSet newRecentsAnimationListenerSet() {
+        Preconditions.assertUIThread();
+        clearListenerState();
+        mRecentsAnimationListener = new RecentsAnimationListenerSet();
+        mRecentsAnimationListener.addListener(this);
+        return mRecentsAnimationListener;
+    }
+
+    public RecentsAnimationListenerSet getActiveListener() {
+        return mRecentsAnimationListener;
+    }
+
+    public void applyActiveRecentsAnimationState(SwipeAnimationListener listener) {
+        if (mLastAnimationTarget != null) {
+            listener.onRecentsAnimationStart(mLastAnimationTarget);
+        } else if (mLastAnimationCancelled) {
+            listener.onRecentsAnimationCanceled();
+        }
+    }
+
+    public void clearAllState() {
+        clearListenerState();
+        canGestureBeContinued = false;
+        goingToLauncher = false;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index ef37595..026f715 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -20,9 +20,6 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
-
-import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -43,8 +40,6 @@
     int INTERACTION_NORMAL = 0;
     int INTERACTION_QUICK_SCRUB = 1;
 
-    default void reset() { }
-
     default void onQuickScrubStart() { }
 
     default void onQuickScrubEnd() { }
@@ -53,22 +48,14 @@
 
     default void onQuickStep(MotionEvent ev) { }
 
-    default void onCommand(int command) { }
+    default void onShowOverviewFromAltTab() {}
 
-    default boolean deferNextEventToMainThread() {
-        return false;
-    }
-
-    default boolean forceToLauncherConsumer() {
+    default boolean isActive() {
         return false;
     }
 
     /**
-     * When continuing a gesture, return the current non-null animation state that hasn't finished.
+     * Called by the event queue when the consumer is about to be switched to a new consumer.
      */
-    default @Nullable RecentsAnimationState getRecentsAnimationStateToReuse() {
-        return null;
-    }
-
-    default void onShowOverviewFromAltTab() {}
+    default void onConsumerAboutToBeSwitched() { }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 0f52deb..97e797b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -44,7 +44,6 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.OtherActivityTouchConsumer.RecentsAnimationState;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -61,6 +60,8 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
+    public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
+
     private static final SparseArray<String> sMotionEventNames;
 
     static {
@@ -81,7 +82,7 @@
             mTouchInteractionLog.prepareForNewGesture();
 
             TraceHelper.beginSection("SysUiBinder");
-            setupTouchConsumer(downHitTarget);
+            mEventQueue.onNewGesture(downHitTarget);
             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
         }
 
@@ -135,7 +136,7 @@
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
             if (triggeredFromAltTab) {
-                setupTouchConsumer(HIT_TARGET_NONE);
+                mEventQueue.onNewGesture(HIT_TARGET_NONE);
                 mEventQueue.onOverviewShownFromAltTab();
             } else {
                 mOverviewCommandHelper.onOverviewShown();
@@ -172,7 +173,6 @@
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
     private MotionEventQueue mEventQueue;
-    private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
     private OverviewComponentObserver mOverviewComponentObserver;
@@ -181,20 +181,22 @@
     private TaskOverlayFactory mTaskOverlayFactory;
     private TouchInteractionLog mTouchInteractionLog;
     private InputConsumerController mInputConsumer;
+    private SwipeSharedState mSwipeSharedState;
 
     @Override
     public void onCreate() {
         super.onCreate();
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
-        mMainThreadExecutor = new MainThreadExecutor();
         mOverviewComponentObserver = new OverviewComponentObserver(this);
         mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
-        mEventQueue = new MotionEventQueue(Looper.myLooper(), Choreographer.getInstance());
+        mEventQueue = new MotionEventQueue(Looper.myLooper(), Choreographer.getInstance(),
+                this::newConsumer);
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
         mTouchInteractionLog = new TouchInteractionLog();
+        mSwipeSharedState = new SwipeSharedState();
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
 
@@ -219,27 +221,15 @@
         return mMyBinder;
     }
 
-    private void setupTouchConsumer(@HitTarget int downHitTarget) {
-        mEventQueue.reset();
-        TouchConsumer oldConsumer = mEventQueue.getConsumer();
-        if (oldConsumer.deferNextEventToMainThread()) {
-            mEventQueue.switchConsumer(() -> getCurrentTouchConsumer(downHitTarget,
-                    oldConsumer.forceToLauncherConsumer(),
-                    oldConsumer.getRecentsAnimationStateToReuse()));
-
-        } else {
-            TouchConsumer consumer = getCurrentTouchConsumer(downHitTarget, false, null);
-            mEventQueue.switchConsumer(() -> consumer);
-        }
-    }
-
-    private TouchConsumer getCurrentTouchConsumer(@HitTarget int downHitTarget,
-            boolean forceToLauncher, RecentsAnimationState recentsAnimationStateToReuse) {
+    private TouchConsumer newConsumer(@HitTarget int downHitTarget, boolean useSharedState) {
         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+        if (!useSharedState) {
+            mSwipeSharedState.clearAllState();
+        }
 
-        if (runningTaskInfo == null && !forceToLauncher) {
+        if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
             return TouchConsumer.NO_OP;
-        } else if (forceToLauncher ||
+        } else if (mSwipeSharedState.goingToLauncher ||
                 mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
             return OverviewTouchConsumer.newInstance(
                     mOverviewComponentObserver.getActivityControlHelper(), false,
@@ -252,10 +242,10 @@
         } else {
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
                     mOverviewComponentObserver.getOverviewIntent(),
-                    mOverviewComponentObserver.getActivityControlHelper(), mMainThreadExecutor,
+                    mOverviewComponentObserver.getActivityControlHelper(),
                     downHitTarget, mOverviewCallbacks,
                     mTaskOverlayFactory, mInputConsumer, mTouchInteractionLog, mEventQueue,
-                    recentsAnimationStateToReuse);
+                    mSwipeSharedState);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 0e9ddee..bc4e094 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -34,6 +34,7 @@
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_FROM_APP_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
+import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
@@ -97,6 +98,8 @@
 import com.android.quickstep.TouchInteractionService.OverviewTouchConsumer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
 import com.android.quickstep.util.TransformedRect;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -113,7 +116,8 @@
 import java.util.function.BiFunction;
 
 @TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
+public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
+        implements SwipeAnimationListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     // Launcher UI related states
@@ -248,14 +252,8 @@
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
 
-    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+    private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
 
-    // An increasing identifier per single instance of OtherActivityTouchConsumer. Generally one
-    // instance of OtherActivityTouchConsumer will only have one swipe handle, but sometimes we can
-    // end up with multiple handlers if we get recents command in the middle of a swipe gesture.
-    // This is used to match the corresponding activity manager callbacks in
-    // OtherActivityTouchConsumer
-    public final int id;
     private final Context mContext;
     private final ActivityControlHelper<T> mActivityControlHelper;
     private final ActivityInitListener mActivityInitListener;
@@ -296,10 +294,9 @@
 
     private Bundle mAssistData;
 
-    WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
+    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
-        this.id = id;
         mContext = context;
         mRunningTaskInfo = runningTaskInfo;
         mRunningTaskId = runningTaskInfo.id;
@@ -747,19 +744,18 @@
                         ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
     }
 
-    @UiThread
-    public void onRecentsAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetSet targets, Rect homeContentInsets, Rect minimizedHomeBounds) {
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         final Rect overviewStackBounds;
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
 
-        if (minimizedHomeBounds != null && runningTaskTarget != null) {
+        if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
             overviewStackBounds = mActivityControlHelper
-                    .getOverviewWindowBounds(minimizedHomeBounds, runningTaskTarget);
-            dp = dp.getMultiWindowProfile(mContext,
-                    new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
-            dp.updateInsets(homeContentInsets);
+                    .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+            dp = dp.getMultiWindowProfile(mContext, new Point(
+                    targetSet.minimizedHomeBounds.width(), targetSet.minimizedHomeBounds.height()));
+            dp.updateInsets(targetSet.homeContentInsets);
         } else {
             if (mActivity != null) {
                 int loc[] = new int[2];
@@ -772,7 +768,7 @@
             }
             // If we are not in multi-window mode, home insets should be same as system insets.
             dp = dp.copy(mContext);
-            dp.updateInsets(homeContentInsets);
+            dp.updateInsets(targetSet.homeContentInsets);
         }
         dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
 
@@ -782,14 +778,14 @@
         mClipAnimationHelper.prepareAnimation(false /* isOpening */);
         initTransitionEndpoints(dp);
 
-        mRecentsAnimationWrapper.setController(controller, targets);
-        mTouchInteractionLog.startRecentsAnimationCallback(targets.apps.length);
+        mRecentsAnimationWrapper.setController(targetSet.controller, targetSet);
+        mTouchInteractionLog.startRecentsAnimationCallback(targetSet.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
     }
 
-    @UiThread
+    @Override
     public void onRecentsAnimationCanceled() {
         mRecentsAnimationWrapper.setController(null, null);
         mActivityInitListener.unregister();
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
new file mode 100644
index 0000000..3e49568
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Set;
+
+import androidx.annotation.UiThread;
+
+/**
+ * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
+ * on the main thread
+ */
+public class RecentsAnimationListenerSet implements RecentsAnimationListener {
+
+    private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
+
+    @UiThread
+    public void addListener(SwipeAnimationListener listener) {
+        Preconditions.assertUIThread();
+        mListeners.add(listener);
+    }
+
+    @UiThread
+    public void removeListener(SwipeAnimationListener listener) {
+        Preconditions.assertUIThread();
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public final void onAnimationStart(RecentsAnimationControllerCompat controller,
+            RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
+            Rect minimizedHomeBounds) {
+        SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
+                homeContentInsets, minimizedHomeBounds);
+        Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+            for (SwipeAnimationListener listener : mListeners) {
+                listener.onRecentsAnimationStart(targetSet);
+            }
+        });
+    }
+
+    @Override
+    public final void onAnimationCanceled() {
+        Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+            for (SwipeAnimationListener listener : mListeners) {
+                listener.onRecentsAnimationCanceled();
+            }
+        });
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
new file mode 100644
index 0000000..4f02acf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Rect;
+
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Extension of {@link RemoteAnimationTargetSet} with additional information about swipe
+ * up animation
+ */
+public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
+
+    public final RecentsAnimationControllerCompat controller;
+    public final Rect homeContentInsets;
+    public final Rect minimizedHomeBounds;
+
+    public SwipeAnimationTargetSet(RecentsAnimationControllerCompat controller,
+            RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
+            Rect minimizedHomeBounds) {
+        super(targets, MODE_CLOSING);
+        this.controller = controller;
+        this.homeContentInsets = homeContentInsets;
+        this.minimizedHomeBounds = minimizedHomeBounds;
+    }
+
+
+    public interface SwipeAnimationListener {
+
+        void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
+
+        void onRecentsAnimationCanceled();
+    }
+}