Merge "Removing static listeners from PackageInstallerCompat" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 057b48b..4994526 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.IconRecentsView;
 
@@ -85,13 +86,13 @@
     @Override
     public ActivityInitListener createActivityInitListener(
             BiPredicate<RecentsActivity, Boolean> onInitListener) {
-        return new RecentsActivityTracker(onInitListener);
+        return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
     }
 
     @Nullable
     @Override
     public RecentsActivity getCreatedActivity() {
-        return RecentsActivityTracker.getCurrentActivity();
+        return RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index b0d9cda..8993b67 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -55,7 +55,7 @@
     }
 
     @Override
-    public ActivityInitListener createActivityInitListener(
+    public LauncherInitListener createActivityInitListener(
             BiPredicate<Launcher, Boolean> onInitListener) {
         return new LauncherInitListener(onInitListener);
     }
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 216972c..9c409cc 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -27,8 +27,8 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.AppToOverviewAnimationProvider.AppToOverviewAnimationListener;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.views.IconRecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
@@ -104,7 +104,7 @@
 
         private final long mToggleClickedTime = SystemClock.uptimeMillis();
         private boolean mUserEventLogged;
-        private ActivityInitListener mListener;
+        private ActivityInitListener<T> mListener;
 
         public RecentsActivityCommand() {
             mHelper = mOverviewComponentObserver.getActivityControlHelper();
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index 3aa783a..e40a9c2 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,7 +17,6 @@
 
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageInfo;
 import android.os.LocaleList;
 import android.os.UserHandle;
 
@@ -44,13 +43,6 @@
     }
 
     /**
-     * Returns the timestamp the entry was last updated in cache.
-     */
-    default long getLastUpdatedTime(T object, PackageInfo info) {
-        return info.lastUpdateTime;
-    }
-
-    /**
      * Returns true the object should be added to mem cache; otherwise returns false.
      */
     default boolean addToMemCache() {
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index bcdbce5..8224966 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,8 +171,7 @@
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
                 T app = componentMap.remove(component);
-                if (version == info.versionCode
-                        && updateTime == cachingLogic.getLastUpdatedTime(app, info)
+                if (version == info.versionCode && updateTime == info.lastUpdateTime
                         && TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.getIconSystemState(info.packageName))) {
 
@@ -232,6 +231,7 @@
         }
     }
 
+
     /**
      * A runnable that updates invalid icons and adds missing icons in the DB for the provided
      * LauncherActivityInfo list. Items are updated/added one at a time, so that the
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
index c5c4add..76050d5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
@@ -27,7 +27,7 @@
     }
 
     @Override
-    protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+    public boolean init(Launcher launcher, boolean alreadyOnHome) {
         PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW);
         return super.init(launcher, alreadyOnHome);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 7196f7c..d524b5d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -57,10 +57,10 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 import com.android.quickstep.util.RectFSpringAnim;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 8c5a788..33180a9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.RecentsView;
@@ -177,13 +178,13 @@
     @Override
     public ActivityInitListener createActivityInitListener(
             BiPredicate<RecentsActivity, Boolean> onInitListener) {
-        return new RecentsActivityTracker(onInitListener);
+        return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
     }
 
     @Nullable
     @Override
     public RecentsActivity getCreatedActivity() {
-        return RecentsActivityTracker.getCurrentActivity();
+        return BaseRecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index a4fdc8a..e7005a6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -50,7 +50,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListenerEx;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
@@ -61,6 +60,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.LauncherRecentsView;
@@ -109,6 +109,14 @@
         // Ensure recents is at the correct position for NORMAL state. For example, when we detach
         // recents, we assume the first task is invisible, making translation off by one task.
         activity.getStateManager().reapplyState();
+        setLauncherHideBackArrow(false);
+    }
+
+    private void setLauncherHideBackArrow(boolean hideBackArrow) {
+        Launcher launcher = getCreatedActivity();
+        if (launcher != null) {
+            launcher.getRootView().setForceHideBackArrow(hideBackArrow);
+        }
     }
 
     @Override
@@ -139,7 +147,7 @@
                 ? FloatingIconView.getFloatingIconView(activity, workspaceView,
                         true /* hideOriginal */, iconLocation, false /* isOpening */)
                 : null;
-
+        setLauncherHideBackArrow(true);
         return new HomeAnimationFactory() {
             @Nullable
             @Override
@@ -396,11 +404,7 @@
     @Nullable
     @Override
     public Launcher getCreatedActivity() {
-        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app == null) {
-            return null;
-        }
-        return (Launcher) app.getModel().getCallback();
+        return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     @Nullable
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 6a06de9..6df9ce5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -25,14 +25,12 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 9a01c54..c4f375a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -39,12 +39,6 @@
                 return response;
             }
 
-            case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        TouchInteractionService.isInitialized());
-                return response;
-            }
-
             case TestProtocol.REQUEST_HOTSEAT_TOP: {
                 if (mLauncher == null) return null;
 
@@ -91,4 +85,9 @@
             observer.onDestroy();
         }
     }
+
+    @Override
+    protected boolean isLauncherInitialized() {
+        return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index bebd45d..4a9aa2d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -38,11 +38,11 @@
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
 import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ObjectWrapper;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index e51ba63..c4d3fa0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -19,6 +19,8 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import android.os.SystemClock;
+import android.util.Log;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -38,6 +40,8 @@
  */
 public class RecentsAnimationWrapper {
 
+    private static final String TAG = "RecentsAnimationWrapper";
+
     // A list of callbacks to run when we receive the recents animation target. There are different
     // than the state callbacks as these run on the current worker thread.
     private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
@@ -125,6 +129,7 @@
             boolean sendUserLeaveHint) {
         SwipeAnimationTargetSet controller = targetSet;
         targetSet = null;
+        disableInputProxy();
         if (controller != null) {
             controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
         }
@@ -153,6 +158,16 @@
         mInputConsumerController.setInputListener(this::onInputConsumerEvent);
     }
 
+    private void disableInputProxy() {
+        if (mInputConsumer != null && mTouchInProgress) {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
+            mInputConsumer.onMotionEvent(dummyCancel);
+            dummyCancel.recycle();
+        }
+        mInputConsumerController.setInputListener(null);
+    }
+
     private boolean onInputConsumerEvent(InputEvent ev) {
         if (ev instanceof MotionEvent) {
             onInputConsumerMotionEvent((MotionEvent) ev);
@@ -168,6 +183,18 @@
 
     private boolean onInputConsumerMotionEvent(MotionEvent ev) {
         int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
             if (mInputConsumer == null) {
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
index e0ff8af..6275d21 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.util.ObjectWrapper;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
@@ -47,7 +48,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.fallback.FallbackRecentsView;
-import com.android.quickstep.util.ObjectWrapper;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.views.TaskView;
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 272d117..663b125 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -19,29 +19,25 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 
 import java.util.function.BiPredicate;
 
 @TargetApi(Build.VERSION_CODES.P)
-public class LauncherInitListener extends InternalStateHandler implements ActivityInitListener {
-
-    private final BiPredicate<Launcher, Boolean> mOnInitListener;
+public class LauncherInitListener extends ActivityInitListener<Launcher> {
 
     private RemoteAnimationProvider mRemoteAnimationProvider;
 
     public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
-        mOnInitListener = onInitListener;
+        super(onInitListener, Launcher.ACTIVITY_TRACKER);
     }
 
     @Override
-    protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+    public boolean init(Launcher launcher, boolean alreadyOnHome) {
         if (mRemoteAnimationProvider != null) {
             QuickstepAppTransitionManagerImpl appTransitionManager =
                     (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
@@ -63,28 +59,19 @@
             }, cancellationSignal);
         }
         launcher.deferOverlayCallbacksUntilNextResumeOrStop();
-        return mOnInitListener.test(launcher, alreadyOnHome);
-    }
-
-    @Override
-    public void register() {
-        initWhenReady();
+        return super.init(launcher, alreadyOnHome);
     }
 
     @Override
     public void unregister() {
         mRemoteAnimationProvider = null;
-        clearReference();
+        super.unregister();
     }
 
     @Override
     public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
             Context context, Handler handler, long duration) {
         mRemoteAnimationProvider = animProvider;
-
-        register();
-
-        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
-        context.startActivity(addToIntent(new Intent((intent))), options);
+        super.registerAndStartActivity(intent, animProvider, context, handler, duration);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 609fb26..2d4b136 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -17,12 +17,10 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Build;
-import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -34,7 +32,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -99,16 +97,6 @@
 
     default void closeOverlay() { }
 
-    interface ActivityInitListener {
-
-        void register();
-
-        void unregister();
-
-        void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
-                Context context, Handler handler, long duration);
-    }
-
     interface AnimationFactory {
 
         enum ShelfAnimState {
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index 1ac7ed4..71833ad 100644
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 
@@ -43,6 +44,7 @@
  */
 public abstract class BaseRecentsActivity extends BaseDraggingActivity {
 
+    public static ActivityTracker<BaseRecentsActivity> ACTIVITY_TRACKER = new ActivityTracker<>();
     private Configuration mOldConfig;
 
     @Override
@@ -55,7 +57,7 @@
 
         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
-        RecentsActivityTracker.onRecentsActivityCreate(this);
+        ACTIVITY_TRACKER.handleCreate((RecentsActivity) this);
     }
 
     /**
@@ -132,13 +134,13 @@
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
-        RecentsActivityTracker.onRecentsActivityNewIntent(this);
+        ACTIVITY_TRACKER.handleNewIntent(this, intent);
     }
 
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        RecentsActivityTracker.onRecentsActivityDestroy(this);
+        ACTIVITY_TRACKER.onActivityDestroyed(this);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
deleted file mode 100644
index 4d1d9ef..0000000
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationProvider;
-
-import java.lang.ref.WeakReference;
-import java.util.function.BiPredicate;
-
-/**
- * Utility class to track create/destroy for some {@link BaseRecentsActivity}.
- */
-@TargetApi(Build.VERSION_CODES.P)
-public class RecentsActivityTracker<T extends BaseRecentsActivity> implements ActivityInitListener {
-
-    private static WeakReference<BaseRecentsActivity> sCurrentActivity =
-            new WeakReference<>(null);
-    private static final Scheduler sScheduler = new Scheduler();
-
-    private final BiPredicate<T, Boolean> mOnInitListener;
-
-    public RecentsActivityTracker(BiPredicate<T, Boolean> onInitListener) {
-        mOnInitListener = onInitListener;
-    }
-
-    @Override
-    public void register() {
-        sScheduler.schedule(this);
-    }
-
-    @Override
-    public void unregister() {
-        sScheduler.clearReference(this);
-    }
-
-    private boolean init(T activity, boolean visible) {
-        return mOnInitListener.test(activity, visible);
-    }
-
-    public static <T extends BaseRecentsActivity> T getCurrentActivity() {
-        return (T) sCurrentActivity.get();
-    }
-
-    @Override
-    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
-            Context context, Handler handler, long duration) {
-        register();
-
-        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
-        context.startActivity(intent, options);
-    }
-
-    public static void onRecentsActivityCreate(BaseRecentsActivity activity) {
-        sCurrentActivity = new WeakReference<>(activity);
-        sScheduler.initIfPending(activity, false);
-    }
-
-
-    public static void onRecentsActivityNewIntent(BaseRecentsActivity activity) {
-        sScheduler.initIfPending(activity, activity.isStarted());
-    }
-
-    public static void onRecentsActivityDestroy(BaseRecentsActivity activity) {
-        if (sCurrentActivity.get() == activity) {
-            sCurrentActivity.clear();
-        }
-    }
-
-
-    private static class Scheduler implements Runnable {
-
-        private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
-
-        public synchronized void schedule(RecentsActivityTracker tracker) {
-            mPendingTracker = new WeakReference<>(tracker);
-            MAIN_EXECUTOR.execute(this);
-        }
-
-        @Override
-        public void run() {
-            BaseRecentsActivity activity = sCurrentActivity.get();
-            if (activity != null) {
-                initIfPending(activity, activity.isStarted());
-            }
-        }
-
-        public synchronized boolean initIfPending(BaseRecentsActivity activity,
-                boolean alreadyOnHome) {
-            RecentsActivityTracker tracker = mPendingTracker.get();
-            if (tracker != null) {
-                if (!tracker.init(activity, alreadyOnHome)) {
-                    mPendingTracker.clear();
-                }
-                return true;
-            }
-            return false;
-        }
-
-        public synchronized boolean clearReference(RecentsActivityTracker tracker) {
-            if (mPendingTracker.get() == tracker) {
-                mPendingTracker.clear();
-                return true;
-            }
-            return false;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
new file mode 100644
index 0000000..fe37d60
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -0,0 +1,60 @@
+/*
+ * 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 android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
+
+import java.util.function.BiPredicate;
+
+public class ActivityInitListener<T extends BaseActivity> implements SchedulerCallback<T> {
+
+    private final BiPredicate<T, Boolean> mOnInitListener;
+    private final ActivityTracker<T> mActivityTracker;
+
+    public ActivityInitListener(BiPredicate<T, Boolean> onInitListener,
+            ActivityTracker<T> tracker) {
+        mOnInitListener = onInitListener;
+        mActivityTracker = tracker;
+    }
+
+    @Override
+    public boolean init(T activity, boolean alreadyOnHome) {
+        return mOnInitListener.test(activity, alreadyOnHome);
+    }
+
+    public void register() {
+        mActivityTracker.schedule(this);
+    }
+
+    public void unregister() {
+        mActivityTracker.clearReference(this);
+    }
+
+    public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+            Context context, Handler handler, long duration) {
+        register();
+
+        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
+        context.startActivity(addToIntent(new Intent((intent))), options);
+    }
+}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4f8b20e..9326e95 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -113,7 +113,6 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.qsb.QsbContainerView;
-import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.DejankBinderTracker;
@@ -124,6 +123,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ActivityResultInfo;
+import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -176,6 +176,9 @@
         Callbacks, LauncherProviderChangeListener, UserEventDelegate,
         InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
     public static final String TAG = "Launcher";
+
+    public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
+
     static final boolean LOGD = false;
 
     static final boolean DEBUG_STRICT_MODE = false;
@@ -359,7 +362,7 @@
 
         mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
 
-        boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
+        boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
         if (internalStateHandled) {
             if (savedInstanceState != null) {
                 // InternalStateHandler has already set the appropriate state.
@@ -1444,8 +1447,7 @@
         boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
                 && AbstractFloatingView.getTopOpenView(this) == null;
         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
-        boolean internalStateHandled = InternalStateHandler
-                .handleNewIntent(this, intent, isStarted());
+        boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this, intent);
 
         if (isActionMain) {
             if (!internalStateHandled) {
@@ -1535,6 +1537,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
+        ACTIVITY_TRACKER.onActivityDestroyed(this);
 
         unregisterReceiver(mScreenOffReceiver);
         mWorkspace.removeFolderListeners();
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index f964b8d..ce1795a 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -39,6 +39,8 @@
     private WindowStateListener mWindowStateListener;
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDisallowBackGesture;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private boolean mForceHideBackArrow;
 
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -176,12 +178,18 @@
     }
 
     @TargetApi(Build.VERSION_CODES.Q)
+    public void setForceHideBackArrow(boolean forceHideBackArrow) {
+        this.mForceHideBackArrow = forceHideBackArrow;
+        setDisallowBackGesture(mDisallowBackGesture);
+    }
+
+    @TargetApi(Build.VERSION_CODES.Q)
     public void setDisallowBackGesture(boolean disallowBackGesture) {
         if (!Utilities.ATLEAST_Q) {
             return;
         }
         mDisallowBackGesture = disallowBackGesture;
-        setSystemGestureExclusionRects(mDisallowBackGesture
+        setSystemGestureExclusionRects((mForceHideBackArrow || mDisallowBackGesture)
                 ? SYSTEM_GESTURE_EXCLUSION_RECT
                 : Collections.emptyList());
     }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 8c59626..dc2f7bd 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -31,12 +31,10 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.states.InternalStateHandler;
 
 /**
  * Abstract base class of floating view responsible for showing discovery bounce animation
@@ -181,7 +179,7 @@
         if (withDelay) {
             new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS);
             return;
-        } else if (InternalStateHandler.hasPending()
+        } else if (Launcher.ACTIVITY_TRACKER.hasPending()
                 || AbstractFloatingView.getTopOpenView(launcher) != null) {
             // TODO: Move these checks to the top and call this method after invalidate handler.
             return;
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 9fb1090..b8981b6 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -176,7 +177,7 @@
                         .setPackage(getPackageName())
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
 
-        listener.initWhenReady();
+        Launcher.ACTIVITY_TRACKER.schedule(listener);
         startActivity(homeIntent,
                 ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
         mFinishOnPause = true;
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 1b08723..75693c6 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -36,8 +36,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
 import com.android.launcher3.widget.PendingItemDragHelper;
 
 import java.util.UUID;
@@ -45,8 +44,8 @@
 /**
  * {@link DragSource} for handling drop from a different window.
  */
-public abstract class BaseItemDragListener extends InternalStateHandler implements
-        View.OnDragListener, DragSource, DragOptions.PreDragCondition {
+public abstract class BaseItemDragListener implements View.OnDragListener, DragSource,
+        DragOptions.PreDragCondition, SchedulerCallback<Launcher> {
 
     private static final String TAG = "BaseItemDragListener";
 
@@ -165,7 +164,7 @@
     }
 
     protected void postCleanup() {
-        clearReference();
+        Launcher.ACTIVITY_TRACKER.clearReference(this);
         if (mLauncher != null) {
             // Remove any drag params from the launcher intent since the drag operation is complete.
             Intent newIntent = new Intent(mLauncher.getIntent());
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index c0a095d..b1818a0 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -27,7 +27,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Process;
@@ -51,7 +50,6 @@
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
@@ -67,7 +65,6 @@
 
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
-    private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
 
     private final LauncherAppsCompat mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -81,7 +78,6 @@
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
-        mShortcutCachingLogic = new ShortcutCachingLogic();
         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -180,14 +176,6 @@
     }
 
     /**
-     * Fill in info with the icon and label for deep shortcut.
-     */
-    public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
-        return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
-                () -> info, mShortcutCachingLogic, false, false);
-    }
-
-    /**
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index c6949af..adc92c4 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.Process;
 
 import androidx.annotation.Nullable;
@@ -32,8 +33,8 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.icons.cache.BaseIconCache;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.Themes;
 
 import java.util.function.Supplier;
@@ -125,12 +126,13 @@
 
     public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
             boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
+                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-        BaseIconCache.CacheEntry entry = cache.getDeepShortcutTitleAndIcon(shortcutInfo);
 
         final Bitmap unbadgedBitmap;
-        if (entry.icon != null) {
-            unbadgedBitmap = entry.icon;
+        if (unbadgedDrawable != null) {
+            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
         } else {
             if (fallbackIconProvider != null) {
                 // Fallback icons are already badged and with appropriate shadow
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
deleted file mode 100644
index 5d696fd..0000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.launcher3.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutKey;
-
-/**
- * Caching logic for shortcuts.
- */
-public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
-
-    @Override
-    public ComponentName getComponent(ShortcutInfo info) {
-        return ShortcutKey.fromInfo(info).componentName;
-    }
-
-    @Override
-    public UserHandle getUser(ShortcutInfo info) {
-        return info.getUserHandle();
-    }
-
-    @Override
-    public CharSequence getLabel(ShortcutInfo info) {
-        return info.getShortLabel();
-    }
-
-    @Override
-    public void loadIcon(Context context, ShortcutInfo info, BitmapInfo target) {
-        LauncherIcons li = LauncherIcons.obtain(context);
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
-                .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
-        if (unbadgedDrawable != null) {
-            target.icon = li.createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
-        }
-        li.recycle();
-    }
-
-    @Override
-    public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
-        if (shortcutInfo == null) return info.lastUpdateTime;
-        return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
-    }
-
-    @Override
-    public boolean addToMemCache() {
-        return false;
-    }
-}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4c88eb7..97cf846 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -62,7 +62,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherActivityCachingLogic;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
@@ -173,8 +172,7 @@
         TraceHelper.beginSection(TAG);
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
-            List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts);
+            loadWorkspace();
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
@@ -193,23 +191,18 @@
             TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
             List<LauncherActivityInfo> allActivityList = loadAllApps();
 
-            TraceHelper.partitionSection(TAG, "step 2.2: binding all apps");
+            TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
             verifyNotStopped();
             mResults.bindAllApps();
 
             verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 2.3: save app icons in icon cache");
+            TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
             updateHandler.updateIcons(allActivityList,
                     LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                     mApp.getModel()::onPackageIconsUpdated);
 
-            verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 2.4: save shortcuts in icon cache");
-            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
-                    mApp.getModel()::onPackageIconsUpdated);
-
             // Take a break
             TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
             waitForIdle();
@@ -217,17 +210,12 @@
 
             // third step
             TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
-            List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
+            loadDeepShortcuts();
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
             mResults.bindDeepShortcuts();
 
-            verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 3.3: save deep shortcuts in icon cache");
-            updateHandler.updateIcons(allDeepShortcuts,
-                    new ShortcutCachingLogic(), (pkgs, user) -> { });
-
             // Take a break
             TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
             waitForIdle();
@@ -242,6 +230,7 @@
             mResults.bindWidgets();
 
             verifyNotStopped();
+
             TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache");
             updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
                     mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
@@ -263,7 +252,7 @@
         this.notify();
     }
 
-    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+    private void loadWorkspace() {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -518,7 +507,6 @@
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                                     }
                                     intent = info.intent;
-                                    allDeepShortcuts.add(pinnedShortcut);
                                 } else {
                                     // Create a shortcut info in disabled mode for now.
                                     info = c.loadSimpleWorkspaceItem();
@@ -867,8 +855,7 @@
         return allActivityList;
     }
 
-    private List<ShortcutInfo> loadDeepShortcuts() {
-        List<ShortcutInfo> allShortcuts = new ArrayList<>();
+    private void loadDeepShortcuts() {
         mBgDataModel.deepShortcutMap.clear();
         mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
         if (mBgDataModel.hasShortcutHostPermission) {
@@ -876,12 +863,10 @@
                 if (mUserManager.isUserUnlocked(user)) {
                     List<ShortcutInfo> shortcuts =
                             mShortcutManager.queryForAllShortcuts(user);
-                    allShortcuts.addAll(shortcuts);
                     mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
                 }
             }
         }
-        return allShortcuts;
     }
 
     public static boolean isValidProvider(AppWidgetProviderInfo provider) {
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 32410a6..717a7e9 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Color;
@@ -33,8 +35,6 @@
 
 import java.util.List;
 
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
-
 /**
  * Utility class to manage notification UI
  */
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
deleted file mode 100644
index a23cd6d..0000000
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.states;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Utility class to sending state handling logic to Launcher from within the same process.
- *
- * Extending {@link Binder} ensures that the platform maintains a single instance of each object
- * which allows this object to safely navigate the system process.
- */
-public abstract class InternalStateHandler extends Binder {
-
-    public static final String EXTRA_STATE_HANDLER = "launcher.state_handler";
-
-    private static final Scheduler sScheduler = new Scheduler();
-
-    /**
-     * Initializes the handler when the launcher is ready.
-     * @return true if the handler wants to stay alive.
-     */
-    protected abstract boolean init(Launcher launcher, boolean alreadyOnHome);
-
-    public final Intent addToIntent(Intent intent) {
-        Bundle extras = new Bundle();
-        extras.putBinder(EXTRA_STATE_HANDLER, this);
-        intent.putExtras(extras);
-        return intent;
-    }
-
-    public final void initWhenReady() {
-        sScheduler.schedule(this);
-    }
-
-    public boolean clearReference() {
-        return sScheduler.clearReference(this);
-    }
-
-    public static boolean hasPending() {
-        return sScheduler.hasPending();
-    }
-
-    public static boolean handleCreate(Launcher launcher, Intent intent) {
-        return handleIntent(launcher, intent, false, false);
-    }
-
-    public static boolean handleNewIntent(Launcher launcher, Intent intent, boolean alreadyOnHome) {
-        return handleIntent(launcher, intent, alreadyOnHome, true);
-    }
-
-    private static boolean handleIntent(
-            Launcher launcher, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
-        boolean result = false;
-        if (intent != null && intent.getExtras() != null) {
-            IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER);
-            if (stateBinder instanceof InternalStateHandler) {
-                InternalStateHandler handler = (InternalStateHandler) stateBinder;
-                if (!handler.init(launcher, alreadyOnHome)) {
-                    intent.getExtras().remove(EXTRA_STATE_HANDLER);
-                }
-                result = true;
-            }
-        }
-        if (!result && !explicitIntent) {
-            result = sScheduler.initIfPending(launcher, alreadyOnHome);
-        }
-        return result;
-    }
-
-    private static class Scheduler implements Runnable {
-
-        private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
-
-        public void schedule(InternalStateHandler handler) {
-            synchronized (this) {
-                mPendingHandler = new WeakReference<>(handler);
-            }
-            MAIN_EXECUTOR.execute(this);
-        }
-
-        @Override
-        public void run() {
-            LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-            if (app == null) {
-                return;
-            }
-            Callbacks cb = app.getModel().getCallback();
-            if (!(cb instanceof Launcher)) {
-                return;
-            }
-            Launcher launcher = (Launcher) cb;
-            initIfPending(launcher, launcher.isStarted());
-        }
-
-        public boolean initIfPending(Launcher launcher, boolean alreadyOnHome) {
-            InternalStateHandler pendingHandler = mPendingHandler.get();
-            if (pendingHandler != null) {
-                if (!pendingHandler.init(launcher, alreadyOnHome)) {
-                    clearReference(pendingHandler);
-                }
-                return true;
-            }
-            return false;
-        }
-
-        public boolean clearReference(InternalStateHandler handler) {
-            synchronized (this) {
-                if (mPendingHandler.get() == handler) {
-                    mPendingHandler.clear();
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        public boolean hasPending() {
-            return mPendingHandler.get() != null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index abf90e2..852928b 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -24,7 +24,6 @@
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -59,7 +58,9 @@
     private boolean mAutoRotateEnabled;
 
     /**
-     * Rotation request made by {@link InternalStateHandler}. This supersedes any other request.
+     * Rotation request made by
+     * {@link com.android.launcher3.util.ActivityTracker.SchedulerCallback}.
+     * This supersedes any other request.
      */
     private int mStateHandlerRequest = REQUEST_NONE;
     /**
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index f5f61cd..619293b 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -29,6 +29,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -55,8 +56,7 @@
         mDeviceProfile = InvariantDeviceProfile.INSTANCE.
                 get(context).getDeviceProfile(context);
         mLauncherAppState = LauncherAppState.getInstanceNoCreate();
-        mLauncher = mLauncherAppState != null ?
-                (Launcher) mLauncherAppState.getModel().getCallback() : null;
+        mLauncher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     public Bundle call(String method) {
@@ -83,7 +83,7 @@
             }
 
             case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
                 break;
             }
 
@@ -168,4 +168,9 @@
         }
         return response;
     }
+
+    protected boolean isLauncherInitialized() {
+        final LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+        return model.getCallback() == null || model.isModelLoaded();
+    }
 }
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 3777a41..d0edfd8 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -24,12 +24,11 @@
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Utilities;
+
 /**
  * One dimensional scroll/drag/swipe gesture detector.
  *
@@ -41,47 +40,14 @@
 
     private static final boolean DBG = false;
     private static final String TAG = "SwipeDetector";
+    private static final float ANIMATION_DURATION = 1200;
+    /** The minimum release velocity in pixels per millisecond that triggers fling.*/
+    private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
 
-    private int mScrollConditions;
     public static final int DIRECTION_POSITIVE = 1 << 0;
     public static final int DIRECTION_NEGATIVE = 1 << 1;
     public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
 
-    private static final float ANIMATION_DURATION = 1200;
-
-    protected int mActivePointerId = INVALID_POINTER_ID;
-
-    /**
-     * The minimum release velocity in pixels per millisecond that triggers fling..
-     */
-    public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
-
-    /* Scroll state, this is set to true during dragging and animation. */
-    private ScrollState mState = ScrollState.IDLE;
-
-    enum ScrollState {
-        IDLE,
-        DRAGGING,      // onDragStart, onDrag
-        SETTLING       // onDragEnd
-    }
-
-    public static abstract class Direction {
-
-        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
-                boolean isRtl);
-
-        /**
-         * Distance in pixels a touch can wander before we think the user is scrolling.
-         */
-        abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
-
-        abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
-
-        abstract boolean isPositive(float displacement);
-
-        abstract boolean isNegative(float displacement);
-    }
-
     public static final Direction VERTICAL = new Direction() {
 
         @Override
@@ -150,35 +116,54 @@
         }
     };
 
-    //------------------- ScrollState transition diagram -----------------------------------
-    //
-    // IDLE ->      (mDisplacement > mTouchSlop) -> DRAGGING
-    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
-    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
-    // SETTLING -> (View settled) -> IDLE
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final Direction mDir;
+    private final boolean mIsRtl;
+    private final float mTouchSlop;
+    private final float mMaxVelocity;
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
 
-    private void setState(ScrollState newState) {
-        if (DBG) {
-            Log.d(TAG, "setState:" + mState + "->" + newState);
-        }
-        // onDragStart and onDragEnd is reported ONLY on state transition
-        if (newState == ScrollState.DRAGGING) {
-            initializeDragging();
-            if (mState == ScrollState.IDLE) {
-                reportDragStart(false /* recatch */);
-            } else if (mState == ScrollState.SETTLING) {
-                reportDragStart(true /* recatch */);
-            }
-        }
-        if (newState == ScrollState.SETTLING) {
-            reportDragEnd();
-        }
+    private int mActivePointerId = INVALID_POINTER_ID;
+    private VelocityTracker mVelocityTracker;
+    private float mLastDisplacement;
+    private float mDisplacement;
+    private float mSubtractDisplacement;
+    private boolean mIgnoreSlopWhenSettling;
+    private int mScrollDirections;
+    private ScrollState mState = ScrollState.IDLE;
 
-        mState = newState;
+    private enum ScrollState {
+        IDLE,
+        DRAGGING,      // onDragStart, onDrag
+        SETTLING       // onDragEnd
     }
 
-    public boolean isDraggingOrSettling() {
-        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+    public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+    }
+
+    @VisibleForTesting
+    protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            @NonNull Direction dir, boolean isRtl) {
+        mListener = l;
+        mDir = dir;
+        mIsRtl = isRtl;
+        mTouchSlop = config.getScaledTouchSlop();
+        mMaxVelocity = config.getScaledMaximumFlingVelocity();
+    }
+
+    public static long calculateDuration(float velocity, float progressNeeded) {
+        // TODO: make these values constants after tuning.
+        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+        float travelDistance = Math.max(0.2f, progressNeeded);
+        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+        if (DBG) {
+            Log.d(TAG, String.format(
+                    "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+        }
+        return duration;
     }
 
     public int getDownX() {
@@ -203,73 +188,31 @@
         return mState == ScrollState.DRAGGING;
     }
 
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-    private final Direction mDir;
-    private final boolean mIsRtl;
-
-    private final float mTouchSlop;
-    private final float mMaxVelocity;
-
-    /* Client of this gesture detector can register a callback. */
-    private final Listener mListener;
-
-    private VelocityTracker mVelocityTracker;
-
-    private float mLastDisplacement;
-    private float mDisplacement;
-
-    private float mSubtractDisplacement;
-    private boolean mIgnoreSlopWhenSettling;
-
-    public interface Listener {
-        void onDragStart(boolean start);
-
-        boolean onDrag(float displacement);
-
-        default boolean onDrag(float displacement, MotionEvent event) {
-            return onDrag(displacement);
-        }
-
-        void onDragEnd(float velocity, boolean fling);
-    }
-
-    public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
-        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
-    }
-
-    @VisibleForTesting
-    protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
-            @NonNull Direction dir, boolean isRtl) {
-        mListener = l;
-        mDir = dir;
-        mIsRtl = isRtl;
-        mTouchSlop = config.getScaledTouchSlop();
-        mMaxVelocity = config.getScaledMaximumFlingVelocity();
+    public boolean isDraggingOrSettling() {
+        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     }
 
     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
-        mScrollConditions = scrollDirectionFlags;
+        mScrollDirections = scrollDirectionFlags;
         mIgnoreSlopWhenSettling = ignoreSlop;
     }
 
     public int getScrollDirections() {
-        return mScrollConditions;
+        return mScrollDirections;
     }
 
-    private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
-        // reject cases where the angle or slop condition is not met.
-        if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
-                > Math.abs(mDisplacement)) {
-            return false;
-        }
+    public void finishedScrolling() {
+        setState(ScrollState.IDLE);
+    }
 
-        // Check if the client is interested in scroll in current direction.
-        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
-                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
-            return true;
-        }
-        return false;
+    /**
+     * Returns if the start drag was towards the positive direction or negative.
+     *
+     * @see #setDetectableScrollConditions(int, boolean)
+     * @see #DIRECTION_BOTH
+     */
+    public boolean wasInitialTouchPositive() {
+        return mDir.isPositive(mSubtractDisplacement);
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
@@ -338,16 +281,50 @@
         return true;
     }
 
-    public void finishedScrolling() {
-        setState(ScrollState.IDLE);
+    //------------------- ScrollState transition diagram -----------------------------------
+    //
+    // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+    // SETTLING -> (View settled) -> IDLE
+
+    private void setState(ScrollState newState) {
+        if (DBG) {
+            Log.d(TAG, "setState:" + mState + "->" + newState);
+        }
+        // onDragStart and onDragEnd is reported ONLY on state transition
+        if (newState == ScrollState.DRAGGING) {
+            initializeDragging();
+            if (mState == ScrollState.IDLE) {
+                reportDragStart(false /* recatch */);
+            } else if (mState == ScrollState.SETTLING) {
+                reportDragStart(true /* recatch */);
+            }
+        }
+        if (newState == ScrollState.SETTLING) {
+            reportDragEnd();
+        }
+
+        mState = newState;
     }
 
-    private boolean reportDragStart(boolean recatch) {
+    private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+        // reject cases where the angle or slop condition is not met.
+        if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+                > Math.abs(mDisplacement)) {
+            return false;
+        }
+
+        // Check if the client is interested in scroll in current direction.
+        return ((mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement))
+                || ((mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement));
+    }
+
+    private void reportDragStart(boolean recatch) {
         mListener.onDragStart(!recatch);
         if (DBG) {
             Log.d(TAG, "onDragStart recatch:" + recatch);
         }
-        return true;
     }
 
     private void initializeDragging() {
@@ -361,26 +338,15 @@
         }
     }
 
-    /**
-     * Returns if the start drag was towards the positive direction or negative.
-     *
-     * @see #setDetectableScrollConditions(int, boolean)
-     * @see #DIRECTION_BOTH
-     */
-    public boolean wasInitialTouchPositive() {
-        return mDir.isPositive(mSubtractDisplacement);
-    }
-
-    private boolean reportDragging(MotionEvent event) {
+    private void reportDragging(MotionEvent event) {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
                 Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
             }
 
             mLastDisplacement = mDisplacement;
-            return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
+            mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
         }
-        return true;
     }
 
     private void reportDragEnd() {
@@ -394,14 +360,33 @@
         mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
     }
 
-    public static long calculateDuration(float velocity, float progressNeeded) {
-        // TODO: make these values constants after tuning.
-        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
-        float travelDistance = Math.max(0.2f, progressNeeded);
-        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
-        if (DBG) {
-            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+    /** Listener to receive updates on the swipe. */
+    public interface Listener {
+        void onDragStart(boolean start);
+
+        boolean onDrag(float displacement);
+
+        default boolean onDrag(float displacement, MotionEvent event) {
+            return onDrag(displacement);
         }
-        return duration;
+
+        void onDragEnd(float velocity, boolean fling);
+    }
+
+    public abstract static class Direction {
+
+        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
+                boolean isRtl);
+
+        /**
+         * Distance in pixels a touch can wander before we think the user is scrolling.
+         */
+        abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+
+        abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
+
+        abstract boolean isPositive(float displacement);
+
+        abstract boolean isNegative(float displacement);
     }
 }
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
new file mode 100644
index 0000000..e85a4e8
--- /dev/null
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -0,0 +1,131 @@
+/*
+ * 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.launcher3.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Helper class to statically track activity creation
+ */
+public final class ActivityTracker<T extends BaseActivity> implements Runnable {
+
+    private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
+    private WeakReference<SchedulerCallback<T>> mPendingCallback = new WeakReference<>(null);
+
+    private static final String EXTRA_SCHEDULER_CALLBACK = "launcher.scheduler_callback";
+
+    @Nullable
+    public <R extends T> R getCreatedActivity() {
+        return (R) mCurrentActivity.get();
+    }
+
+    public void onActivityDestroyed(T activity) {
+        if (mCurrentActivity.get() == activity) {
+            mCurrentActivity.clear();
+        }
+    }
+
+    public void schedule(SchedulerCallback<? extends T> callback) {
+        synchronized (this) {
+            mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
+        }
+        MAIN_EXECUTOR.execute(this);
+    }
+
+    @Override
+    public void run() {
+        T activity = mCurrentActivity.get();
+        if (activity != null) {
+            initIfPending(activity, activity.isStarted());
+        }
+    }
+
+    public boolean initIfPending(T activity, boolean alreadyOnHome) {
+        SchedulerCallback<T> pendingCallback = mPendingCallback.get();
+        if (pendingCallback != null) {
+            if (!pendingCallback.init(activity, alreadyOnHome)) {
+                clearReference(pendingCallback);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public boolean clearReference(SchedulerCallback<? extends T> handler) {
+        synchronized (this) {
+            if (mPendingCallback.get() == handler) {
+                mPendingCallback.clear();
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public boolean hasPending() {
+        return mPendingCallback.get() != null;
+    }
+
+    public boolean handleCreate(T activity) {
+        mCurrentActivity = new WeakReference<>(activity);
+        return handleIntent(activity, activity.getIntent(), false, false);
+    }
+
+    public boolean handleNewIntent(T activity, Intent intent) {
+        return handleIntent(activity, intent, activity.isStarted(), true);
+    }
+
+    private boolean handleIntent(
+            T activity, Intent intent, boolean alreadyOnHome, boolean explicitIntent) {
+        boolean result = false;
+        if (intent != null && intent.getExtras() != null) {
+            IBinder stateBinder = intent.getExtras().getBinder(EXTRA_SCHEDULER_CALLBACK);
+            if (stateBinder instanceof ObjectWrapper) {
+                SchedulerCallback<T> handler =
+                        ((ObjectWrapper<SchedulerCallback>) stateBinder).get();
+                if (!handler.init(activity, alreadyOnHome)) {
+                    intent.getExtras().remove(EXTRA_SCHEDULER_CALLBACK);
+                }
+                result = true;
+            }
+        }
+        if (!result && !explicitIntent) {
+            result = initIfPending(activity, alreadyOnHome);
+        }
+        return result;
+    }
+
+    public interface SchedulerCallback<T extends BaseActivity> {
+
+        boolean init(T activity, boolean alreadyOnHome);
+
+        default Intent addToIntent(Intent intent) {
+            Bundle extras = new Bundle();
+            extras.putBinder(EXTRA_SCHEDULER_CALLBACK, ObjectWrapper.wrap(this));
+            intent.putExtras(extras);
+            return intent;
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java b/src/com/android/launcher3/util/ObjectWrapper.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
rename to src/com/android/launcher3/util/ObjectWrapper.java
index abfe3ad..b692431 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
+++ b/src/com/android/launcher3/util/ObjectWrapper.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep.util;
+package com.android.launcher3.util;
 
 import android.os.Binder;
 import android.os.IBinder;
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
index e042357..f209fae 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
@@ -25,6 +25,10 @@
 import android.util.Log;
 import android.view.ViewConfiguration;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.testcomponent.TouchEventGenerator;
 
 import org.junit.Before;
@@ -33,10 +37,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SwipeDetectorTest {
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 3d4e17b..1d89d6e 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -73,7 +73,6 @@
 
         // Launch the home activity
         mDevice.pressHome();
-        waitForModelLoaded();
 
         mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
     }
@@ -89,7 +88,6 @@
 
         // Launch the home activity
         mDevice.pressHome();
-        waitForModelLoaded();
 
         // Verify widget present
         assertTrue("Widget is not present",
@@ -106,7 +104,6 @@
 
         // Launch the home activity
         mDevice.pressHome();
-        waitForModelLoaded();
 
         mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
     }