Merge "Fix test bug where launcher throws NPE because it isnt loaded yet." into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 78ea02a..3d1d996 100644
--- a/Android.mk
+++ b/Android.mk
@@ -84,8 +84,7 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, src_shortcuts_overrides) \
-    $(call all-java-files-under, src_ui_overrides) \
-    $(call all-java-files-under, src_flags)
+    $(call all-java-files-under, src_ui_overrides)
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 # Proguard is disable for testing. Derivarive prjects to keep proguard enabled
@@ -146,7 +145,7 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
@@ -163,7 +162,6 @@
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
     $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
-    $(call all-java-files-under, src_flags) \
     $(call all-java-files-under, src_shortcuts_overrides)
 
 LOCAL_RESOURCE_DIR := \
@@ -218,7 +216,7 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
@@ -264,7 +262,7 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index bcb1f5c..3953fd0 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -40,7 +40,9 @@
 
     @Override
     protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
-            RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets,
+            boolean launcherClosing) {
         // Stubbed. Recents launch animation will come from the recents view itself and will not
         // use remote animations.
     }
@@ -74,21 +76,23 @@
         }
 
         @Override
-        public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+        public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                RemoteAnimationTargetCompat[] wallpaperTargets,
                 AnimationResult result) {
             boolean isGoingToRecents =
-                    taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING)
+                    taskIsATargetWithMode(appTargets, mLauncher.getTaskId(), MODE_OPENING)
                     && (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
             if (isGoingToRecents) {
                 IconRecentsView recentsView = mLauncher.getOverviewPanel();
                 if (!recentsView.isReadyForRemoteAnim()) {
                     recentsView.setOnReadyForRemoteAnimCallback(() ->
-                        postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))
+                        postAsyncCallback(mHandler, () -> onCreateAnimation(appTargets,
+                                wallpaperTargets, result))
                     );
                     return;
                 }
             }
-            super.onCreateAnimation(targetCompats, result);
+            super.onCreateAnimation(appTargets, wallpaperTargets, result);
         }
     }
 }
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index cbc77d2..f2aa842 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -20,7 +20,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeStatesTouchController;
@@ -51,7 +50,7 @@
                     .getMode().hasGestures;
             list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
         }
-        if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
+        if (Utilities.IS_DEBUG_DEVICE
                 && !launcher.getDeviceProfile().isMultiWindowMode
                 && !launcher.getDeviceProfile().isVerticalBarLayout()) {
             list.add(new StatusBarTouchController(launcher));
@@ -90,5 +89,5 @@
         return RotationMode.NORMAL;
     }
 
-    public static void clearSwipeSharedState(boolean finishAnimation) {}
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
 }
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 92900f2..6b50088 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.IconRecentsView;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -47,12 +46,12 @@
         RemoteAnimationProvider {
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final ActivityControlHelper<T> mHelper;
+    private final BaseActivityInterface<T> mHelper;
     private final int mTargetTaskId;
     private IconRecentsView mRecentsView;
     private AppToOverviewAnimationListener mAnimationReadyListener;
 
-    AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
+    AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
         mHelper = helper;
         mTargetTaskId = targetTaskId;
     }
@@ -76,7 +75,7 @@
         if (mAnimationReadyListener != null) {
             mAnimationReadyListener.onActivityReady(activity);
         }
-        ActivityControlHelper.AnimationFactory factory =
+        BaseActivityInterface.AnimationFactory factory =
                 mHelper.prepareRecentsUI(activity, wasVisible,
                         false /* animate activity */, (controller) -> {
                             controller.dispatchOnStart();
@@ -86,7 +85,7 @@
                             anim.start();
                         });
         factory.onRemoteAnimationReceived(null);
-        factory.createActivityController(getRecentsLaunchDuration());
+        factory.createActivityInterface(getRecentsLaunchDuration());
         mRecentsView = activity.getOverviewPanel();
         return false;
     }
@@ -95,11 +94,12 @@
      * Create remote window animation from the currently running app to the overview panel. Should
      * be called after {@link #onActivityReady}.
      *
-     * @param targetCompats the target apps
+     * @param appTargets the target apps
      * @return animation from app to overview
      */
     @Override
-    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
         if (mAnimationReadyListener != null) {
             mAnimationReadyListener.onWindowAnimationCreated();
         }
@@ -112,14 +112,14 @@
             return anim;
         }
 
-        RemoteAnimationTargetSet targetSet =
-                new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
-        mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
+        RemoteAnimationTargets targets =
+                new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
+        mRecentsView.setTransitionedFromApp(!targets.isAnimatingHome());
 
         RemoteAnimationTargetCompat recentsTarget = null;
         RemoteAnimationTargetCompat closingAppTarget = null;
 
-        for (RemoteAnimationTargetCompat target : targetCompats) {
+        for (RemoteAnimationTargetCompat target : appTargets) {
             if (target.mode == MODE_OPENING) {
                 recentsTarget = target;
             } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) {
@@ -157,16 +157,17 @@
                 false /* startAtFrontOfQueue */) {
 
             @Override
-            public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+            public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                    RemoteAnimationTargetCompat[] wallpaperTargets,
                     AnimationResult result) {
                 IconRecentsView recentsView = mRecentsView;
                 if (!recentsView.isReadyForRemoteAnim()) {
                     recentsView.setOnReadyForRemoteAnimCallback(() -> postAsyncCallback(handler,
-                            () -> onCreateAnimation(targetCompats, result))
+                            () -> onCreateAnimation(appTargets, wallpaperTargets, result))
                     );
                     return;
                 }
-                result.setAnimation(createWindowAnimation(targetCompats), context);
+                result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 85%
rename from go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
rename to go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 057b48b..2af8441 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -26,21 +26,21 @@
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.views.IconRecentsView;
 
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * {@link BaseActivityInterface} for recents when the default launcher is different than the
  * currently running one and apps should interact with the {@link RecentsActivity} as opposed
  * to the in-launcher one.
  */
-public final class FallbackActivityControllerHelper extends
-        GoActivityControlHelper<RecentsActivity> {
+public final class FallbackActivityInterface extends
+        GoActivityInterface<RecentsActivity> {
 
-    public FallbackActivityControllerHelper() { }
+    public FallbackActivityInterface() { }
 
     @Override
     public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
@@ -58,17 +58,17 @@
             boolean isAnimatingToRecents = false;
 
             @Override
-            public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+            public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
                 isAnimatingToRecents = targets != null && targets.isAnimatingHome();
                 if (!isAnimatingToRecents) {
                     rv.setAlpha(1);
                 }
-                createActivityController(getSwipeUpDestinationAndLength(
+                createActivityInterface(getSwipeUpDestinationAndLength(
                         activity.getDeviceProfile(), activity, new Rect()));
             }
 
             @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 if (!isAnimatingToRecents) {
                     return;
                 }
@@ -85,13 +85,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/GoActivityControlHelper.java b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
similarity index 93%
rename from go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
rename to go/quickstep/src/com/android/quickstep/GoActivityInterface.java
index 274a347..5ce0f4c 100644
--- a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
+++ b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
@@ -13,8 +13,8 @@
  *
  * @param <T> activity that contains the overview
  */
-public abstract class GoActivityControlHelper<T extends BaseDraggingActivity> implements
-        ActivityControlHelper<T> {
+public abstract class GoActivityInterface<T extends BaseDraggingActivity> implements
+        BaseActivityInterface<T> {
 
     @Override
     public void onTransitionCancelled(T activity, boolean activityVisible) {
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 92%
rename from go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
rename to go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index b0d9cda..5bff8e8 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -30,10 +30,10 @@
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for the in-launcher recents.
+ * {@link BaseActivityInterface} for the in-launcher recents.
  * TODO: Implement the app to overview animation functionality
  */
-public final class LauncherActivityControllerHelper extends GoActivityControlHelper<Launcher> {
+public final class LauncherActivityInterface extends GoActivityInterface<Launcher> {
 
     @Override
     public AnimationFactory prepareRecentsUI(Launcher activity,
@@ -43,8 +43,7 @@
         activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
         //TODO: Implement this based off where the recents view needs to be for app => recents anim.
         return new AnimationFactory() {
-            @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
                         fromState, OVERVIEW, transitionLength));
             }
@@ -55,7 +54,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..a436ce7 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;
@@ -40,26 +40,28 @@
 public class OverviewCommandHelper {
 
     private final Context mContext;
-    private final ActivityManagerWrapper mAM;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
 
-    public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
+    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
+            OverviewComponentObserver observer) {
         mContext = context;
-        mAM = ActivityManagerWrapper.getInstance();
+        mDeviceState = deviceState;
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
     public void onOverviewToggle() {
         // If currently screen pinning, do not enter overview
-        if (mAM.isScreenPinningActive()) {
+        if (mDeviceState.isScreenPinningActive()) {
             return;
         }
 
-        mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
@@ -99,15 +101,15 @@
 
     private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
 
-        protected final ActivityControlHelper<T> mHelper;
+        protected final BaseActivityInterface<T> mHelper;
         private final long mCreateTime;
 
         private final long mToggleClickedTime = SystemClock.uptimeMillis();
         private boolean mUserEventLogged;
-        private ActivityInitListener mListener;
+        private ActivityInitListener<T> mListener;
 
         public RecentsActivityCommand() {
-            mHelper = mOverviewComponentObserver.getActivityControlHelper();
+            mHelper = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
 
             // Preload the plan
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 19dd82f..f743663 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -34,6 +34,9 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -44,15 +47,6 @@
 public class TouchInteractionService extends Service {
 
     private static final String TAG = "GoTouchInteractionService";
-    private boolean mIsUserUnlocked;
-    private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                initWhenUserUnlocked();
-            }
-        }
-    };
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
@@ -63,26 +57,26 @@
         public void onInitialize(Bundle bundle) throws RemoteException {
             ISystemUiProxy iSystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            mRecentsModel.setSystemUiProxy(iSystemUiProxy);
+            SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
         }
 
         @Override
         public void onOverviewToggle() {
-            if (mIsUserUnlocked) {
+            if (mDeviceState.isUserUnlocked()) {
                 mOverviewCommandHelper.onOverviewToggle();
             }
         }
 
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
-            if (mIsUserUnlocked) {
+            if (mDeviceState.isUserUnlocked()) {
                 mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
             }
         }
 
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (mIsUserUnlocked && triggeredFromAltTab && !triggeredFromHomeKey) {
+            if (mDeviceState.isUserUnlocked() && triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab hides the overview and ends at the target app
                 mOverviewCommandHelper.onOverviewHidden();
             }
@@ -90,7 +84,7 @@
 
         @Override
         public void onTip(int actionType, int viewType) {
-            if (mIsUserUnlocked) {
+            if (mDeviceState.isUserUnlocked()) {
                 mOverviewCommandHelper.onTip(actionType, viewType);
             }
         }
@@ -127,7 +121,7 @@
         public void onMotionEvent(MotionEvent ev) { }
 
         public void onBind(ISystemUiProxy iSystemUiProxy) {
-            mRecentsModel.setSystemUiProxy(iSystemUiProxy);
+            SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
         }
     };
 
@@ -140,35 +134,30 @@
     private RecentsModel mRecentsModel;
     private OverviewComponentObserver mOverviewComponentObserver;
     private OverviewCommandHelper mOverviewCommandHelper;
+    private RecentsAnimationDeviceState mDeviceState;
 
     @Override
     public void onCreate() {
         super.onCreate();
-        if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
-            initWhenUserUnlocked();
-        } else {
-            mIsUserUnlocked = false;
-            registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-        }
+        mDeviceState = new RecentsAnimationDeviceState(this);
+        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
 
         sConnected = true;
     }
 
-    private void initWhenUserUnlocked() {
+    public void onUserUnlocked() {
         mRecentsModel = RecentsModel.INSTANCE.get(this);
-        mOverviewComponentObserver = new OverviewComponentObserver(this);
-        mOverviewCommandHelper = new OverviewCommandHelper(this,
+        mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
+        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
                 mOverviewComponentObserver);
-        mIsUserUnlocked = true;
-        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
     }
 
     @Override
     public void onDestroy() {
-        if (mIsUserUnlocked) {
+        if (mDeviceState.isUserUnlocked()) {
             mOverviewComponentObserver.onDestroy();
         }
-        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+        mDeviceState.destroy();
         sConnected = false;
         super.onDestroy();
     }
diff --git a/go/src/com/android/launcher3/config/FeatureFlags.java b/go/src/com/android/launcher3/config/FeatureFlags.java
deleted file mode 100644
index a90808c..0000000
--- a/go/src/com/android/launcher3/config/FeatureFlags.java
+++ /dev/null
@@ -1,32 +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.config;
-
-import android.content.Context;
-
-/**
- * Defines a set of flags used to control various launcher behaviors
- */
-public final class FeatureFlags extends BaseFlags {
-    private FeatureFlags() {
-        // Prevent instantiation
-    }
-
-    // Features to control Launcher3Go behavior
-    public static final boolean GO_DISABLE_WIDGETS = true;
-    public static final boolean LAUNCHER3_SPRING_ICONS = false;
-}
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 18f3f9d..7b8f4e6 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -37,6 +37,10 @@
  * <p> The widgets and shortcuts are organized using package name as its index.
  */
 public class WidgetsModel {
+
+    // True is the widget support is disabled.
+    public static final boolean GO_DISABLE_WIDGETS = false;
+
     private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
     /**
diff --git a/gradle.properties b/gradle.properties
index 5b90f08..a77f52a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,4 +10,4 @@
 PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
 
 BUILD_TOOLS_VERSION=28.0.3
-COMPILE_SDK=android-Q
\ No newline at end of file
+COMPILE_SDK=android-R
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index fc7d6b3..f491ed7 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
+
 import androidx.annotation.NonNull;
 
 /**
@@ -35,6 +36,8 @@
     static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
     static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
+    private static final float ICON_BADGE_SCALE = 0.444f;
+
     private final Rect mOldBounds = new Rect();
     protected final Context mContext;
     private final Canvas mCanvas;
@@ -254,7 +257,7 @@
      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
      */
     public void badgeWithDrawable(Canvas target, Drawable badge) {
-        int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
         badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
                 mIconBitmapSize, mIconBitmapSize);
         badge.draw(target);
@@ -335,6 +338,13 @@
     }
 
     /**
+     * Returns the correct badge size given an icon size
+     */
+    public static int getBadgeSizeForIconSize(int iconSize) {
+        return (int) (ICON_BADGE_SCALE * iconSize);
+    }
+
+    /**
      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
      * This allows the badging to be done based on the action bitmap size rather than
      * the scaled bitmap size.
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 36d1c3e..93f0538 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -267,9 +267,13 @@
             entry = new CacheEntry();
             cachingLogic.loadIcon(mContext, object, entry);
         }
+        // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded
+        // (e.g. fallback icon, default icon). So we drop here since there's no point in caching
+        // an empty entry.
+        if (entry.icon == null) return;
         entry.title = cachingLogic.getLabel(object);
         entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
-        mCache.put(key, entry);
+        if (cachingLogic.addToMemCache()) mCache.put(key, entry);
 
         ContentValues values = newContentValues(entry, entry.title.toString(),
                 componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
@@ -308,20 +312,12 @@
             @NonNull ComponentName componentName, @NonNull UserHandle user,
             @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
             boolean usePackageIcon, boolean useLowResIcon) {
-        return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon,
-                useLowResIcon, true);
-    }
-
-    protected <T> CacheEntry cacheLocked(
-            @NonNull ComponentName componentName, @NonNull UserHandle user,
-            @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
-            boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) {
         assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
         if (entry == null || (entry.isLowRes() && !useLowResIcon)) {
             entry = new CacheEntry();
-            if (addToMemCache) {
+            if (cachingLogic.addToMemCache()) {
                 mCache.put(cacheKey, entry);
             }
 
@@ -378,7 +374,7 @@
      * Adds a default package entry in the cache. This entry is not persisted and will be removed
      * when the cache is flushed.
      */
-    public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
+    protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
             Bitmap icon, CharSequence title) {
         removeFromMemCacheLocked(packageName, user);
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index 09f59b8..e40a9c2 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -41,4 +41,11 @@
     default String getKeywords(T object, LocaleList localeList) {
         return null;
     }
+
+    /**
+     * Returns true the object should be added to mem cache; otherwise returns false.
+     */
+    default boolean addToMemCache() {
+        return true;
+    }
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 3c71bd0..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,8 @@
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
                 T app = componentMap.remove(component);
-                if (version == info.versionCode && updateTime == info.lastUpdateTime &&
-                        TextUtils.equals(c.getString(systemStateIndex),
+                if (version == info.versionCode && updateTime == info.lastUpdateTime
+                        && TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.getIconSystemState(info.packageName))) {
 
                     if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 371161e..d842484 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -18,16 +18,11 @@
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 
-import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
-import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
-import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -38,11 +33,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -70,15 +64,16 @@
 
     @Override
     protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
-            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+            @NonNull RemoteAnimationTargetCompat[] appTargets,
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing) {
         RecentsView recentsView = mLauncher.getOverviewPanel();
         boolean skipLauncherChanges = !launcherClosing;
 
-        TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets);
+        TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
 
-        ClipAnimationHelper helper = new ClipAnimationHelper(mLauncher);
-        anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
-                .setDuration(RECENTS_LAUNCH_DURATION));
+        AppWindowAnimationHelper helper = new AppWindowAnimationHelper(mLauncher);
+        anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets,
+                wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION));
 
         Animator childStateAnimation = null;
         // Found a visible recents task that matches the opening app, lets launch the app from there
@@ -156,8 +151,11 @@
                 return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
             case INDEX_RECENTS_TRANSLATE_X_ANIM:
-                return new SpringObjectAnimator<>(mLauncher.getOverviewPanel(),
-                        VIEW_TRANSLATE_X, MIN_VISIBLE_CHANGE_PIXELS, 0.8f, 250, values);
+                return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
+                        .setDampingRatio(0.8f)
+                        .setStiffness(250)
+                        .setValues(values)
+                        .build(mLauncher);
             default:
                 return super.createStateElementAnimation(index, values);
         }
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/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index a12917f..4c7943b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,12 +41,19 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.AppLaunchEventsPlugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker {
+public class PredictionAppTracker extends AppLaunchTracker
+        implements PluginListener<AppLaunchEventsPlugin> {
 
     private static final String TAG = "PredictionAppTracker";
     private static final boolean DBG = false;
@@ -58,6 +65,7 @@
 
     protected final Context mContext;
     private final Handler mMessageHandler;
+    private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList;
 
     // Accessed only on worker thread
     private AppPredictor mHomeAppPredictor;
@@ -69,6 +77,10 @@
         InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
 
         mMessageHandler.sendEmptyMessage(MSG_INIT);
+
+        mAppLaunchEventsPluginsList = new ArrayList<>();
+        PluginManagerWrapper.INSTANCE.get(context)
+                .addPluginListener(this, AppLaunchEventsPlugin.class, true);
     }
 
     @UiThread
@@ -96,7 +108,7 @@
         AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
 
         if (apm == null) {
-          return null;
+            return null;
         }
 
         AppPredictor predictor = apm.createAppPredictionSession(
@@ -116,7 +128,7 @@
      */
     @WorkerThread
     @Nullable
-    public Bundle getAppPredictionContextExtras(Client client){
+    public Bundle getAppPredictionContextExtras(Client client) {
         return null;
     }
 
@@ -128,7 +140,7 @@
                 destroy();
 
                 // Initialize the clients
-                int count = InvariantDeviceProfile.INSTANCE.get(mContext).numColumns;
+                int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
                 mHomeAppPredictor = createPredictor(Client.HOME, count);
                 mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count);
                 return true;
@@ -167,12 +179,15 @@
         if (DBG) {
             Log.d(TAG, String.format("Sent immediate message to update %s", client));
         }
+
+        // Relay onReturnedToHome to every plugin.
+        mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome);
     }
 
     @Override
     @UiThread
     public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
-            String container) {
+                                String container) {
         // TODO: Use the full shortcut info
         AppTarget target = new AppTarget.Builder(
                 new AppTargetId("shortcut:" + shortcutId), packageName, user)
@@ -180,6 +195,16 @@
                 .build();
 
         sendLaunch(target, container);
+
+        // Relay onStartShortcut info to every connected plugin.
+        mAppLaunchEventsPluginsList
+                .forEach(plugin -> plugin.onStartShortcut(
+                        packageName,
+                        shortcutId,
+                        user,
+                        container != null ? container : CONTAINER_DEFAULT)
+        );
+
     }
 
     @Override
@@ -191,6 +216,14 @@
                     .setClassName(cn.getClassName())
                     .build();
             sendLaunch(target, container);
+
+            // Relay onStartApp to every connected plugin.
+            mAppLaunchEventsPluginsList
+                    .forEach(plugin -> plugin.onStartApp(
+                            cn,
+                            user,
+                            container != null ? container : CONTAINER_DEFAULT)
+            );
         }
     }
 
@@ -203,6 +236,14 @@
                 .setClassName(cn.getClassName())
                 .build();
         sendDismiss(target, container);
+
+        // Relay onDismissApp to every connected plugin.
+        mAppLaunchEventsPluginsList
+                .forEach(plugin -> plugin.onDismissApp(
+                        cn,
+                        user,
+                        container != null ? container : CONTAINER_DEFAULT)
+        );
     }
 
     @UiThread
@@ -222,4 +263,14 @@
     private void sendDismiss(AppTarget target, String container) {
         sendEvent(target, container, AppTargetEvent.ACTION_DISMISS);
     }
+
+    @Override
+    public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) {
+        mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin);
+    }
+
+    @Override
+    public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) {
+        mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 95f63ce..23db5df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -93,7 +93,7 @@
 
     private final Launcher mLauncher;
     private final PredictionUiStateManager mPredictionUiStateManager;
-    private final int mNumPredictedAppsPerRow;
+    private int mNumPredictedAppsPerRow;
 
     // The set of predicted app component names
     private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
@@ -129,7 +129,7 @@
 
         mFocusHelper = new SimpleFocusIndicatorHelper(this);
 
-        mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+        mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
         mLauncher = Launcher.getLauncher(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
 
@@ -227,6 +227,7 @@
 
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
+        mNumPredictedAppsPerRow = dp.inv.numAllAppsColumns;
         removeAllViews();
         applyPredictionApps();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 085bbc4..1a59770 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -24,7 +24,6 @@
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -32,6 +31,8 @@
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.StateListener;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
@@ -58,7 +59,7 @@
  * 4) Maintains the current active client id (for the predictions) and all updates are performed on
  * that client id.
  */
-public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInfoUpdateReceiver,
+public class PredictionUiStateManager implements StateListener, ItemInfoUpdateReceiver,
         OnIDPChangeListener, OnUpdateListener {
 
     public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
@@ -153,7 +154,10 @@
     public void reapplyItemInfo(ItemInfoWithIcon info) { }
 
     @Override
-    public void onGlobalLayout() {
+    public void onStateTransitionStart(LauncherState toState) { }
+
+    @Override
+    public void onStateTransitionComplete(LauncherState state) {
         if (mAppsView == null) {
             return;
         }
@@ -162,7 +166,8 @@
             mPendingState = null;
         }
         if (mPendingState == null) {
-            mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            Launcher.getLauncher(mAppsView.getContext()).getStateManager()
+                    .removeStateListener(this);
         }
     }
 
@@ -170,9 +175,8 @@
         boolean registerListener = mPendingState == null;
         mPendingState = state;
         if (registerListener) {
-            // OnGlobalLayoutListener is called whenever a view in the view tree changes
-            // visibility. Add a listener and wait until appsView is invisible again.
-            mAppsView.getViewTreeObserver().addOnGlobalLayoutListener(this);
+            // Add a listener and wait until appsView is invisible again.
+            Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 596bc4f..ff73679 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
@@ -28,17 +29,15 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.util.TouchController;
@@ -47,8 +46,8 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TouchInteractionService;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.util.ArrayList;
 
@@ -57,9 +56,16 @@
  */
 public abstract class RecentsUiFactory {
 
+    private static final String TAG = RecentsUiFactory.class.getSimpleName();
+
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
-    private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
-            WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
+
+    /**
+     * Reusable command for applying the shelf height on the background thread.
+     */
+    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> {
+        SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
+    };
 
     public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
         @Override
@@ -164,8 +170,7 @@
             }
         }
 
-        if (FeatureFlags.PULL_DOWN_STATUS_BAR
-                && !launcher.getDeviceProfile().isMultiWindowMode) {
+        if (!launcher.getDeviceProfile().isMultiWindowMode) {
             list.add(new StatusBarTouchController(launcher));
         }
 
@@ -183,11 +188,15 @@
         return new RecentsViewStateController(launcher);
     }
 
-    /**
-     * Clears the swipe shared state for the current swipe gesture.
-     */
-    public static void clearSwipeSharedState(boolean finishAnimation) {
-        TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
+    /** Clears the swipe shared state for the current swipe gesture. */
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            launcher.<RecentsView>getOverviewPanel().switchToScreenshot(
+                    () -> TouchInteractionService.getSwipeSharedState().clearAllState(
+                            finishAnimation));
+        } else {
+            TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
+        }
     }
 
     /**
@@ -200,9 +209,8 @@
         DeviceProfile profile = launcher.getDeviceProfile();
         boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
                 && !profile.isVerticalBarLayout();
-        UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
-                visible ? 1 : 0, profile.hotseatBarSizePx);
-
+        UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT, visible ? 1 : 0,
+                profile.hotseatBarSizePx);
         if (state == NORMAL) {
             launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 50cfac8..e4e60a0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -19,7 +19,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.LayoutUtils;
@@ -69,9 +68,17 @@
         if (taskCount == 0) {
             return super.getOverviewScaleAndTranslation(launcher);
         }
-        TaskView dummyTask = recentsView.getTaskViewAt(Utilities.boundToRange(
-                recentsView.getCurrentPage(), 0, taskCount - 1));
-        return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
+        TaskView dummyTask;
+        if (recentsView.getCurrentPage() >= 0) {
+            if (recentsView.getCurrentPage() <= taskCount - 1) {
+                dummyTask = recentsView.getCurrentPageTaskView();
+            } else {
+                dummyTask = recentsView.getTaskViewAt(taskCount - 1);
+            }
+        } else {
+            dummyTask = recentsView.getTaskViewAt(0);
+        }
+        return recentsView.getTempAppWindowAnimationHelper().updateForFullscreenOverview(dummyTask)
                 .getScaleAndTranslation();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 4a3ad1d..ee2e951 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -32,8 +32,8 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -50,7 +50,7 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
 
@@ -120,7 +120,7 @@
      * having it as part of the existing animation to the target state.
      */
     private boolean handlingOverviewAnim() {
-        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
         return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
     }
 
@@ -132,16 +132,33 @@
             // Fade in prediction icons quickly, then rest of all apps after reaching overview.
             float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
                     - OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(ACCEL,
-                    0, ALL_APPS_CONTENT_FADE_THRESHOLD));
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(LINEAR,
-                    progressToReachOverview, 1));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    0,
+                    ALL_APPS_CONTENT_FADE_THRESHOLD));
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    progressToReachOverview,
+                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
 
             // Get workspace out of the way quickly, to prepare for potential pause.
             builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
             builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
             builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
             return builder;
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            // Keep all apps/predictions opaque until the very end of the transition.
+            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    progressToReachOverview));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    1));
+            return builder;
         }
         return super.getAnimatorSetBuilderForStates(fromState, toState);
     }
@@ -164,6 +181,7 @@
 
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
             builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
             if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
                 builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
                 builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 20a2487..03862db 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -47,9 +47,9 @@
      * @return true if we should intercept the motion event
      */
     boolean canInterceptTouch(MotionEvent ev) {
-        if (mRecentsView.getChildCount() > 0) {
+        if (mRecentsView.getTaskViewCount() > 0) {
             // Allow swiping up in the gap between the hotseat and overview.
-            return ev.getY() >= mRecentsView.getChildAt(0).getBottom();
+            return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom();
         } else {
             // If there are no tasks, we only intercept if we're below the hotseat height.
             return isTouchOverHotseat(mLauncher, ev);
@@ -63,7 +63,7 @@
      * @return true if going back should take the user to the currently running task
      */
     boolean shouldSwipeDownReturnToApp() {
-        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage());
+        TaskView taskView = mRecentsView.getNextPageTaskView();
         return taskView != null && mRecentsView.shouldSwipeDownLaunchApp();
     }
 
@@ -76,7 +76,7 @@
      */
     PendingAnimation createSwipeDownToTaskAppAnimation(long duration) {
         mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
-        TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getCurrentPage());
+        TaskView taskView = mRecentsView.getCurrentPageTaskView();
         if (taskView == null) {
             throw new IllegalStateException("There is no task view to animate to.");
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index eb571f6..5c3b55d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -45,12 +45,11 @@
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Handles quick switching to a recent task from the home screen.
@@ -83,7 +82,7 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
         if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
             return NORMAL;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 5ebefa3..8a11ac8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -30,9 +30,8 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -49,14 +48,14 @@
     private static final long RECENTS_LAUNCH_DURATION = 250;
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final ActivityControlHelper<T> mHelper;
+    private final BaseActivityInterface<T> mHelper;
     // The id of the currently running task that is transitioning to overview.
     private final int mTargetTaskId;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
-    AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
+    AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
         mHelper = helper;
         mTargetTaskId = targetTaskId;
     }
@@ -70,7 +69,7 @@
     boolean onActivityReady(T activity, Boolean wasVisible) {
         activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
-        ActivityControlHelper.AnimationFactory factory =
+        BaseActivityInterface.AnimationFactory factory =
                 mHelper.prepareRecentsUI(activity, wasVisible,
                 false /* animate activity */, (controller) -> {
                     controller.dispatchOnStart();
@@ -80,7 +79,7 @@
                     anim.start();
                 });
         factory.onRemoteAnimationReceived(null);
-        factory.createActivityController(RECENTS_LAUNCH_DURATION);
+        factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
         factory.setRecentsAttachedToAppWindow(true, false);
         mActivity = activity;
         mRecentsView = mActivity.getOverviewPanel();
@@ -90,11 +89,12 @@
     /**
      * Create remote window animation from the currently running app to the overview panel.
      *
-     * @param targetCompats the target apps
+     * @param appTargets the target apps
      * @return animation from app to overview
      */
     @Override
-    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+    public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
         if (mRecentsView != null) {
             mRecentsView.setRunningTaskIconScaledDown(true);
         }
@@ -102,6 +102,7 @@
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
+                mHelper.onSwipeUpToRecentsComplete(mActivity);
                 if (mRecentsView != null) {
                     mRecentsView.animateUpRunningTaskIconScale();
                 }
@@ -113,18 +114,18 @@
             return anim;
         }
 
-        RemoteAnimationTargetSet targetSet =
-                new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
+        RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
+                wallpaperTargets, MODE_CLOSING);
 
         // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mTargetTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
             return anim;
         }
 
-        final ClipAnimationHelper clipHelper = new ClipAnimationHelper(mActivity);
+        final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(mActivity);
 
         // At this point, the activity is already started and laid-out. Get the home-bounds
         // relative to the screen using the rootView of the activity.
@@ -140,20 +141,22 @@
         clipHelper.updateTargetRect(targetRect);
         clipHelper.prepareAnimation(mActivity.getDeviceProfile(), false /* isOpening */);
 
-        ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
+        AppWindowAnimationHelper.TransformParams params = new AppWindowAnimationHelper.TransformParams()
                 .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView));
         ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
         valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
         valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
         valueAnimator.addUpdateListener((v) -> {
-            params.setProgress((float) v.getAnimatedValue());
-            clipHelper.applyTransform(targetSet, params);
+            params.setProgress((float) v.getAnimatedValue())
+                    .setTargetSet(targets)
+                    .setLauncherOnTop(true);
+            clipHelper.applyTransform(params);
         });
 
-        if (targetSet.isAnimatingHome()) {
+        if (targets.isAnimatingHome()) {
             // If we are animating home, fade in the opening targets
-            RemoteAnimationTargetSet openingSet =
-                    new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
+            RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets,
+                    wallpaperTargets, MODE_OPENING);
 
             TransactionCompat transaction = new TransactionCompat();
             valueAnimator.addUpdateListener((v) -> {
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 3f4dfd2..e1e994c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -25,7 +25,6 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 
 import android.animation.Animator;
 import android.annotation.TargetApi;
@@ -44,7 +43,6 @@
 import android.provider.Settings;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowManager;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.UiThread;
@@ -58,22 +56,22 @@
 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.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
+import java.util.ArrayList;
 import java.util.function.Consumer;
 
 /**
@@ -81,7 +79,7 @@
  */
 @TargetApi(Build.VERSION_CODES.Q)
 public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
-        implements SwipeAnimationListener {
+        implements RecentsAnimationListener {
 
     private static final String TAG = "BaseSwipeUpHandler";
     protected static final Rect TEMP_RECT = new Rect();
@@ -99,11 +97,11 @@
 
     protected final Context mContext;
     protected final OverviewComponentObserver mOverviewComponentObserver;
-    protected final ActivityControlHelper<T> mActivityControlHelper;
+    protected final BaseActivityInterface<T> mActivityInterface;
     protected final RecentsModel mRecentsModel;
     protected final int mRunningTaskId;
 
-    protected final ClipAnimationHelper mClipAnimationHelper;
+    protected final AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected final TransformParams mTransformParams = new TransformParams();
 
     private final Vibrator mVibrator;
@@ -116,7 +114,13 @@
     protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
 
     protected final ActivityInitListener mActivityInitListener;
-    protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+    protected final InputConsumerController mInputConsumer;
+
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
+
+    // Callbacks to be made once the recents animation starts
+    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
 
     protected T mActivity;
     protected Q mRecentsView;
@@ -131,21 +135,20 @@
     protected boolean mCanceled;
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
-    protected BaseSwipeUpHandler(Context context,
+    protected BaseSwipeUpHandler(Context context, GestureState gestureState,
             OverviewComponentObserver overviewComponentObserver,
             RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
         mContext = context;
         mOverviewComponentObserver = overviewComponentObserver;
-        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+        mActivityInterface = gestureState.getActivityInterface();
         mRecentsModel = recentsModel;
         mActivityInitListener =
-                mActivityControlHelper.createActivityInitListener(this::onActivityInit);
+                mActivityInterface.createActivityInitListener(this::onActivityInit);
         mRunningTaskId = runningTaskId;
-        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
-                this::createNewInputProxyHandler);
+        mInputConsumer = inputConsumer;
         mMode = SysUINavigationMode.getMode(context);
 
-        mClipAnimationHelper = new ClipAnimationHelper(context);
+        mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
         mVibrator = context.getSystemService(Vibrator.class);
         initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
@@ -211,8 +214,8 @@
     protected void linkRecentsViewScroll() {
         SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
             mTransformParams.setSyncTransactionApplier(applier);
-            mRecentsAnimationWrapper.runOnInit(() ->
-                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+            runOnRecentsAnimationStart(() ->
+                    mRecentsAnimationTargets.addDependentTransactionApplier(applier));
         });
 
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -220,20 +223,22 @@
                 updateFinalShift();
             }
         });
-        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
-        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+        mRecentsView.setAppWindowAnimationHelper(mAppWindowAnimationHelper);
+        runOnRecentsAnimationStart(() ->
+                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+                        mRecentsAnimationTargets));
     }
 
     protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
         // Launch the task user scrolled to (mRecentsView.getNextPage()).
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
+            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
                     true /* freezeTaskList */);
         } else {
-            int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
+            int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
             mFinishingRecentsAnimationForNewTaskId = taskId;
-            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+            mRecentsAnimationController.finish(true /* toRecents */, () -> {
                 if (!mCanceled) {
                     TaskView nextTask = mRecentsView.getTaskView(taskId);
                     if (nextTask != null) {
@@ -241,10 +246,10 @@
                                 success -> {
                                     resultCallback.accept(success);
                                     if (!success) {
-                                        mActivityControlHelper.onLaunchTaskFailed(mActivity);
+                                        mActivityInterface.onLaunchTaskFailed(mActivity);
                                         nextTask.notifyTaskLaunchFailed(TAG);
                                     } else {
-                                        mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+                                        mActivityInterface.onLaunchTaskSuccess(mActivity);
                                     }
                                 }, mMainThreadHandler);
                     }
@@ -254,18 +259,40 @@
                 mFinishingRecentsAnimationForNewTaskId = -1;
             });
         }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+    }
+
+    /**
+     * Runs the given {@param action} if the recents animation has already started, or queues it to
+     * be run when it is next started.
+     */
+    protected void runOnRecentsAnimationStart(Runnable action) {
+        if (mRecentsAnimationTargets == null) {
+            mRecentsAnimationStartCallbacks.add(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /**
+     * @return whether the recents animation has started and there are valid app targets.
+     */
+    protected boolean hasTargets() {
+        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
     }
 
     @Override
-    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+    public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
+            RecentsAnimationTargets targets) {
+        mRecentsAnimationController = recentsAnimationController;
+        mRecentsAnimationTargets = targets;
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         final Rect overviewStackBounds;
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
 
-        if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
-            overviewStackBounds = mActivityControlHelper
-                    .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+        if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+            overviewStackBounds = mActivityInterface
+                    .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
             dp = dp.getMultiWindowProfile(mContext, new Point(
                     overviewStackBounds.width(), overviewStackBounds.height()));
         } else {
@@ -273,16 +300,34 @@
             dp = dp.copy(mContext);
             overviewStackBounds = getStackBounds(dp);
         }
-        dp.updateInsets(targetSet.homeContentInsets);
-        dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
+        dp.updateInsets(targets.homeContentInsets);
+        dp.updateIsSeascape(mContext);
         if (runningTaskTarget != null) {
-            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+            mAppWindowAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
         }
 
-        mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
+        mAppWindowAnimationHelper.prepareAnimation(dp, false /* isOpening */);
         initTransitionEndpoints(dp);
 
-        mRecentsAnimationWrapper.setController(targetSet);
+        // Notify when the animation starts
+        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+                action.run();
+            }
+            mRecentsAnimationStartCallbacks.clear();
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+    }
+
+    @Override
+    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
     }
 
     private Rect getStackBounds(DeviceProfile dp) {
@@ -300,16 +345,16 @@
     protected void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
 
-        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+        mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
                 dp, mContext, TEMP_RECT);
         if (!dp.isMultiWindowMode) {
             // When updating the target rect, also update the home bounds since the location on
             // screen of the launcher window may be stale (position is not updated until first
             // traversal after the window is resized).  We only do this for non-multiwindow because
             // we otherwise use the minimized home bounds provided by the system.
-            mClipAnimationHelper.updateHomeBounds(getStackBounds(dp));
+            mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
         }
-        mClipAnimationHelper.updateTargetRect(TEMP_RECT);
+        mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
         if (mMode == Mode.NO_BUTTON) {
             // We can drag all the way to the top of the screen.
             mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
@@ -367,10 +412,13 @@
         float shift = mCurrentShift.value;
         float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
         float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
-                mClipAnimationHelper.getTargetRect().width());
-        mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
-        mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
-                mTransformParams);
+        mAppWindowAnimationHelper.getTargetRect().width());
+        mTransformParams.setProgress(shift)
+                .setOffsetX(offsetX)
+                .setOffsetScale(offsetScale)
+                .setTargetSet(mRecentsAnimationTargets)
+                .setLauncherOnTop(true);
+        mAppWindowAnimationHelper.applyTransform(mTransformParams);
     }
 
     private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
@@ -386,9 +434,11 @@
      */
     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
-        final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
-        final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet,
-                mTransformParams.setProgress(startProgress), false /* launcherOnTop */));
+        final RectF startRect = new RectF(
+                mAppWindowAnimationHelper.applyTransform(
+                        mTransformParams.setProgress(startProgress)
+                                .setTargetSet(mRecentsAnimationTargets)
+                                .setLauncherOnTop(false)));
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
         final View floatingView = homeAnimationFactory.getFloatingView();
@@ -404,7 +454,7 @@
 
         // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
         // rounding at the end of the animation.
-        float startRadius = mClipAnimationHelper.getCurrentCornerRadius();
+        float startRadius = mAppWindowAnimationHelper.getCurrentCornerRadius();
         float endRadius = startRect.width() / 6f;
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
@@ -435,12 +485,11 @@
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
                             * (1f - progress));
                 }
-                mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
-                        false /* launcherOnTop */);
+                mAppWindowAnimationHelper.applyTransform(mTransformParams);
 
                 if (isFloatingIconView) {
                     ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
-                            windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(),
+                            windowAlphaThreshold, mAppWindowAnimationHelper.getCurrentCornerRadius(),
                             false);
                 }
             }
@@ -468,7 +517,7 @@
 
     public interface Factory {
 
-        BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
+        BaseSwipeUpHandler newHandler(GestureState gestureState, RunningTaskInfo runningTask,
                 long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 8c5a788..8deb835 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -35,8 +35,8 @@
 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;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -44,14 +44,14 @@
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * {@link BaseActivityInterface} for recents when the default launcher is different than the
  * currently running one and apps should interact with the {@link RecentsActivity} as opposed
  * to the in-launcher one.
  */
-public final class FallbackActivityControllerHelper implements
-        ActivityControlHelper<RecentsActivity> {
+public final class FallbackActivityInterface implements
+        BaseActivityInterface<RecentsActivity> {
 
-    public FallbackActivityControllerHelper() { }
+    public FallbackActivityInterface() { }
 
     @Override
     public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
@@ -137,17 +137,17 @@
             boolean isAnimatingToRecents = false;
 
             @Override
-            public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+            public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
                 isAnimatingToRecents = targets != null && targets.isAnimatingHome();
                 if (!isAnimatingToRecents) {
                     rv.setContentAlpha(1);
                 }
-                createActivityController(getSwipeUpDestinationAndLength(
+                createActivityInterface(getSwipeUpDestinationAndLength(
                         activity.getDeviceProfile(), activity, new Rect()));
             }
 
             @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 AnimatorSet animatorSet = new AnimatorSet();
                 if (isAnimatingToRecents) {
                     ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
@@ -177,13 +177,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/LauncherActivityInterface.java
similarity index 87%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 4f97180..f6b3654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -38,7 +38,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.graphics.Region;
 import android.os.UserHandle;
 import android.view.MotionEvent;
 import android.view.View;
@@ -50,7 +49,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,20 +59,23 @@
 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;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for the in-launcher recents.
+ * {@link BaseActivityInterface} for the in-launcher recents.
  */
-public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
+public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
 
     private Runnable mAdjustInterpolatorsRunnable;
 
@@ -108,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
@@ -138,7 +147,7 @@
                 ? FloatingIconView.getFloatingIconView(activity, workspaceView,
                         true /* hideOriginal */, iconLocation, false /* isOpening */)
                 : null;
-
+        setLauncherHideBackArrow(true);
         return new HomeAnimationFactory() {
             @Nullable
             @Override
@@ -172,8 +181,6 @@
                 AnimatorSetBuilder builder = new AnimatorSetBuilder();
                 // setRecentsAttachedToAppWindow() will animate recents out.
                 builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
-                // We want to keep all apps content as GONE to avoid relayout during home animation.
-                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_UPDATE_ALL_APPS_VISIBILITY);
                 stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
                 builder.build().start();
 
@@ -202,16 +209,13 @@
         // This ensures then the next swipe up to all-apps starts from scroll 0.
         activity.getAppsView().reset(false /* animate */);
 
-        // Optimization, hide the all apps view to prevent layout while initializing
-        activity.getAppsView().getContentView().setVisibility(View.GONE);
-
         return new AnimationFactory() {
             private ShelfAnimState mShelfState;
             private boolean mIsAttachedToWindow;
 
             @Override
-            public void createActivityController(long transitionLength) {
-                createActivityControllerInternal(activity, fromState, transitionLength, callback);
+            public void createActivityInterface(long transitionLength) {
+                createActivityInterfaceInternal(activity, fromState, transitionLength, callback);
                 // Creating the activity controller animation sometimes reapplies the launcher state
                 // (because we set the animation as the current state animation), so we reapply the
                 // attached state here as well to ensure recents is shown/hidden appropriately.
@@ -273,22 +277,12 @@
                         INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
 
                 int runningTaskIndex = recentsView.getRunningTaskIndex();
-                if (runningTaskIndex == 0) {
+                if (runningTaskIndex == recentsView.getTaskViewStartIndex()) {
                     // If we are on the first task (we haven't quick switched), translate recents in
                     // from the side. Calculate the start translation based on current scale/scroll.
                     float currScale = recentsView.getScaleX();
                     float scrollOffsetX = recentsView.getScrollOffset();
-
-                    float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
-                    // The first task is hidden, so offset by its width.
-                    int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
-                    offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
-                    // Offset since scale pushes tasks outwards.
-                    offscreenX += firstTaskWidth * (currScale - 1) / 2;
-                    offscreenX = Math.max(0, offscreenX);
-                    if (recentsView.isRtl()) {
-                        offscreenX = -offscreenX;
-                    }
+                    float offscreenX = recentsView.getOffscreenTranslationX(currScale);
 
                     float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
                     float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
@@ -318,7 +312,7 @@
         };
     }
 
-    private void createActivityControllerInternal(Launcher activity, LauncherState fromState,
+    private void createActivityInterfaceInternal(Launcher activity, LauncherState fromState,
             long transitionLength, Consumer<AnimatorPlaybackController> callback) {
         LauncherState endState = OVERVIEW;
         if (fromState == endState) {
@@ -356,8 +350,7 @@
     private void playScaleDownAnim(AnimatorSet anim, Launcher launcher, LauncherState fromState,
             LauncherState endState) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage());
-        if (v == null) {
+        if (recentsView.getCurrentPageTaskView() == null) {
             return;
         }
 
@@ -385,7 +378,11 @@
             // recents as a whole needs to translate further to keep up with the app window.
             TaskView runningTaskView = recentsView.getRunningTaskView();
             if (runningTaskView == null) {
-                runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage());
+                runningTaskView = recentsView.getCurrentPageTaskView();
+                if (runningTaskView == null) {
+                    // There are no task views in LockTask mode when Overview is enabled.
+                    return;
+                }
             }
             TimeInterpolator oldInterpolator = translateY.getInterpolator();
             Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
@@ -407,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
@@ -447,8 +440,8 @@
     }
 
     @Override
-    public boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
-        return activeNavBarRegion.contains((int) ev.getX(), (int) ev.getY());
+    public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+        return deviceState.isInDeferredGestureRegion(ev);
     }
 
     @Override
@@ -484,4 +477,38 @@
     public void onLaunchTaskSuccess(Launcher launcher) {
         launcher.getStateManager().moveToRestState();
     }
+
+    @Override
+    public void closeOverlay() {
+        Launcher launcher = getCreatedActivity();
+        if (launcher == null) {
+            return;
+        }
+        LauncherOverlayManager om = launcher.getOverlayManager();
+        if (!launcher.isStarted() || launcher.isForceInvisible()) {
+            om.hideOverlay(false /* animate */);
+        } else {
+            om.hideOverlay(150);
+        }
+    }
+
+    @Override
+    public void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
+        Launcher launcher = getCreatedActivity();
+        RecentsView recentsView = launcher.getOverviewPanel();
+        if (recentsView == null) {
+            if (runnable != null) {
+                runnable.run();
+            }
+            return;
+        }
+        TaskView taskView = recentsView.getRunningTaskView();
+        if (taskView != null) {
+            taskView.setShowScreenshot(true);
+            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+            ViewUtils.postDraw(taskView, runnable);
+        } else if (runnable != null) {
+            runnable.run();
+        }
+    }
 }
\ No newline at end of file
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 c18b604..150c44d 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,13 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.ViewConfiguration;
 
+import androidx.annotation.BinderThread;
 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;
@@ -46,37 +45,43 @@
 public class OverviewCommandHelper {
 
     private final Context mContext;
-    private final ActivityManagerWrapper mAM;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
 
-    public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
+    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
+            OverviewComponentObserver observer) {
         mContext = context;
-        mAM = ActivityManagerWrapper.getInstance();
+        mDeviceState = deviceState;
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
+    @BinderThread
     public void onOverviewToggle() {
         // If currently screen pinning, do not enter overview
-        if (mAM.isScreenPinningActive()) {
+        if (mDeviceState.isScreenPinningActive()) {
             return;
         }
 
-        mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
+    @BinderThread
     public void onOverviewShown(boolean triggeredFromAltTab) {
         MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
     }
 
+    @BinderThread
     public void onOverviewHidden() {
         MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
+    @BinderThread
     public void onTip(int actionType, int viewType) {
         MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
@@ -93,14 +98,14 @@
         @Override
         protected boolean handleCommand(long elapsedTime) {
             // TODO: Go to the next page if started from alt-tab.
-            return mHelper.getVisibleRecentsView() != null;
+            return mActivityInterface.getVisibleRecentsView() != null;
         }
 
         @Override
         protected void onTransitionComplete() {
             // TODO(b/138729100) This doesn't execute first time launcher is run
             if (mTriggeredFromAltTab) {
-                RecentsView rv = (RecentsView) mHelper.getVisibleRecentsView();
+                RecentsView rv = (RecentsView) mActivityInterface.getVisibleRecentsView();
                 if (rv == null) {
                     return;
                 }
@@ -109,7 +114,7 @@
                 TaskView taskView = rv.getNextTaskView();
                 if (taskView == null) {
                     if (rv.getTaskViewCount() > 0) {
-                        taskView = (TaskView) rv.getPageAt(0);
+                        taskView = rv.getTaskViewAt(0);
                         taskView.requestFocus();
                     } else {
                         rv.requestFocus();
@@ -125,7 +130,7 @@
 
         @Override
         protected boolean handleCommand(long elapsedTime) {
-            RecentsView recents = (RecentsView) mHelper.getVisibleRecentsView();
+            RecentsView recents = (RecentsView) mActivityInterface.getVisibleRecentsView();
             if (recents == null) {
                 return false;
             }
@@ -141,7 +146,7 @@
 
     private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
 
-        protected final ActivityControlHelper<T> mHelper;
+        protected final BaseActivityInterface<T> mActivityInterface;
         private final long mCreateTime;
         private final AppToOverviewAnimationProvider<T> mAnimationProvider;
 
@@ -150,10 +155,10 @@
         private ActivityInitListener mListener;
 
         public RecentsActivityCommand() {
-            mHelper = mOverviewComponentObserver.getActivityControlHelper();
+            mActivityInterface = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
-            mAnimationProvider =
-                    new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
+            mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
+                    RecentsModel.getRunningTaskId());
 
             // Preload the plan
             mRecentsModel.getTasks(null);
@@ -161,9 +166,6 @@
 
         @Override
         public void run() {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "RecentsActivityCommand.run");
-            }
             long elapsedTime = mCreateTime - mLastToggleTime;
             mLastToggleTime = mCreateTime;
 
@@ -172,13 +174,13 @@
                 return;
             }
 
-            if (mHelper.switchToRecentsIfVisible(this::onTransitionComplete)) {
+            if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
                 // If successfully switched, then return
                 return;
             }
 
             // Otherwise, start overview.
-            mListener = mHelper.createActivityInitListener(this::onActivityReady);
+            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
                     this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
                     mAnimationProvider.getRecentsLaunchDuration());
@@ -188,7 +190,7 @@
             // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
             //       the menu activity which takes window focus, preventing the right condition from
             //       being run below
-            RecentsView recents = mHelper.getVisibleRecentsView();
+            RecentsView recents = mActivityInterface.getVisibleRecentsView();
             if (recents != null) {
                 // Launch the next task
                 recents.showNextTask();
@@ -205,14 +207,15 @@
             if (!mUserEventLogged) {
                 activity.getUserEventDispatcher().logActionCommand(
                         LauncherLogProto.Action.Command.RECENTS_BUTTON,
-                        mHelper.getContainerType(),
+                        mActivityInterface.getContainerType(),
                         LauncherLogProto.ContainerType.TASKSWITCHER);
                 mUserEventLogged = true;
             }
             return mAnimationProvider.onActivityReady(activity, wasVisible);
         }
 
-        private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+        private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+                RemoteAnimationTargetCompat[] wallpaperTargets) {
             if (LatencyTrackerCompat.isEnabled(mContext)) {
                 LatencyTrackerCompat.logToggleRecents(
                         (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
@@ -220,7 +223,8 @@
 
             mListener.unregister();
 
-            AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(targetCompats);
+            AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets,
+                    wallpaperTargets);
             animatorSet.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
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 72a14b5..daaa95b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -11,12 +11,18 @@
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
 
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
+    private final Context mContext;
     public QuickstepTestInformationHandler(Context context) {
+        mContext = context;
     }
 
     @Override
@@ -37,12 +43,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;
 
@@ -54,12 +54,10 @@
             case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
                 try {
                     final int leftMargin = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get();
+                            getRecentsView().getLeftGestureMargin()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
-                } catch (ExecutionException e) {
-                    e.printStackTrace();
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
+                } catch (ExecutionException | InterruptedException e) {
+                    throw new RuntimeException(e);
                 }
                 return response;
             }
@@ -67,18 +65,50 @@
             case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
                 try {
                     final int rightMargin = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()).
-                            get();
+                            getRecentsView().getRightGestureMargin()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
-                } catch (ExecutionException e) {
-                    e.printStackTrace();
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
+                } catch (ExecutionException | InterruptedException e) {
+                    throw new RuntimeException(e);
                 }
                 return response;
             }
+
+            case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
+                ArrayList<String> taskBaseIntentComponents = new ArrayList<>();
+                CountDownLatch latch = new CountDownLatch(1);
+                RecentsModel.INSTANCE.get(mContext).getTasks((tasks) -> {
+                    for (Task t : tasks) {
+                        taskBaseIntentComponents.add(
+                                t.key.baseIntent.getComponent().flattenToString());
+                    }
+                    latch.countDown();
+                });
+                try {
+                    latch.await(2, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                response.putStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        taskBaseIntentComponents);
+                return response;
+            }
         }
 
         return super.call(method);
     }
+
+    private RecentsView getRecentsView() {
+        OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
+                new RecentsAnimationDeviceState(mContext));
+        try {
+            return observer.getActivityInterface().getCreatedActivity().getOverviewPanel();
+        } finally {
+            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 9bdc98b..7b8ec4d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -16,12 +16,10 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl
-        .STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl
-        .STATUS_BAR_TRANSITION_PRE_DELAY;
-import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
@@ -40,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.util.AppWindowAnimationHelper;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
@@ -152,9 +150,10 @@
                 true /* startAtFrontOfQueue */) {
 
             @Override
-            public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
-                    AnimationResult result) {
-                AnimatorSet anim = composeRecentsLaunchAnimator(taskView, targetCompats);
+            public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                    RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+                AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets,
+                        wallpaperTargets);
                 anim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -174,12 +173,13 @@
      * Composes the animations for a launch from the recents list if possible.
      */
     private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
-            RemoteAnimationTargetCompat[] targets) {
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
         AnimatorSet target = new AnimatorSet();
-        boolean activityClosing = taskIsATargetWithMode(targets, getTaskId(), MODE_CLOSING);
-        ClipAnimationHelper helper = new ClipAnimationHelper(this);
-        target.play(getRecentsWindowAnimator(taskView, !activityClosing, targets, helper)
-                .setDuration(RECENTS_LAUNCH_DURATION));
+        boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
+        AppWindowAnimationHelper helper = new AppWindowAnimationHelper(this);
+        target.play(getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
+                wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION));
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
         if (activityClosing) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
deleted file mode 100644
index e51ba63..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ /dev/null
@@ -1,200 +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 android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.systemui.shared.system.InputConsumerController;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Wrapper around RecentsAnimationController to help with some synchronization
- */
-public class 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<>();
-
-    public SwipeAnimationTargetSet targetSet;
-
-    private boolean mWindowThresholdCrossed = false;
-
-    private final InputConsumerController mInputConsumerController;
-    private final Supplier<InputConsumer> mInputProxySupplier;
-
-    private InputConsumer mInputConsumer;
-    private boolean mTouchInProgress;
-
-    private boolean mFinishPending;
-
-    public RecentsAnimationWrapper(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> inputProxySupplier) {
-        mInputConsumerController = inputConsumerController;
-        mInputProxySupplier = inputProxySupplier;
-    }
-
-    public boolean hasTargets() {
-        return targetSet != null && targetSet.hasTargets();
-    }
-
-    @UiThread
-    public synchronized void setController(SwipeAnimationTargetSet targetSet) {
-        Preconditions.assertUIThread();
-        this.targetSet = targetSet;
-
-        if (targetSet == null) {
-            return;
-        }
-        targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
-
-        if (!mCallbacks.isEmpty()) {
-            for (Runnable action : new ArrayList<>(mCallbacks)) {
-                action.run();
-            }
-            mCallbacks.clear();
-        }
-    }
-
-    public synchronized void runOnInit(Runnable action) {
-        if (targetSet == null) {
-            mCallbacks.add(action);
-        } else {
-            action.run();
-        }
-    }
-
-    /** See {@link #finish(boolean, Runnable, boolean)} */
-    @UiThread
-    public void finish(boolean toRecents, Runnable onFinishComplete) {
-        finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
-    }
-
-    /**
-     * @param onFinishComplete A callback that runs on the main thread after the animation
-     *                         controller has finished on the background thread.
-     * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
-     *                          activity. If userLeaveHint is true, the activity will enter into
-     *                          picture-in-picture mode upon being paused.
-     */
-    @UiThread
-    public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
-        Preconditions.assertUIThread();
-        if (!toRecents) {
-            finishAndClear(false, onFinishComplete, sendUserLeaveHint);
-        } else {
-            if (mTouchInProgress) {
-                mFinishPending = true;
-                // Execute the callback
-                if (onFinishComplete != null) {
-                    onFinishComplete.run();
-                }
-            } else {
-                finishAndClear(true, onFinishComplete, sendUserLeaveHint);
-            }
-        }
-    }
-
-    private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
-            boolean sendUserLeaveHint) {
-        SwipeAnimationTargetSet controller = targetSet;
-        targetSet = null;
-        if (controller != null) {
-            controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
-        }
-    }
-
-    public void enableInputConsumer() {
-        if (targetSet != null) {
-            targetSet.enableInputConsumer();
-        }
-    }
-
-    /**
-     * Indicates that the gesture has crossed the window boundary threshold and system UI can be
-     * update the represent the window behind
-     */
-    public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
-        if (mWindowThresholdCrossed != windowThresholdCrossed) {
-            mWindowThresholdCrossed = windowThresholdCrossed;
-            if (targetSet != null) {
-                targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
-            }
-        }
-    }
-
-    public void enableInputProxy() {
-        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
-    }
-
-    private boolean onInputConsumerEvent(InputEvent ev) {
-        if (ev instanceof MotionEvent) {
-            onInputConsumerMotionEvent((MotionEvent) ev);
-        } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-            mInputConsumer.onKeyEvent((KeyEvent) ev);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
-        int action = ev.getAction();
-        if (action == ACTION_DOWN) {
-            mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
-            // Finish any pending actions
-            mTouchInProgress = false;
-            if (mFinishPending) {
-                mFinishPending = false;
-                finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
-            }
-        }
-        if (mInputConsumer != null) {
-            mInputConsumer.onMotionEvent(ev);
-        }
-
-        return true;
-    }
-
-    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-        if (targetSet != null) {
-            targetSet.controller.setDeferCancelUntilNextTransition(defer, screenshot);
-        }
-    }
-
-    public SwipeAnimationTargetSet getController() {
-        return targetSet;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 8783ee3..cd8e1a4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -22,21 +22,22 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 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;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.io.PrintWriter;
 
 /**
  * Utility class used to store state information shared across multiple transitions.
  */
-public class SwipeSharedState implements SwipeAnimationListener {
+public class SwipeSharedState implements RecentsAnimationListener {
 
     private OverviewComponentObserver mOverviewComponentObserver;
 
-    private RecentsAnimationListenerSet mRecentsAnimationListener;
-    private SwipeAnimationTargetSet mLastAnimationTarget;
+    private RecentsAnimationCallbacks mRecentsAnimationListener;
+    private RecentsAnimationController mLastRecentsAnimationController;
+    private RecentsAnimationTargets mLastAnimationTarget;
 
     private boolean mLastAnimationCancelled = false;
     private boolean mLastAnimationRunning = false;
@@ -52,13 +53,35 @@
     }
 
     @Override
-    public final void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        mLastAnimationTarget = targetSet;
+    public final void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        mLastRecentsAnimationController = controller;
+        mLastAnimationTarget = targets;
 
         mLastAnimationCancelled = false;
         mLastAnimationRunning = true;
     }
 
+    @Override
+    public final void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        if (thumbnailData != null) {
+            mOverviewComponentObserver.getActivityInterface().switchToScreenshot(thumbnailData,
+                    () -> {
+                        mLastRecentsAnimationController.cleanupScreenshot();
+                        clearAnimationState();
+                    });
+        } else {
+            clearAnimationState();
+        }
+    }
+
+    @Override
+    public final void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        if (mLastRecentsAnimationController == controller) {
+            mLastAnimationRunning = false;
+        }
+    }
+
     private void clearAnimationTarget() {
         if (mLastAnimationTarget != null) {
             mLastAnimationTarget.release();
@@ -66,8 +89,7 @@
         }
     }
 
-    @Override
-    public final void onRecentsAnimationCanceled() {
+    private void clearAnimationState() {
         clearAnimationTarget();
 
         mLastAnimationCancelled = true;
@@ -77,12 +99,13 @@
     private void clearListenerState(boolean finishAnimation) {
         if (mRecentsAnimationListener != null) {
             mRecentsAnimationListener.removeListener(this);
-            mRecentsAnimationListener.cancelListener();
-            if (mLastAnimationRunning && mLastAnimationTarget != null) {
+            mRecentsAnimationListener.notifyAnimationCanceled();
+            if (mLastAnimationRunning && mLastRecentsAnimationController != null) {
                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                         finishAnimation
-                                ? mLastAnimationTarget::finishAnimation
-                                : mLastAnimationTarget::cancelAnimation);
+                                ? mLastRecentsAnimationController::finishAnimationToHome
+                                : mLastRecentsAnimationController::finishAnimationToApp);
+                mLastRecentsAnimationController = null;
                 mLastAnimationTarget = null;
             }
         }
@@ -92,13 +115,7 @@
         mLastAnimationRunning = false;
     }
 
-    private void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet) {
-        if (mLastAnimationTarget == targetSet) {
-            mLastAnimationRunning = false;
-        }
-    }
-
-    public RecentsAnimationListenerSet newRecentsAnimationListenerSet() {
+    public RecentsAnimationCallbacks newRecentsAnimationCallbacks() {
         Preconditions.assertUIThread();
 
         if (mLastAnimationRunning) {
@@ -112,22 +129,22 @@
 
         clearListenerState(false /* finishAnimation */);
         boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
-                : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
-        mRecentsAnimationListener = new RecentsAnimationListenerSet(
-                shouldMinimiseSplitScreen, this::onSwipeAnimationFinished);
+                : mOverviewComponentObserver.getActivityInterface().shouldMinimizeSplitScreen();
+        mRecentsAnimationListener = new RecentsAnimationCallbacks(shouldMinimiseSplitScreen);
         mRecentsAnimationListener.addListener(this);
         return mRecentsAnimationListener;
     }
 
-    public RecentsAnimationListenerSet getActiveListener() {
+    public RecentsAnimationCallbacks getActiveListener() {
         return mRecentsAnimationListener;
     }
 
-    public void applyActiveRecentsAnimationState(SwipeAnimationListener listener) {
-        if (mLastAnimationTarget != null) {
-            listener.onRecentsAnimationStart(mLastAnimationTarget);
+    public void applyActiveRecentsAnimationState(RecentsAnimationListener listener) {
+        if (mLastRecentsAnimationController != null) {
+            listener.onRecentsAnimationStart(mLastRecentsAnimationController,
+                    mLastAnimationTarget);
         } else if (mLastAnimationCancelled) {
-            listener.onRecentsAnimationCanceled();
+            listener.onRecentsAnimationCanceled(null);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index 1af0db0..5a2e3ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -28,9 +28,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
@@ -44,7 +42,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
@@ -240,13 +237,7 @@
 
         @Override
         protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-            try {
-                sysUiProxy.onSplitScreenInvoked();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
-                return false;
-            }
+            SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
             activity.getUserEventDispatcher().logActionOnControl(TAP,
                     LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
             return true;
@@ -293,8 +284,7 @@
         @Override
         public View.OnClickListener getOnClickListener(
                 BaseDraggingActivity activity, TaskView taskView) {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-            if (sysUiProxy == null) {
+            if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
                 return null;
             }
             if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
@@ -307,11 +297,8 @@
             return view -> {
                 Consumer<Boolean> resultCallback = success -> {
                     if (success) {
-                        try {
-                            sysUiProxy.startScreenPinning(taskView.getTask().key.id);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to start screen pinning: ", e);
-                        }
+                        SystemUiProxy.INSTANCE.get(activity).startScreenPinning(
+                                taskView.getTask().key.id);
                     } else {
                         taskView.notifyTaskLaunchFailed(TAG);
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 6897c1e..2522c0f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
@@ -30,14 +31,18 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
@@ -111,15 +116,18 @@
      * animation.
      */
     public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
-            RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, final AppWindowAnimationHelper inOutHelper) {
         SyncRtSurfaceTransactionApplierCompat applier =
                 new SyncRtSurfaceTransactionApplierCompat(v);
-        ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
-                .setSyncTransactionApplier(applier);
-
-        final RemoteAnimationTargetSet targetSet =
-                new RemoteAnimationTargetSet(targets, MODE_OPENING);
-        targetSet.addDependentTransactionApplier(applier);
+        final RemoteAnimationTargets targets =
+                new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
+        targets.addDependentTransactionApplier(applier);
+        AppWindowAnimationHelper.TransformParams params =
+                new AppWindowAnimationHelper.TransformParams()
+                    .setSyncTransactionApplier(applier)
+                    .setTargetSet(targets)
+                    .setLauncherOnTop(true);
 
         final RecentsView recentsView = v.getRecentsView();
         final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
@@ -140,7 +148,7 @@
                         BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
                         true /* isOpening */);
                 inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
-                        targetSet.apps.length == 0 ? null : targetSet.apps[0]);
+                        targets.apps.length == 0 ? null : targets.apps[0]);
 
                 mThumbnailRect = new RectF(inOutHelper.getTargetRect());
                 mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
@@ -151,7 +159,33 @@
             public void onUpdate(float percent) {
                 // TODO: Take into account the current fullscreen progress for animating the insets
                 params.setProgress(1 - percent);
-                RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
+                RectF taskBounds;
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                    List<SurfaceParams> surfaceParamsList = new ArrayList<>();
+                    // Append the surface transform params for the app that's being opened.
+                    Collections.addAll(surfaceParamsList, inOutHelper.getSurfaceParams(params));
+
+                    AppWindowAnimationHelper liveTileAnimationHelper =
+                            v.getRecentsView().getClipAnimationHelper();
+                    if (liveTileAnimationHelper != null) {
+                        // Append the surface transform params for the live tile app.
+                        AppWindowAnimationHelper.TransformParams liveTileParams =
+                                v.getRecentsView().getLiveTileParams(true /* mightNeedToRefill */);
+                        if (liveTileParams != null) {
+                            Collections.addAll(surfaceParamsList,
+                                    liveTileAnimationHelper.getSurfaceParams(liveTileParams));
+                        }
+                    }
+                    // Apply surface transform using the surface params list.
+                    AppWindowAnimationHelper.applySurfaceParams(params.syncTransactionApplier,
+                            surfaceParamsList.toArray(new SurfaceParams[surfaceParamsList.size()]));
+                    // Get the task bounds for the app that's being opened after surface transform
+                    // update.
+                    taskBounds = inOutHelper.updateCurrentRect(params);
+                } else {
+                    taskBounds = inOutHelper.applyTransform(params);
+                }
+
                 int taskIndex = recentsView.indexOfChild(v);
                 int centerTaskIndex = recentsView.getCurrentPage();
                 boolean parallaxCenterAndAdjacentTask = taskIndex != centerTaskIndex;
@@ -168,7 +202,7 @@
         appAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                targetSet.release();
+                targets.release();
             }
         });
         return appAnimator;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 716e8f4..4b7ae6f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static android.view.MotionEvent.ACTION_DOWN;
+
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
@@ -26,15 +27,6 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
 
 import android.annotation.TargetApi;
@@ -42,66 +34,53 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.Service;
 import android.app.TaskInfo;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
-import android.graphics.Point;
-import android.graphics.RectF;
 import android.graphics.Region;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.InputEvent;
 import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.WindowManager;
+
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.logging.EventLogArray;
+import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.DejankBinderTracker;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
-import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
+import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
 import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
-import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
+import com.android.quickstep.inputconsumers.QuickCaptureInputConsumer;
 import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
 import com.android.systemui.shared.system.TaskInfoCompat;
 
 import java.io.FileDescriptor;
@@ -132,50 +111,42 @@
  */
 @TargetApi(Build.VERSION_CODES.Q)
 public class TouchInteractionService extends Service implements
-        NavigationModeChangeListener, DisplayListener {
-
-    /**
-     * NOTE: This value should be kept same as
-     * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
-     */
-    public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
-
-
-    public static final EventLogArray TOUCH_INTERACTION_LOG =
-            new EventLogArray("touch_interaction_log", 40);
+        NavigationModeChangeListener {
 
     private static final String TAG = "TouchInteractionService";
 
     private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
+    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
     private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
     private int mBackGestureNotificationCounter = -1;
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
-        public void onActiveNavBarRegionChanges(Region region) {
-            mActiveNavBarRegion = region;
-        }
-
+        @BinderThread
         public void onInitialize(Bundle bundle) {
-            mISystemUiProxy = ISystemUiProxy.Stub
-                    .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+            ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
+                    bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+            MAIN_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(TouchInteractionService.this)
+                    .setProxy(proxy));
             MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
-            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
             MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
             sIsInitialized = true;
         }
 
+        @BinderThread
         @Override
         public void onOverviewToggle() {
             mOverviewCommandHelper.onOverviewToggle();
         }
 
+        @BinderThread
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
             mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
         }
 
+        @BinderThread
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
             if (triggeredFromAltTab && !triggeredFromHomeKey) {
@@ -184,44 +155,54 @@
             }
         }
 
+        @BinderThread
         @Override
         public void onTip(int actionType, int viewType) {
             mOverviewCommandHelper.onTip(actionType, viewType);
         }
 
+        @BinderThread
         @Override
         public void onAssistantAvailable(boolean available) {
-            mAssistantAvailable = available;
+            MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available));
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
+        @BinderThread
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
-            mLastAssistantVisibility = visibility;
-            MAIN_EXECUTOR.execute(
-                    TouchInteractionService.this::onAssistantVisibilityChanged);
+            MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility));
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
+        @BinderThread
         public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
                 boolean gestureSwipeLeft) {
             if (mOverviewComponentObserver == null) {
                 return;
             }
 
-            final ActivityControlHelper activityControl =
-                    mOverviewComponentObserver.getActivityControlHelper();
+            final BaseActivityInterface activityInterface =
+                    mOverviewComponentObserver.getActivityInterface();
             UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
-                    isButton, gestureSwipeLeft, activityControl.getContainerType());
+                    isButton, gestureSwipeLeft, activityInterface.getContainerType());
 
             if (completed && !isButton && shouldNotifyBackGesture()) {
                 UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
             }
         }
 
+        @BinderThread
         public void onSystemUiStateChanged(int stateFlags) {
-            mSystemUiStateFlags = stateFlags;
+            MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags));
             MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
         }
 
+        @BinderThread
+        public void onActiveNavBarRegionChanges(Region region) {
+            MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
+        }
+
         /** Deprecated methods **/
         public void onQuickStep(MotionEvent motionEvent) { }
 
@@ -267,77 +248,30 @@
 
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
-    private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
     private OverviewComponentObserver mOverviewComponentObserver;
-    private OverviewInteractionState mOverviewInteractionState;
-    private OverviewCallbacks mOverviewCallbacks;
-    private TaskOverlayFactory mTaskOverlayFactory;
     private InputConsumerController mInputConsumer;
-    private boolean mAssistantAvailable;
-    private float mLastAssistantVisibility = 0;
-    private @SystemUiStateFlags int mSystemUiStateFlags;
-
-    private boolean mIsUserUnlocked;
-    private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                initWhenUserUnlocked();
-            }
-        }
-    };
+    private RecentsAnimationDeviceState mDeviceState;
 
     private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
 
-    private Region mActiveNavBarRegion = new Region();
-
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
     private Mode mMode = Mode.THREE_BUTTONS;
-    private int mDefaultDisplayId;
-    private final RectF mSwipeTouchRegion = new RectF();
-    private final RectF mAssistantLeftRegion = new RectF();
-    private final RectF mAssistantRightRegion = new RectF();
-
-    private ComponentName mGestureBlockingActivity;
-
-    private Region mExclusionRegion;
-    private SystemGestureExclusionListenerCompat mExclusionListener;
 
     @Override
     public void onCreate() {
         super.onCreate();
+        mDeviceState = new RecentsAnimationDeviceState(this);
+        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
 
         // Initialize anything here that is needed in direct boot mode.
-        // Everything else should be initialized in initWhenUserUnlocked() below.
+        // Everything else should be initialized in onUserUnlocked() below.
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
 
-        if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
-            initWhenUserUnlocked();
-        } else {
-            mIsUserUnlocked = false;
-            registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-        }
-
-        mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay()
-                .getDisplayId();
-        String blockingActivity = getString(R.string.gesture_blocking_activity);
-        mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null :
-                ComponentName.unflattenFromString(blockingActivity);
-
-        mExclusionListener = new SystemGestureExclusionListenerCompat(mDefaultDisplayId) {
-            @Override
-            @BinderThread
-            public void onExclusionChanged(Region region) {
-                // Assignments are atomic, it should be safe on binder thread
-                mExclusionRegion = region;
-            }
-        };
-
         onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this));
         sConnected = true;
     }
@@ -360,75 +294,24 @@
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
         }
-        if (!mMode.hasGestures || mISystemUiProxy == null) {
+        disposeEventHandlers();
+        if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) {
             return;
         }
-        disposeEventHandlers();
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 2");
         }
 
-        try {
-            mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
-                    .monitorGestureInput("swipe-up", mDefaultDisplayId), KEY_EXTRA_INPUT_MONITOR);
-            mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
-                    mMainChoreographer, this::onInputEvent);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to create input monitor", e);
-        }
-        initTouchBounds();
-    }
-
-    private int getNavbarSize(String resName) {
-        return ResourceUtils.getNavbarSize(resName, getResources());
-    }
-
-    private void initTouchBounds() {
-        if (!mMode.hasGestures) {
-            return;
+        Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
+                mDeviceState.getDisplayId());
+        mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
+        mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+                mMainChoreographer, this::onInputEvent);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
         }
 
-        Display defaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
-        Point realSize = new Point();
-        defaultDisplay.getRealSize(realSize);
-        mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
-        if (mMode == Mode.NO_BUTTON) {
-            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
-            mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - touchHeight;
-
-            final int assistantWidth = getResources()
-                    .getDimensionPixelSize(R.dimen.gestures_assistant_width);
-            final float assistantHeight = Math.max(touchHeight,
-                    QuickStepContract.getWindowCornerRadius(getResources()));
-            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeTouchRegion.bottom;
-            mAssistantLeftRegion.top = mAssistantRightRegion.top =
-                    mSwipeTouchRegion.bottom - assistantHeight;
-
-            mAssistantLeftRegion.left = 0;
-            mAssistantLeftRegion.right = assistantWidth;
-
-            mAssistantRightRegion.right = mSwipeTouchRegion.right;
-            mAssistantRightRegion.left = mSwipeTouchRegion.right - assistantWidth;
-        } else {
-            mAssistantLeftRegion.setEmpty();
-            mAssistantRightRegion.setEmpty();
-            switch (defaultDisplay.getRotation()) {
-                case Surface.ROTATION_90:
-                    mSwipeTouchRegion.left = mSwipeTouchRegion.right
-                            - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
-                    break;
-                case Surface.ROTATION_270:
-                    mSwipeTouchRegion.right = mSwipeTouchRegion.left
-                            + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
-                    break;
-                default:
-                    mSwipeTouchRegion.top = mSwipeTouchRegion.bottom
-                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
-            }
-        }
+        mDeviceState.updateGestureTouchRegions();
     }
 
     @Override
@@ -436,55 +319,21 @@
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onNavigationModeChanged " + newMode);
         }
-        if (mMode.hasGestures != newMode.hasGestures) {
-            if (newMode.hasGestures) {
-                getSystemService(DisplayManager.class).registerDisplayListener(
-                        this, MAIN_EXECUTOR.getHandler());
-            } else {
-                getSystemService(DisplayManager.class).unregisterDisplayListener(this);
-            }
-        }
         mMode = newMode;
-
-        disposeEventHandlers();
         initInputMonitor();
-
-        if (mMode == Mode.NO_BUTTON) {
-            mExclusionListener.register();
-        } else {
-            mExclusionListener.unregister();
-        }
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
-    @Override
-    public void onDisplayAdded(int i) { }
-
-    @Override
-    public void onDisplayRemoved(int i) { }
-
-    @Override
-    public void onDisplayChanged(int displayId) {
-        if (displayId != mDefaultDisplayId) {
-            return;
-        }
-
-        initTouchBounds();
-    }
-
-    private void initWhenUserUnlocked() {
+    @UiThread
+    public void onUserUnlocked() {
         mRecentsModel = RecentsModel.INSTANCE.get(this);
-        mOverviewComponentObserver = new OverviewComponentObserver(this);
-
-        mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
-        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
-        mOverviewCallbacks = OverviewCallbacks.get(this);
-        mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
+        mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
+        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
+                mOverviewComponentObserver);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
-        mIsUserUnlocked = true;
 
         sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
         mInputConsumer.registerInputConsumer();
-        onSystemUiProxySet();
         onSystemUiFlagsChanged();
         onAssistantVisibilityChanged();
 
@@ -492,51 +341,56 @@
         // new ModelPreload().start(this);
         mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
                 .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
-
-        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
-    @UiThread
-    private void onSystemUiProxySet() {
-        if (mIsUserUnlocked) {
-            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+    private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
+        if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) {
+            // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
+            // mode doesn't have gestures
+            return;
+        }
+
+        // Reset home bounce seen on quick step enabled for first time
+        SharedPreferences sharedPrefs = Utilities.getPrefs(this);
+        if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
+            sharedPrefs.edit()
+                    .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
+                    .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+                    .apply();
         }
     }
 
     @UiThread
     private void onSystemUiFlagsChanged() {
-        if (mIsUserUnlocked) {
-            mOverviewInteractionState.setSystemUiStateFlags(mSystemUiStateFlags);
-            mOverviewComponentObserver.onSystemUiStateChanged(mSystemUiStateFlags);
+        if (mDeviceState.isUserUnlocked()) {
+            SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(
+                    mDeviceState.getSystemUiStateFlags());
+            mOverviewComponentObserver.onSystemUiStateChanged();
         }
     }
 
     @UiThread
     private void onAssistantVisibilityChanged() {
-        if (mIsUserUnlocked) {
-            mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
-                    mLastAssistantVisibility);
+        if (mDeviceState.isUserUnlocked()) {
+            mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
+                    mDeviceState.getAssistantVisibility());
         }
     }
 
     @Override
     public void onDestroy() {
         sIsInitialized = false;
-        if (mIsUserUnlocked) {
+        if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.onDestroy();
         }
         disposeEventHandlers();
-        if (mMode.hasGestures) {
-            getSystemService(DisplayManager.class).unregisterDisplayListener(this);
-        }
+        mDeviceState.destroy();
+        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+        SystemUiProxy.INSTANCE.get(this).setProxy(null);
 
         sConnected = false;
-        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
-        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
-        mExclusionListener.unregister();
-
         super.onDestroy();
     }
 
@@ -547,6 +401,7 @@
     }
 
     private void onInputEvent(InputEvent ev) {
+        DejankBinderTracker.allowBinderTrackingInTests();
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onInputEvent " + ev);
         }
@@ -557,57 +412,45 @@
 
         MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
-            mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId();
+            GestureState newGestureState = new GestureState(
+                    mOverviewComponentObserver.getActivityInterface());
+
+            mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId();
             sSwipeSharedState.setLogTraceId(mLogId);
 
-            if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
+            if (mDeviceState.isInSwipeUpTouchRegion(event)) {
                 boolean useSharedState = mConsumer.useSharedSwipeState();
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(useSharedState, event);
-                TOUCH_INTERACTION_LOG.addLog("setInputConsumer", mConsumer.getType());
+                mConsumer = newConsumer(newGestureState, useSharedState, event);
+                ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
-            } else if (mIsUserUnlocked && mMode == Mode.NO_BUTTON
-                    && canTriggerAssistantAction(event)) {
+            } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON
+                    && mDeviceState.canTriggerAssistantAction(event)) {
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
-                mUncheckedConsumer =
-                        new AssistantTouchConsumer(this, mISystemUiProxy,
-                                mOverviewComponentObserver.getActivityControlHelper(),
-                                InputConsumer.NO_OP, mInputMonitorCompat);
+                mUncheckedConsumer = new AssistantInputConsumer(this, newGestureState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
         }
 
-        TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
+        ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
         mUncheckedConsumer.onMotionEvent(event);
+        DejankBinderTracker.disallowBinderTrackingInTests();
     }
 
-    private boolean validSystemUiFlags() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
-                && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
-                        || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
-    }
+    private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState,
+            MotionEvent event) {
+        boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
 
-    private boolean canTriggerAssistantAction(MotionEvent ev) {
-        return mAssistantAvailable
-                && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                && (mAssistantLeftRegion.contains(ev.getX(), ev.getY()) ||
-                    mAssistantRightRegion.contains(ev.getX(), ev.getY()))
-                && !ActivityManagerWrapper.getInstance().isLockToAppActive();
-    }
-
-    private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
-        boolean isInValidSystemUiState = validSystemUiFlags();
-
-        if (!mIsUserUnlocked) {
-            if (isInValidSystemUiState) {
+        if (!mDeviceState.isUserUnlocked()) {
+            if (canStartSystemGesture) {
                 // This handles apps launched in direct boot mode (e.g. dialer) as well as apps
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
-                return createDeviceLockedInputConsumer(mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
+                return createDeviceLockedInputConsumer(gestureState,
+                        mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
             } else {
                 return mResetGestureInputConsumer;
             }
@@ -615,53 +458,54 @@
 
         // When using sharedState, bypass systemState check as this is a followup gesture and the
         // first gesture started in a valid system state.
-        InputConsumer base = isInValidSystemUiState || useSharedState
-                ? newBaseConsumer(useSharedState, event) : mResetGestureInputConsumer;
+        InputConsumer base = canStartSystemGesture || useSharedState
+                ? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer;
         if (mMode == Mode.NO_BUTTON) {
-            final ActivityControlHelper activityControl =
-                    mOverviewComponentObserver.getActivityControlHelper();
-            if (canTriggerAssistantAction(event)) {
-                base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
-                        mInputMonitorCompat);
+            if (mDeviceState.canTriggerAssistantAction(event)) {
+                base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat);
             }
 
-            if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
+            if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
+                // Put the Compose gesture as higher priority than the Assistant or base gestures
+                base = new QuickCaptureInputConsumer(this, gestureState, base, mInputMonitorCompat);
+            }
+
+            if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
-                base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
+                base = new ScreenPinnedInputConsumer(this, gestureState);
             }
 
-            if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
-                base = new AccessibilityInputConsumer(this, mISystemUiProxy,
-                        (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
-                        mInputMonitorCompat, mSwipeTouchRegion);
+            if (mDeviceState.isAccessibilityMenuAvailable()) {
+                base = new AccessibilityInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
             }
         } else {
-            if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
+            if (mDeviceState.isScreenPinningActive()) {
                 base = mResetGestureInputConsumer;
             }
         }
         return base;
     }
 
-    private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
-        RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+    private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState,
+            MotionEvent event) {
+        RunningTaskInfo runningTaskInfo = DejankBinderTracker.whitelistIpcs(
+                () -> mAM.getRunningTask(0));
         if (!useSharedState) {
             sSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
-        if ((mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) {
+        if (mDeviceState.isKeyguardShowingOccluded()) {
             // This handles apps showing over the lockscreen (e.g. camera)
-            return createDeviceLockedInputConsumer(runningTaskInfo);
+            return createDeviceLockedInputConsumer(gestureState, runningTaskInfo);
         }
 
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-
         boolean forceOverviewInputConsumer = false;
         if (isExcludedAssistant(runningTaskInfo)) {
             // In the case where we are in the excluded assistant state, ignore it and treat the
             // running activity as the task behind the assistant
-            runningTaskInfo = mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT);
+            runningTaskInfo = DejankBinderTracker.whitelistIpcs(
+                    () -> mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
             if (!ActivityManagerWrapper.isHomeTask(runningTaskInfo)) {
                 final ComponentName homeComponent =
                     mOverviewComponentObserver.getHomeIntent().getComponent();
@@ -678,17 +522,18 @@
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
             info.id = sSwipeSharedState.nextRunningTaskId;
-            return createOtherActivityInputConsumer(event, info);
-        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
+            return createOtherActivityInputConsumer(gestureState, event, info);
+        } else if (sSwipeSharedState.goingToLauncher
+                || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
-            return createOverviewInputConsumer(event);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
-            return createOverviewInputConsumer(event);
-        } else if (mGestureBlockingActivity != null && runningTaskInfo != null
-                && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
+            return createOverviewInputConsumer(gestureState, event);
+        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode()) {
+            return createOverviewInputConsumer(gestureState, event);
+        } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) {
             return mResetGestureInputConsumer;
         } else {
-            return createOtherActivityInputConsumer(event, runningTaskInfo);
+            return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo);
         }
     }
 
@@ -698,15 +543,8 @@
                 && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
     }
 
-    private boolean disableHorizontalSwipe(MotionEvent event) {
-        // mExclusionRegion can change on binder thread, use a local instance here.
-        Region exclusionRegion = mExclusionRegion;
-        return mMode == Mode.NO_BUTTON && exclusionRegion != null
-                && exclusionRegion.contains((int) event.getX(), (int) event.getY());
-    }
-
-    private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
-            RunningTaskInfo runningTaskInfo) {
+    private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
+            MotionEvent event, RunningTaskInfo runningTaskInfo) {
 
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
@@ -715,40 +553,40 @@
             shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
             factory = mFallbackNoButtonFactory;
         } else {
-            shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
-                    .deferStartingActivity(mActiveNavBarRegion, event);
+            shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
+                    event);
             factory = mWindowTreansformFactory;
         }
 
-        return new OtherActivityInputConsumer(this, runningTaskInfo,
-                shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
-                sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
-                disableHorizontalSwipe(event), factory, mLogId);
+        final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
+        return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo,
+                shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat,
+                disableHorizontalSwipe, factory, mLogId);
     }
 
-    private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
+    private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
+            RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
-            return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
-                    mSwipeTouchRegion, taskInfo.taskId, mLogId);
+            return new DeviceLockedInputConsumer(this, mDeviceState, gestureState,
+                    sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId);
         } else {
             return mResetGestureInputConsumer;
         }
     }
 
-    public InputConsumer createOverviewInputConsumer(MotionEvent event) {
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        BaseDraggingActivity activity = activityControl.getCreatedActivity();
+    public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) {
+        BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
         if (activity == null) {
             return mResetGestureInputConsumer;
         }
 
         if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
-            return new OverviewInputConsumer(activity, mInputMonitorCompat,
+            return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
-            return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
-                    disableHorizontalSwipe(event));
+            final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
+            return new OverviewWithoutFocusInputConsumer(activity, gestureState,
+                    mInputMonitorCompat, disableHorizontalSwipe);
         }
     }
 
@@ -763,7 +601,7 @@
     }
 
     private void preloadOverview(boolean fromInit) {
-        if (!mIsUserUnlocked) {
+        if (!mDeviceState.isUserUnlocked()) {
             return;
         }
         if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
@@ -777,11 +615,11 @@
             return;
         }
 
-        final ActivityControlHelper<BaseDraggingActivity> activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        if (activityControl.getCreatedActivity() == null) {
+        final BaseActivityInterface<BaseDraggingActivity> activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        if (activityInterface.getCreatedActivity() == null) {
             // Make sure that UI states will be initialized.
-            activityControl.createActivityInitListener((activity, wasVisible) -> {
+            activityInterface.createActivityInitListener((activity, wasVisible) -> {
                 AppLaunchTracker.INSTANCE.get(activity);
                 return false;
             }).register();
@@ -799,12 +637,12 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        if (!mIsUserUnlocked) {
+        if (!mDeviceState.isUserUnlocked()) {
             return;
         }
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+        final BaseActivityInterface activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        final BaseDraggingActivity activity = activityInterface.getCreatedActivity();
         if (activity == null || activity.isStarted()) {
             // We only care about the existing background activity.
             return;
@@ -832,17 +670,11 @@
             }
         } else {
             // Dump everything
+            mDeviceState.dump(pw);
             pw.println("TouchState:");
             pw.println("  navMode=" + mMode);
-            pw.println("  validSystemUiFlags=" + validSystemUiFlags());
-            pw.println("  systemUiFlags=" + mSystemUiStateFlags);
-            pw.println("  systemUiFlagsDesc="
-                    + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
-            pw.println("  assistantAvailable=" + mAssistantAvailable);
-            pw.println("  assistantDisabled="
-                    + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
             boolean resumed = mOverviewComponentObserver != null
-                    && mOverviewComponentObserver.getActivityControlHelper().isResumed();
+                    && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
@@ -856,8 +688,7 @@
             pw.println("  ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
             pw.println("  ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
             pw.println("  FAKE_LANDSCAPE_UI=" + FAKE_LANDSCAPE_UI.get());
-            TOUCH_INTERACTION_LOG.dump("", pw);
-
+            ActiveGestureLog.INSTANCE.dump("", pw);
         }
     }
 
@@ -869,26 +700,30 @@
     private void onCommand(PrintWriter pw, ArgList args) {
         switch (args.nextArg()) {
             case "clear-touch-log":
-                TOUCH_INTERACTION_LOG.clear();
+                ActiveGestureLog.INSTANCE.clear();
                 break;
         }
     }
 
-    private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return  new WindowTransformSwipeHandler(runningTask, this, touchTimeMs,
-                mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
+    private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState,
+            RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
+            boolean isLikelyToStartNewTask) {
+        return  new WindowTransformSwipeHandler(this, mDeviceState, gestureState, runningTask,
+                touchTimeMs, mOverviewComponentObserver, continuingLastGesture, mInputConsumer,
+                mRecentsModel);
     }
 
-    private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
-                mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+    private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
+            RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
+            boolean isLikelyToStartNewTask) {
+        return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver,
+                runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask,
+                continuingLastGesture);
     }
 
     protected boolean shouldNotifyBackGesture() {
         return mBackGestureNotificationCounter > 0 &&
-                mGestureBlockingActivity != null;
+                mDeviceState.getGestureBlockedActivityPackage() != null;
     }
 
     @WorkerThread
@@ -898,7 +733,7 @@
             Utilities.getDevicePrefs(this).edit()
                     .putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
             sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(
-                    mGestureBlockingActivity.getPackageName()));
+                    mDeviceState.getGestureBlockedActivityPackage()));
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
new file mode 100644
index 0000000..cbb6ad4
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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 android.graphics.Canvas;
+import android.view.View;
+
+import com.android.systemui.shared.system.WindowCallbacksCompat;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility class for helpful methods related to {@link View} objects.
+ */
+public class ViewUtils {
+
+    /** See {@link #postDraw(View, Runnable, BooleanSupplier)}} */
+    public static boolean postDraw(View view, Runnable onFinishRunnable) {
+        return postDraw(view, onFinishRunnable, () -> false);
+    }
+
+    /**
+     * Inject some addition logic in order to make sure that the view is updated smoothly post
+     * draw, and allow addition task to be run after view update.
+     *
+     * @param onFinishRunnable runnable to be run right after the view finishes drawing.
+     */
+    public static boolean postDraw(View view, Runnable onFinishRunnable, BooleanSupplier canceled) {
+        // Defer finishing the animation until the next launcher frame with the
+        // new thumbnail
+        return new WindowCallbacksCompat(view) {
+            // The number of frames to defer until we actually finish the animation
+            private int mDeferFrameCount = 2;
+
+            @Override
+            public void onPostDraw(Canvas canvas) {
+                // If we were cancelled after this was attached, do not update
+                // the state.
+                if (canceled.getAsBoolean()) {
+                    detach();
+                    return;
+                }
+
+                if (mDeferFrameCount > 0) {
+                    mDeferFrameCount--;
+                    // Workaround, detach and reattach to invalidate the root node for
+                    // another draw
+                    detach();
+                    attach();
+                    view.invalidate();
+                    return;
+                }
+
+                if (onFinishRunnable != null) {
+                    onFinishRunnable.run();
+                }
+                detach();
+            }
+        }.attach();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index a3bd348..e3b12cb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -26,16 +26,14 @@
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
+import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
+import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 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;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -45,7 +43,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Canvas;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.Build;
@@ -68,20 +65,20 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.uioverrides.DejankBinderTracker;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
+import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -89,12 +86,10 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
-        extends BaseSwipeUpHandler<T, RecentsView>
-        implements OnApplyWindowInsetsListener {
+        extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -200,6 +195,9 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
+    private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
+
     private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
@@ -231,11 +229,13 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
-    public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
-            long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
-            boolean continuingLastGesture,
+    public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, RunningTaskInfo runningTaskInfo, long touchTimeMs,
+            OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture,
             InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        mDeviceState = deviceState;
+        mGestureState = gestureState;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
         initStateCallbacks();
@@ -291,9 +291,6 @@
         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
-        mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
-                mRecentsAnimationWrapper::enableInputConsumer);
-
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -354,7 +351,7 @@
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
         if (mGestureEndTarget != HOME) {
             Runnable initAnimFactory = () -> {
-                mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+                mAnimationFactory = mActivityInterface.prepareRecentsUI(mActivity,
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
                 maybeUpdateRecentsAttachedState(false /* animate */);
@@ -417,15 +414,19 @@
     }
 
     private void sendRemoteAnimationsToAnimationFactory() {
-        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
+        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
     }
 
     private void initializeLauncherAnimationController() {
         buildAnimationController();
 
-        if (LatencyTrackerCompat.isEnabled(mContext)) {
-            LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
-        }
+        DejankBinderTracker.whitelistIpcs(() -> {
+            // Only used in debug builds
+            if (LatencyTrackerCompat.isEnabled(mContext)) {
+                LatencyTrackerCompat.logToggleRecents(
+                        (int) (mLauncherFrameDrawnTime - mTouchTimeMs));
+            }
+        });
 
         // This method is only called when STATE_GESTURE_STARTED is set, so we can enable the
         // high-res thumbnail loader here once we are sure that we will end up in an overview state
@@ -460,9 +461,9 @@
         if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
             return;
         }
-        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
+        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null
                 ? null
-                : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
+                : mRecentsAnimationTargets.findTask(mRunningTaskId);
         final boolean recentsAttachedToAppWindow;
         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
         if (mGestureEndTarget != null) {
@@ -479,8 +480,8 @@
             recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
             if (animate) {
                 // Only animate if an adjacent task view is visible on screen.
-                TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
-                TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
+                TaskView adjacentTask1 = mRecentsView.getNextTaskView();
+                TaskView adjacentTask2 = mRecentsView.getPreviousTaskView();
                 float prevTranslationX = mRecentsView.getTranslationX();
                 mRecentsView.setTranslationX(0);
                 animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
@@ -519,7 +520,7 @@
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
-        mAnimationFactory.createActivityController(mTransitionDragLength);
+        mAnimationFactory.createActivityInterface(mTransitionDragLength);
     }
 
     @Override
@@ -544,17 +545,15 @@
 
     @Override
     public void updateFinalShift() {
-
-        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
-        if (controller != null) {
+        if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
             updateSysUiFlags(mCurrentShift.value);
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationWrapper.getController() != null) {
-                mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(),
-                        mClipAnimationHelper.getCurrentCornerRadius());
+            if (mRecentsAnimationTargets != null) {
+                mLiveTileOverlay.update(mAppWindowAnimationHelper.getCurrentRectWithInsets(),
+                        mAppWindowAnimationHelper.getCurrentCornerRadius());
             }
         }
 
@@ -590,34 +589,41 @@
      */
     private void updateSysUiFlags(float windowProgress) {
         if (mRecentsView != null) {
-            TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView
-                    .getPageNearestToCenterOfScreen());
+            TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
             int centermostTaskFlags = centermostTask == null ? 0
                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
             boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
             // We will handle the sysui flags based on the centermost task view.
-            mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
-                    || useHomeScreenFlags);
+            if (mRecentsAnimationController != null) {
+                mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0
+                        || useHomeScreenFlags);
+            }
             int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
         }
     }
 
     @Override
-    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        super.onRecentsAnimationStart(targetSet);
-        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+        super.onRecentsAnimationStart(controller, targets);
+
+        // Only add the callback to enable the input consumer after we actually have the controller
+        mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+                mRecentsAnimationController::enableInputConsumer);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
     }
 
     @Override
-    public void onRecentsAnimationCanceled() {
-        mRecentsAnimationWrapper.setController(null);
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        super.onRecentsAnimationCanceled(thumbnailData);
+        mRecentsView.setRecentsAnimationTargets(null, null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
-        TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
     }
 
     @Override
@@ -684,9 +690,9 @@
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
         }
 
-        BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
-        return activity == null
-                ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
+        BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
+        return activity == null ? InputConsumer.NO_OP
+                : new OverviewInputConsumer(mGestureState, activity, null, true);
     }
 
     private void endRunningWindowAnim(boolean cancel) {
@@ -704,7 +710,7 @@
         final GestureEndTarget endTarget;
         final boolean goingToNewTask;
         if (mRecentsView != null) {
-            if (!mRecentsAnimationWrapper.hasTargets()) {
+            if (!hasTargets()) {
                 // If there are no running tasks, then we can assume that this is a continuation of
                 // the last gesture, but after the recents animation has finished
                 goingToNewTask = true;
@@ -754,9 +760,7 @@
             }
         }
 
-        int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags();
-        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0
-                && (endTarget == RECENTS || endTarget == LAST_TASK)) {
+        if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
             return LAST_TASK;
         }
         return endTarget;
@@ -809,8 +813,9 @@
             }
         }
 
-        if (endTarget.isLauncher) {
-            mRecentsAnimationWrapper.enableInputProxy();
+        if (endTarget.isLauncher && mRecentsAnimationController != null) {
+            mRecentsAnimationController.enableInputProxy(mInputConsumer,
+                    this::createNewInputProxyHandler);
         }
 
         if (endTarget == HOME) {
@@ -865,7 +870,7 @@
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
             GestureEndTarget target, PointF velocityPxPerMs) {
-        mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
+        runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
 
@@ -879,13 +884,13 @@
         if (mGestureEndTarget == HOME) {
             HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
-                homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
+                homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity);
             } else {
                 homeAnimFactory = new HomeAnimationFactory() {
                     @NonNull
                     @Override
                     public RectF getWindowTargetRect() {
-                        RectF fallbackTarget = new RectF(mClipAnimationHelper.getTargetRect());
+                        RectF fallbackTarget = new RectF(mAppWindowAnimationHelper.getTargetRect());
                         Utilities.scaleRectFAboutCenter(fallbackTarget, 0.25f);
                         return fallbackTarget;
                     }
@@ -923,7 +928,15 @@
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
-                    setStateOnUiThread(target.endState);
+                    if (target == NEW_TASK && mRecentsView != null
+                            && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
+                        // We are about to launch the current running task, so use LAST_TASK state
+                        // instead of NEW_TASK. This could happen, for example, if our scroll is
+                        // aborted after we determined the target to be NEW_TASK.
+                        setStateOnUiThread(LAST_TASK.endState);
+                    } else {
+                        setStateOnUiThread(target.endState);
+                    }
                 }
             });
             windowAnim.start();
@@ -983,7 +996,7 @@
                 }
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
-                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
+                mActivityInterface.onSwipeUpToHomeComplete(mActivity);
             }
         });
         return anim;
@@ -1003,10 +1016,14 @@
         }
     }
 
+    public boolean isCanceled() {
+        return mCanceled;
+    }
+
     @UiThread
     private void resumeLastTask() {
-        mRecentsAnimationWrapper.finish(false /* toRecents */, null);
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+        mRecentsAnimationController.finish(false /* toRecents */, null);
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
         doLogGesture(LAST_TASK);
         reset();
     }
@@ -1088,7 +1105,7 @@
 
     private void resetStateForAnimationCancel() {
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
-        mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
+        mActivityInterface.onTransitionCancelled(mActivity, wasVisible);
 
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
@@ -1096,17 +1113,23 @@
 
     private void switchToScreenshot() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (mRecentsAnimationController != null) {
+                // Update the screenshot of the task
+                if (mTaskSnapshot == null) {
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                }
+                mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
+            }
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-        } else if (!mRecentsAnimationWrapper.hasTargets()) {
+        } else if (!hasTargets()) {
             // If there are no targets, then we don't need to capture anything
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             boolean finishTransitionPosted = false;
-            SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
-            if (controller != null) {
+            if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
                 }
                 final TaskView taskView;
                 if (mGestureEndTarget == HOME) {
@@ -1119,34 +1142,8 @@
                 if (taskView != null && !mCanceled) {
                     // Defer finishing the animation until the next launcher frame with the
                     // new thumbnail
-                    finishTransitionPosted = new WindowCallbacksCompat(taskView) {
-
-                        // The number of frames to defer until we actually finish the animation
-                        private int mDeferFrameCount = 2;
-
-                        @Override
-                        public void onPostDraw(Canvas canvas) {
-                            // If we were cancelled after this was attached, do not update
-                            // the state.
-                            if (mCanceled) {
-                                detach();
-                                return;
-                            }
-
-                            if (mDeferFrameCount > 0) {
-                                mDeferFrameCount--;
-                                // Workaround, detach and reattach to invalidate the root node for
-                                // another draw
-                                detach();
-                                attach();
-                                taskView.invalidate();
-                                return;
-                            }
-
-                            setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-                            detach();
-                        }
-                    }.attach();
+                    finishTransitionPosted = ViewUtils.postDraw(taskView,
+                            () -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
                 }
             }
             if (!finishTransitionPosted) {
@@ -1161,33 +1158,35 @@
     private void finishCurrentTransitionToRecents() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-        } else if (!mRecentsAnimationWrapper.hasTargets()) {
+        } else if (!hasTargets()) {
             // If there are no targets, then there is nothing to finish
             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
-            synchronized (mRecentsAnimationWrapper) {
-                mRecentsAnimationWrapper.finish(true /* toRecents */,
+            synchronized (mRecentsAnimationController) {
+                mRecentsAnimationController.finish(true /* toRecents */,
                         () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
             }
         }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
     }
 
     private void finishCurrentTransitionToHome() {
-        synchronized (mRecentsAnimationWrapper) {
-            mRecentsAnimationWrapper.finish(true /* toRecents */,
+        synchronized (mRecentsAnimationController) {
+            mRecentsAnimationController.finish(true /* toRecents */,
                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
                     true /* sendUserLeaveHint */);
         }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
         doLogGesture(HOME);
     }
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
-        mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
-        mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
-                true /* screenshot */);
+        mActivityInterface.onSwipeUpToRecentsComplete(mActivity);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
+                    true /* screenshot */);
+        }
         mRecentsView.onSwipeUpAnimationSuccess();
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1197,7 +1196,7 @@
     }
 
     private void setTargetAlphaProvider(TargetAlphaProvider provider) {
-        mClipAnimationHelper.setTaskAlphaCallback(provider);
+        mAppWindowAnimationHelper.setTaskAlphaCallback(provider);
         updateFinalShift();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 2e9c0a3..79e71a1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -142,7 +142,7 @@
             mZoomTranslationY = 0f;
         } else {
             TaskView dummyTask = getTaskViewAt(0);
-            ScaleAndTranslation sat = getTempClipAnimationHelper()
+            ScaleAndTranslation sat = getTempAppWindowAnimationHelper()
                     .updateForFullscreenOverview(dummyTask)
                     .getScaleAndTranslation();
             mZoomScale = sat.scale;
@@ -176,7 +176,7 @@
     protected void applyLoadPlan(ArrayList<Task> tasks) {
         // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
-        // track the index of the next task appropriately, as it we are switching on any other app.
+        // track the index of the next task appropriately, as if we are switching on any other app.
         if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) {
             // Check if the task list has running task
             boolean found = false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 1f73a28..d3765c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -23,31 +23,29 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import android.content.Context;
-import android.graphics.RectF;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.R;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
- * Touch consumer for two finger swipe actions for accessibility actions
+ * Input consumer for two finger swipe actions for accessibility actions
  */
 public class AccessibilityInputConsumer extends DelegateInputConsumer {
 
     private static final String TAG = "A11yInputConsumer";
 
-    private final ISystemUiProxy mSystemUiProxy;
+    private final Context mContext;
     private final VelocityTracker mVelocityTracker;
     private final MotionPauseDetector mMotionPauseDetector;
-    private final boolean mAllowLongClick;
-    private final RectF mSwipeTouchRegion;
+    private final RecentsAnimationDeviceState mDeviceState;
 
     private final float mMinGestureDistance;
     private final float mMinFlingVelocity;
@@ -56,19 +54,17 @@
     private float mDownY;
     private float mTotalY;
 
-    public AccessibilityInputConsumer(Context context, ISystemUiProxy systemUiProxy,
-            boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor,
-            RectF swipeTouchRegion) {
+    public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
-        mSystemUiProxy = systemUiProxy;
+        mContext = context;
         mVelocityTracker = VelocityTracker.obtain();
         mMinGestureDistance = context.getResources()
                 .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
         mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
-        mSwipeTouchRegion = swipeTouchRegion;
+        mDeviceState = deviceState;
 
         mMotionPauseDetector = new MotionPauseDetector(context);
-        mAllowLongClick = allowLongClick;
     }
 
     @Override
@@ -103,7 +99,7 @@
             case ACTION_POINTER_DOWN: {
                 if (mState == STATE_INACTIVE) {
                     int pointerIndex = ev.getActionIndex();
-                    if (mSwipeTouchRegion.contains(ev.getX(pointerIndex), ev.getY(pointerIndex))
+                    if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
                             && mDelegate.allowInterceptByParent()) {
                         setActive(ev);
 
@@ -116,7 +112,7 @@
                 break;
             }
             case ACTION_MOVE: {
-                if (mState == STATE_ACTIVE && mAllowLongClick) {
+                if (mState == STATE_ACTIVE && mDeviceState.isAccessibilityMenuShortcutAvailable()) {
                     int pointerIndex = ev.findPointerIndex(mActivePointerId);
                     if (pointerIndex == -1) {
                         break;
@@ -129,21 +125,18 @@
             }
             case ACTION_UP:
                 if (mState == STATE_ACTIVE) {
-                    try {
-                        if (mAllowLongClick && mMotionPauseDetector.isPaused()) {
-                            mSystemUiProxy.notifyAccessibilityButtonLongClicked();
-                        } else {
-                            mTotalY += (ev.getY() - mDownY);
-                            mVelocityTracker.computeCurrentVelocity(1000);
+                    if (mDeviceState.isAccessibilityMenuShortcutAvailable()
+                            && mMotionPauseDetector.isPaused()) {
+                        SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonLongClicked();
+                    } else {
+                        mTotalY += (ev.getY() - mDownY);
+                        mVelocityTracker.computeCurrentVelocity(1000);
 
-                            if ((-mTotalY) > mMinGestureDistance
-                                    || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
-                                mSystemUiProxy.notifyAccessibilityButtonClicked(
-                                        Display.DEFAULT_DISPLAY);
-                            }
+                        if ((-mTotalY) > mMinGestureDistance
+                                || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
+                            SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonClicked(
+                                    Display.DEFAULT_DISPLAY);
                         }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unable to notify accessibility event", e);
                     }
                 }
                 // Follow through
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
similarity index 76%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 38b5a13..94126ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2019 The Android Open Source Project
  *
@@ -36,9 +37,7 @@
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.HapticFeedbackConstants;
@@ -49,16 +48,18 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.quickstep.ActivityControlHelper;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
  * Touch consumer for handling events to launch assistant from launcher
  */
-public class AssistantTouchConsumer extends DelegateInputConsumer {
+public class AssistantInputConsumer extends DelegateInputConsumer {
 
-    private static final String TAG = "AssistantTouchConsumer";
+    private static final String TAG = "AssistantInputConsumer";
     private static final long RETRACT_ANIMATION_DURATION_MS = 300;
 
     // From //java/com/google/android/apps/gsa/search/shared/util/OpaContract.java.
@@ -80,31 +81,30 @@
     private long mDragTime;
     private float mLastProgress;
     private int mDirection;
-    private ActivityControlHelper mActivityControlHelper;
+    private BaseActivityInterface mActivityInterface;
 
-    private final float mDistThreshold;
+    private final float mDragDistThreshold;
+    private final float mFlingDistThreshold;
     private final long mTimeThreshold;
     private final int mAngleThreshold;
     private final float mSquaredSlop;
-    private final ISystemUiProxy mSysUiProxy;
     private final Context mContext;
     private final GestureDetector mGestureDetector;
 
-    public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
-            ActivityControlHelper activityControlHelper, InputConsumer delegate,
-            InputMonitorCompat inputMonitor) {
+    public AssistantInputConsumer(Context context, GestureState gestureState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
-        mSysUiProxy = systemUiProxy;
-        mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+        mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+        mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
         mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
 
         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
 
         mSquaredSlop = slop * slop;
-        mActivityControlHelper = activityControlHelper;
+        mActivityInterface = gestureState.getActivityInterface();
 
         mGestureDetector = new GestureDetector(context, new AssistantGestureListener());
     }
@@ -117,8 +117,6 @@
     @Override
     public void onMotionEvent(MotionEvent ev) {
         // TODO add logging
-        mGestureDetector.onTouchEvent(ev);
-
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
                 mActivePointerId = ev.getPointerId(0);
@@ -130,8 +128,8 @@
             case ACTION_POINTER_DOWN: {
                 if (mState != STATE_ACTIVE) {
                     mState = STATE_DELEGATE_ACTIVE;
-                    break;
                 }
+                break;
             }
             case ACTION_POINTER_UP: {
                 int ptrIdx = ev.getActionIndex();
@@ -197,13 +195,7 @@
                         SWIPE_NOOP, mDirection, NAVBAR);
                     animator.addUpdateListener(valueAnimator -> {
                         float progress = (float) valueAnimator.getAnimatedValue();
-                        try {
-
-                            mSysUiProxy.onAssistantProgress(progress);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
-                                + progress, e);
-                        }
+                        SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(progress);
                     });
                     animator.setInterpolator(Interpolators.DEACCEL_2);
                     animator.start();
@@ -213,6 +205,8 @@
                 break;
         }
 
+        mGestureDetector.onTouchEvent(ev);
+
         if (mState != STATE_ACTIVE) {
             mDelegate.onMotionEvent(ev);
         }
@@ -220,23 +214,18 @@
 
     private void updateAssistantProgress() {
         if (!mLaunchedAssistant) {
-            mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
-            try {
-                if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
-                    mSysUiProxy.onAssistantGestureCompletion(0);
-                    startAssistantInternal(SWIPE);
+            mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
+            if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(0);
+                startAssistantInternal(SWIPE);
 
-                    Bundle args = new Bundle();
-                    args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
-                    args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
-                    mSysUiProxy.startAssistant(args);
-                    mLaunchedAssistant = true;
-                } else {
-                    mSysUiProxy.onAssistantProgress(mLastProgress);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress,
-                    e);
+                Bundle args = new Bundle();
+                args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
+                args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+                SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+                mLaunchedAssistant = true;
+            } else {
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(mLastProgress);
             }
         }
     }
@@ -245,8 +234,7 @@
         UserEventDispatcher.newInstance(mContext)
             .logActionOnContainer(gestureType, mDirection, NAVBAR);
 
-        BaseDraggingActivity launcherActivity = mActivityControlHelper
-            .getCreatedActivity();
+        BaseDraggingActivity launcherActivity = mActivityInterface.getCreatedActivity();
         if (launcherActivity != null) {
             launcherActivity.getRootView().performHapticFeedback(
                 13, // HapticFeedbackConstants.GESTURE_END
@@ -271,22 +259,18 @@
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
             if (isValidAssistantGestureAngle(velocityX, -velocityY)
-                && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
+                    && mDistance >= mFlingDistThreshold
+                    && !mLaunchedAssistant
+                    && mState != STATE_DELEGATE_ACTIVE) {
                 mLastProgress = 1;
-                try {
-                    mSysUiProxy.onAssistantGestureCompletion(
-                        (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
-                    startAssistantInternal(FLING);
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(
+                    (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
+                startAssistantInternal(FLING);
 
-                    Bundle args = new Bundle();
-                    args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
-                    mSysUiProxy.startAssistant(args);
-                    mLaunchedAssistant = true;
-                } catch (RemoteException e) {
-                    Log.w(TAG,
-                        "Failed to send SysUI start/send assistant progress: " + mLastProgress,
-                        e);
-                }
+                Bundle args = new Bundle();
+                args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+                SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+                mLaunchedAssistant = true;
             }
             return true;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 311ddd2..0b5129c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -2,6 +2,7 @@
 
 import android.view.MotionEvent;
 
+import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public abstract class DelegateInputConsumer implements InputConsumer {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 0743925..12b7c26 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -18,12 +18,13 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
+
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
 import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,19 +32,24 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
-import android.view.WindowManager;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.LockScreenRecentsActivity;
 import com.android.quickstep.MultiStateCallback;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationTargets;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -51,7 +57,7 @@
  * A dummy input consumer used when the device is still locked, e.g. from secure camera.
  */
 public class DeviceLockedInputConsumer implements InputConsumer,
-        SwipeAnimationTargetSet.SwipeAnimationListener {
+        RecentsAnimationCallbacks.RecentsAnimationListener {
 
     private static final float SCALE_DOWN = 0.75f;
 
@@ -69,17 +75,18 @@
             getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
 
     private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
     private final float mTouchSlopSquared;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
 
     private final PointF mTouchDown = new PointF();
-    private final ClipAnimationHelper mClipAnimationHelper;
+    private final AppWindowAnimationHelper mAppWindowAnimationHelper;
     private int mLogId;
-    private final ClipAnimationHelper.TransformParams mTransformParams;
+    private final AppWindowAnimationHelper.TransformParams mTransformParams;
     private final Point mDisplaySize;
     private final MultiStateCallback mStateCallback;
-    private final RectF mSwipeTouchRegion;
     public final int mRunningTaskId;
 
     private VelocityTracker mVelocityTracker;
@@ -87,24 +94,25 @@
 
     private boolean mThresholdCrossed = false;
 
-    private SwipeAnimationTargetSet mTargetSet;
+    private RecentsAnimationController mRecentsAnimationController;
+    private RecentsAnimationTargets mRecentsAnimationTargets;
 
-    public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
-            InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId,
-            int logId) {
+    public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, SwipeSharedState swipeSharedState,
+            InputMonitorCompat inputMonitorCompat, int runningTaskId, int logId) {
         mContext = context;
+        mDeviceState = deviceState;
+        mGestureState = gestureState;
         mTouchSlopSquared = squaredTouchSlop(context);
         mSwipeSharedState = swipeSharedState;
-        mClipAnimationHelper = new ClipAnimationHelper(context);
+        mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mLogId = logId;
-        mTransformParams = new ClipAnimationHelper.TransformParams();
+        mTransformParams = new AppWindowAnimationHelper.TransformParams();
         mInputMonitorCompat = inputMonitorCompat;
-        mSwipeTouchRegion = swipeTouchRegion;
         mRunningTaskId = runningTaskId;
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = new Point();
-        context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize);
+        mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
@@ -136,7 +144,7 @@
                 if (!mThresholdCrossed) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         int action = ev.getAction();
                         ev.setAction(ACTION_CANCEL);
                         finishTouchTracking(ev);
@@ -154,9 +162,7 @@
                     float dy = Math.max(mTouchDown.y - y, 0);
                     mProgress = dy / mDisplaySize.y;
                     mTransformParams.setProgress(mProgress);
-                    if (mTargetSet != null) {
-                        mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
-                    }
+                    mAppWindowAnimationHelper.applyTransform(mTransformParams);
                 }
                 break;
             }
@@ -201,9 +207,8 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        RecentsAnimationListenerSet newListenerSet =
-                mSwipeSharedState.newRecentsAnimationListenerSet();
-        newListenerSet.addListener(this);
+        RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
+        callbacks.addListener(this);
         Intent intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
@@ -211,35 +216,40 @@
                 .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
 
         mInputMonitorCompat.pilferPointers();
-        startRecentsActivityAsync(intent, newListenerSet);
+        startRecentsActivityAsync(intent, callbacks);
     }
 
     @Override
-    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        mTargetSet = targetSet;
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        mRecentsAnimationController = controller;
+        mRecentsAnimationTargets = targets;
 
         Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
-        RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat targetCompat = targets.findTask(mRunningTaskId);
         if (targetCompat != null) {
-            mClipAnimationHelper.updateSource(displaySize, targetCompat);
+            mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
         }
 
         Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
         displaySize.offsetTo(displaySize.left, 0);
-        mClipAnimationHelper.updateTargetRect(displaySize);
-        mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams);
+        mTransformParams.setTargetSet(mRecentsAnimationTargets)
+                .setLauncherOnTop(true);
+        mAppWindowAnimationHelper.updateTargetRect(displaySize);
+        mAppWindowAnimationHelper.applyTransform(mTransformParams);
 
         mStateCallback.setState(STATE_TARGET_RECEIVED);
     }
 
     @Override
-    public void onRecentsAnimationCanceled() {
-        mTargetSet = null;
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
     }
 
     private void endRemoteAnimation() {
-        if (mTargetSet != null) {
-            mTargetSet.finishController(
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finishController(
                     false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
         }
     }
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..370b487 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,18 +38,21 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.launcher3.util.ObjectWrapper;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.RecentsAnimationController;
 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.RecentsAnimationTargets;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -112,12 +115,13 @@
     private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
     private RunningWindowAnim mFinishAnimation;
 
-    public FallbackNoButtonInputConsumer(Context context,
+    public FallbackNoButtonInputConsumer(Context context, GestureState gestureState,
             OverviewComponentObserver overviewComponentObserver,
             RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
             InputConsumerController inputConsumer,
             boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
-        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
+                runningTaskInfo.id);
         mLauncherAlpha.value = 1;
 
         mRunningTaskInfo = runningTaskInfo;
@@ -127,9 +131,9 @@
         mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
 
         if (mSwipeUpOverHome) {
-            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
+            mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
         } else {
-            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+            mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
         }
 
         initStateCallbacks();
@@ -157,7 +161,7 @@
     }
 
     private void onLauncherAlphaChanged() {
-        if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
+        if (mRecentsAnimationTargets != null && mEndTarget == null) {
             applyTransformUnchecked();
         }
     }
@@ -231,9 +235,11 @@
     @Override
     public void updateFinalShift() {
         mTransformParams.setProgress(mCurrentShift.value);
-        mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
-                && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
-        if (mRecentsAnimationWrapper.targetSet != null) {
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
+                    && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
+        }
+        if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
         }
     }
@@ -316,26 +322,25 @@
         switch (mEndTarget) {
             case HOME: {
                 if (mSwipeUpOverHome) {
-                    mRecentsAnimationWrapper.finish(false, null, false);
+                    mRecentsAnimationController.finish(false, null, false);
                     // Send a home intent to clear the task stack
                     mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
                 } else {
-                    mRecentsAnimationWrapper.finish(true, null, true);
+                    mRecentsAnimationController.finish(true, null, true);
                 }
                 break;
             }
             case LAST_TASK:
-                mRecentsAnimationWrapper.finish(false, null, false);
+                mRecentsAnimationController.finish(false, null, false);
                 break;
             case RECENTS: {
                 if (mSwipeUpOverHome) {
-                    mRecentsAnimationWrapper.finish(true, null, true);
+                    mRecentsAnimationController.finish(true, null, true);
                     break;
                 }
 
-                ThumbnailData thumbnail =
-                        mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
-                mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+                ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
                         false /* screenshot */);
 
                 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
@@ -348,7 +353,7 @@
                 Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
                         .putExtras(extras);
                 mContext.startActivity(intent, options.toBundle());
-                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+                mRecentsAnimationController.cleanupScreenshot();
                 break;
             }
             case NEW_TASK: {
@@ -364,7 +369,7 @@
         if (mInQuickSwitchMode) {
             // Recalculate the end target, some views might have been initialized after
             // gesture has ended.
-            if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
+            if (mRecentsView == null || !hasTargets()) {
                 mEndTarget = LAST_TASK;
             } else {
                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
@@ -414,12 +419,13 @@
     }
 
     @Override
-    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        super.onRecentsAnimationStart(targetSet);
-        mRecentsAnimationWrapper.enableInputConsumer();
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        super.onRecentsAnimationStart(controller, targets);
+        mRecentsAnimationController.enableInputConsumer();
 
         if (mRunningOverHome) {
-            mClipAnimationHelper.prepareAnimation(mDp, true);
+            mAppWindowAnimationHelper.prepareAnimation(mDp, true);
         }
         applyTransformUnchecked();
 
@@ -427,8 +433,8 @@
     }
 
     @Override
-    public void onRecentsAnimationCanceled() {
-        mRecentsAnimationWrapper.setController(null);
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        mRecentsView.setRecentsAnimationTargets(null, null);
         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e41880d..fbedc0f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -22,13 +22,13 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
+
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
-import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
+import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
@@ -37,30 +37,36 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.graphics.PointF;
-import android.graphics.RectF;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
+
 import androidx.annotation.UiThread;
+
 import com.android.launcher3.R;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.BaseSwipeUpHandler;
 import com.android.quickstep.BaseSwipeUpHandler.Factory;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
+
 import java.util.function.Consumer;
 
 /**
@@ -75,13 +81,14 @@
     // TODO: Move to quickstep contract
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
+    private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
-    private final OverviewCallbacks mOverviewCallbacks;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
-    private final RectF mSwipeTouchRegion;
+    private final BaseActivityInterface mActivityInterface;
 
     private final BaseSwipeUpHandler.Factory mHandlerFactory;
 
@@ -120,20 +127,20 @@
     };
     private int mLogId;
 
-    public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
-            Consumer<OtherActivityInputConsumer> onCompleteCallback,
+    public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, RunningTaskInfo runningTaskInfo,
+            boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            RectF swipeTouchRegion, boolean disableHorizontalSwipe,
-            Factory handlerFactory, int logId) {
+            boolean disableHorizontalSwipe, Factory handlerFactory, int logId) {
         super(base);
         mLogId = logId;
-
+        mDeviceState = deviceState;
+        mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
         mMode = SysUINavigationMode.getMode(base);
-        mSwipeTouchRegion = swipeTouchRegion;
         mHandlerFactory = handlerFactory;
+        mActivityInterface = mGestureState.getActivityInterface();
 
         mMotionPauseDetector = new MotionPauseDetector(base);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -144,7 +151,6 @@
 
         boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
-        mOverviewCallbacks = overviewCallbacks;
         mSwipeSharedState = swipeSharedState;
 
         mNavBarPosition = new NavBarPosition(base);
@@ -213,7 +219,7 @@
                 if (!mPassedPilferInputSlop) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
+                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         forceCancelGesture(ev);
                     }
                 }
@@ -307,13 +313,13 @@
     }
 
     private void notifyGestureStarted() {
-        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
         }
         mInputMonitorCompat.pilferPointers();
 
-        mOverviewCallbacks.closeAllWindows();
+        mActivityInterface.closeOverlay();
         ActivityManagerWrapper.getInstance().closeSystemWindows(
                 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
 
@@ -323,11 +329,11 @@
 
     private void startTouchTrackingForWindowAnimation(
             long touchTimeMs, boolean isLikelyToStartNewTask) {
-        TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
-        RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
-        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
-                listenerSet != null, isLikelyToStartNewTask);
+        RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener();
+        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mGestureState, mRunningTask,
+                touchTimeMs, listenerSet != null, isLikelyToStartNewTask);
 
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
@@ -339,12 +345,11 @@
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
             notifyGestureStarted();
         } else {
-            RecentsAnimationListenerSet newListenerSet =
-                    mSwipeSharedState.newRecentsAnimationListenerSet();
-            newListenerSet.addListener(handler);
+            RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
+            callbacks.addListener(handler);
             Intent intent = handler.getLaunchIntent();
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
-            startRecentsActivityAsync(intent, newListenerSet);
+            startRecentsActivityAsync(intent, callbacks);
         }
     }
 
@@ -412,7 +417,7 @@
     }
 
     private void removeListener() {
-        RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
+        RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener();
         if (listenerSet != null) {
             listenerSet.removeListener(mInteractionHandler);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index e553891..c19754f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -16,10 +16,8 @@
 package com.android.quickstep.inputconsumers;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -27,9 +25,11 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -42,6 +42,7 @@
         implements InputConsumer {
 
     private final T mActivity;
+    private final BaseActivityInterface<T> mActivityInterface;
     private final BaseDragLayer mTarget;
     private final InputMonitorCompat mInputMonitor;
 
@@ -52,11 +53,12 @@
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
 
-    public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
-            boolean startingInActivityBounds) {
+    public OverviewInputConsumer(GestureState gestureState, T activity,
+            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
         mActivity = activity;
         mInputMonitor = inputMonitor;
         mStartingInActivityBounds = startingInActivityBounds;
+        mActivityInterface = gestureState.getActivityInterface();
 
         mTarget = activity.getDragLayer();
         if (startingInActivityBounds) {
@@ -98,10 +100,10 @@
         if (!mTargetHandledTouch && handled) {
             mTargetHandledTouch = true;
             if (!mStartingInActivityBounds) {
-                OverviewCallbacks.get(mActivity).closeAllWindows();
+                mActivityInterface.closeOverlay();
                 ActivityManagerWrapper.getInstance()
                         .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+                ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
                 mInputMonitor.pilferPointers();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 05cbb78..50069ea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -21,7 +21,6 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.content.Context;
@@ -34,10 +33,12 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -50,16 +51,17 @@
     private final float mSquaredTouchSlop;
     private final Context mContext;
     private final NavBarPosition mNavBarPosition;
+    private final BaseActivityInterface mActivityInterface;
 
     private boolean mInterceptedTouch;
     private VelocityTracker mVelocityTracker;
 
-
-    public OverviewWithoutFocusInputConsumer(Context context, InputMonitorCompat inputMonitor,
-            boolean disableHorizontalSwipe) {
+    public OverviewWithoutFocusInputConsumer(Context context, GestureState gestureState,
+            InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
         mInputMonitor = inputMonitor;
         mDisableHorizontalSwipe = disableHorizontalSwipe;
         mContext = context;
+        mActivityInterface = gestureState.getActivityInterface();
         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
         mNavBarPosition = new NavBarPosition(context);
 
@@ -148,10 +150,10 @@
         }
 
         if (triggerQuickstep) {
-            OverviewCallbacks.get(mContext).closeAllWindows();
+            mActivityInterface.closeOverlay();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+            ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
             int pageIndex = -1; // This number doesn't reflect workspace page index.
                                 // It only indicates that launcher client screen was shown.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
new file mode 100644
index 0000000..97ca730
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Input consumer for handling events to launch quick capture from launcher
+ * @param <T> Draggable activity subclass used by RecentsView
+ */
+public class QuickCaptureInputConsumer<T extends BaseDraggingActivity>
+        extends DelegateInputConsumer {
+
+    private static final String TAG = "QuickCaptureInputConsumer";
+
+    private static final String QUICK_CAPTURE_PACKAGE = "com.google.auxe.compose";
+    private static final String QUICK_CAPTURE_PACKAGE_DEV = "com.google.auxe.compose.debug";
+
+    private static final String EXTRA_DEVICE_STATE = "deviceState";
+    private static final String DEVICE_STATE_LOCKED = "Locked";
+    private static final String DEVICE_STATE_LAUNCHER = "Launcher";
+    private static final String DEVICE_STATE_APP = "App";
+    private static final String DEVICE_STATE_UNKNOWN = "Unknown";
+
+    private static final int ANGLE_THRESHOLD = 35; // Degrees
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final PointF mStartDragPos = new PointF();
+
+    private int mActivePointerId = -1;
+    private boolean mPassedSlop = false;
+
+    private final float mSquaredSlop;
+
+    private Context mContext;
+
+    private RecentsView mRecentsView;
+
+    public QuickCaptureInputConsumer(Context context, GestureState gestureState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
+        mContext = context;
+
+        float slop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mSquaredSlop = slop * slop;
+
+        gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
+                .register();
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_QUICK_CAPTURE | mDelegate.getType();
+    }
+
+    private boolean onActivityInit(final BaseDraggingActivity activity, Boolean alreadyOnHome) {
+        mRecentsView = activity.getOverviewPanel();
+
+        return true;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+
+                break;
+            }
+            case ACTION_POINTER_DOWN: {
+                if (mState != STATE_ACTIVE) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                }
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                int ptrIdx = ev.getActionIndex();
+                int ptrId = ev.getPointerId(ptrIdx);
+                if (ptrId == mActivePointerId) {
+                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
+                    mDownPos.set(
+                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
+                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
+                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
+                    mActivePointerId = ev.getPointerId(newPointerIdx);
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                if (mState == STATE_DELEGATE_ACTIVE) {
+                    break;
+                }
+                if (!mDelegate.allowInterceptByParent()) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == -1) {
+                    break;
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+
+                if (!mPassedSlop) {
+                    // Normal gesture, ensure we pass the slop before we start tracking the gesture
+                    if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+                            > mSquaredSlop) {
+
+                        mPassedSlop = true;
+                        mStartDragPos.set(mLastPos.x, mLastPos.y);
+
+                        if (isValidQuickCaptureGesture()) {
+                            setActive(ev);
+                        } else {
+                            mState = STATE_DELEGATE_ACTIVE;
+                        }
+                    }
+                }
+
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP:
+                if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop) {
+                    startQuickCapture();
+                }
+
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private boolean isValidQuickCaptureGesture() {
+        // Make sure there isn't an app to quick switch to on our right
+        boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0);
+
+        // Check if the gesture is within our angle threshold of horizontal
+        float deltaY = Math.abs(mLastPos.y - mDownPos.y);
+        float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left
+        boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < ANGLE_THRESHOLD;
+
+        return atRightMostApp && angleInBounds;
+    }
+
+    private void startQuickCapture() {
+        // Inspect our delegate's type to figure out where the user invoked Compose
+        String deviceState = DEVICE_STATE_UNKNOWN;
+        int consumerType = mDelegate.getType();
+        if (((consumerType & InputConsumer.TYPE_OVERVIEW) > 0)
+                || ((consumerType & InputConsumer.TYPE_OVERVIEW_WITHOUT_FOCUS)) > 0) {
+            deviceState = DEVICE_STATE_LAUNCHER;
+        } else if ((consumerType & InputConsumer.TYPE_OTHER_ACTIVITY) > 0) {
+            deviceState = DEVICE_STATE_APP;
+        } else if (((consumerType & InputConsumer.TYPE_RESET_GESTURE) > 0)
+                || ((consumerType & InputConsumer.TYPE_DEVICE_LOCKED) > 0)) {
+            deviceState = DEVICE_STATE_LOCKED;
+        }
+
+        // Then launch the app
+        PackageManager pm = mContext.getPackageManager();
+
+        Intent qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE);
+
+        if (qcIntent == null) {
+            // If we couldn't find the regular app, try the dev version
+            qcIntent = pm.getLaunchIntentForPackage(QUICK_CAPTURE_PACKAGE_DEV);
+        }
+
+        if (qcIntent != null) {
+            qcIntent.putExtra(EXTRA_DEVICE_STATE, deviceState);
+
+            Bundle options = ActivityOptions.makeCustomAnimation(mContext, R.anim.slide_in_right,
+                    0).toBundle();
+
+            mContext.startActivity(qcIntent, options);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index 8eede81..e04c0c7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -17,6 +17,7 @@
 
 import android.view.MotionEvent;
 
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.SwipeSharedState;
 
 /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index a0e20f2..d5ed321 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -16,16 +16,15 @@
 package com.android.quickstep.inputconsumers;
 
 import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.SystemUiProxy;
 
 /**
  * An input consumer that detects swipe up and hold to exit screen pinning mode.
@@ -39,25 +38,21 @@
 
     private float mTouchDownY;
 
-    public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
-            ActivityControlHelper activityControl) {
+    public ScreenPinnedInputConsumer(Context context, GestureState gestureState) {
         mMotionPauseMinDisplacement = context.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
         mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
         mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
             if (isPaused) {
-                try {
-                    sysuiProxy.stopScreenPinning();
-                    BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
-                    if (launcherActivity != null) {
-                        launcherActivity.getRootView().performHapticFeedback(
-                                HapticFeedbackConstants.LONG_PRESS,
-                                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                    }
-                    mMotionPauseDetector.clear();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Unable to stop screen pinning ", e);
+                SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
+                BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
+                        .getCreatedActivity();
+                if (launcherActivity != null) {
+                    launcherActivity.getRootView().performHapticFeedback(
+                            HapticFeedbackConstants.LONG_PRESS,
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 }
+                mMotionPauseDetector.clear();
             }
         });
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
new file mode 100644
index 0000000..9a3bb76
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.android.launcher3.logging.EventLogArray;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
+/**
+ * A log to keep track of the active gesture.
+ */
+public class ActiveGestureLog extends EventLogArray {
+
+    public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
+
+    /**
+     * NOTE: This value should be kept same as
+     * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
+     */
+    public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
+
+    public ActiveGestureLog() {
+        super("touch_interaction_log", 40);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
similarity index 88%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index cae273a..5eee897 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -28,7 +28,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.RemoteException;
 
 import androidx.annotation.Nullable;
 
@@ -36,13 +35,13 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SystemUiProxy;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -50,13 +49,11 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.util.function.BiFunction;
-
 /**
  * Utility class to handle window clip animation
  */
 @TargetApi(Build.VERSION_CODES.P)
-public class ClipAnimationHelper {
+public class AppWindowAnimationHelper {
 
     // The bounds of the source app in device coordinates
     private final Rect mSourceStackBounds = new Rect();
@@ -102,7 +99,7 @@
     private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
     private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
 
-    public ClipAnimationHelper(Context context) {
+    public AppWindowAnimationHelper(Context context) {
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
         mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
         mTaskCornerRadius = TaskCornerRadius.get(context);
@@ -157,12 +154,75 @@
         mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
     }
 
-    public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
-        return applyTransform(targetSet, params, true /* launcherOnTop */);
+    public RectF applyTransform(TransformParams params) {
+        SurfaceParams[] surfaceParams = getSurfaceParams(params);
+        if (surfaceParams == null) {
+            return null;
+        }
+        applySurfaceParams(params.syncTransactionApplier, surfaceParams);
+        return params.currentRect;
     }
 
-    public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params,
-            boolean launcherOnTop) {
+    public SurfaceParams[] getSurfaceParams(TransformParams params) {
+        if (params.targetSet == null) {
+            return null;
+        }
+
+        float progress = params.progress;
+        updateCurrentRect(params);
+
+        SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length];
+        for (int i = 0; i < params.targetSet.unfilteredApps.length; i++) {
+            RemoteAnimationTargetCompat app = params.targetSet.unfilteredApps[i];
+            mTmpMatrix.setTranslate(app.position.x, app.position.y);
+            Rect crop = mTmpRect;
+            crop.set(app.sourceContainerBounds);
+            crop.offsetTo(0, 0);
+            float alpha;
+            int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
+            float cornerRadius = 0f;
+            float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
+            if (app.mode == params.targetSet.targetMode) {
+                alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
+                if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+                    mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
+                    mTmpMatrix.postTranslate(app.position.x, app.position.y);
+                    mClipRectF.roundOut(crop);
+                    if (mSupportsRoundedCornersOnWindows) {
+                        if (params.cornerRadius > -1) {
+                            cornerRadius = params.cornerRadius;
+                            scale = params.currentRect.width() / crop.width();
+                        } else {
+                            float windowCornerRadius = mUseRoundedCornersOnWindows
+                                    ? mWindowCornerRadius : 0;
+                            cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
+                                    mTaskCornerRadius);
+                        }
+                        mCurrentCornerRadius = cornerRadius;
+                    }
+                } else if (params.targetSet.hasRecents) {
+                    // If home has a different target then recents, reverse anim the
+                    // home target.
+                    alpha = 1 - (progress * params.targetAlpha);
+                }
+            } else {
+                alpha = mBaseAlphaCallback.getAlpha(app, progress);
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.launcherOnTop) {
+                    crop = null;
+                    layer = Integer.MAX_VALUE;
+                }
+            }
+
+            // Since radius is in Surface space, but we draw the rounded corners in screen space, we
+            // have to undo the scale.
+            surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
+                    cornerRadius / scale);
+        }
+        applySurfaceParams(params.syncTransactionApplier, surfaceParams);
+        return surfaceParams;
+    }
+
+    public RectF updateCurrentRect(TransformParams params) {
         float progress = params.progress;
         if (params.currentRect == null) {
             RectF currentRect;
@@ -183,55 +243,6 @@
                     mSourceStackBounds.height() - (sourceWindowClipInsets.bottom * progress);
             params.setCurrentRectAndTargetAlpha(currentRect, 1);
         }
-
-        SurfaceParams[] surfaceParams = new SurfaceParams[targetSet.unfilteredApps.length];
-        for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
-            RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
-            mTmpMatrix.setTranslate(app.position.x, app.position.y);
-            Rect crop = mTmpRect;
-            crop.set(app.sourceContainerBounds);
-            crop.offsetTo(0, 0);
-            float alpha;
-            int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
-            float cornerRadius = 0f;
-            float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
-            if (app.mode == targetSet.targetMode) {
-                alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
-                if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
-                    mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
-                    mTmpMatrix.postTranslate(app.position.x, app.position.y);
-                    mClipRectF.roundOut(crop);
-                    if (mSupportsRoundedCornersOnWindows) {
-                        if (params.cornerRadius > -1) {
-                            cornerRadius = params.cornerRadius;
-                            scale = params.currentRect.width() / crop.width();
-                        } else {
-                            float windowCornerRadius = mUseRoundedCornersOnWindows
-                                    ? mWindowCornerRadius : 0;
-                            cornerRadius = Utilities.mapRange(progress, windowCornerRadius,
-                                    mTaskCornerRadius);
-                        }
-                        mCurrentCornerRadius = cornerRadius;
-                    }
-                } else if (targetSet.hasRecents) {
-                    // If home has a different target then recents, reverse anim the
-                    // home target.
-                    alpha = 1 - (progress * params.targetAlpha);
-                }
-            } else {
-                alpha = mBaseAlphaCallback.getAlpha(app, progress);
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
-                    crop = null;
-                    layer = Integer.MAX_VALUE;
-                }
-            }
-
-            // Since radius is in Surface space, but we draw the rounded corners in screen space, we
-            // have to undo the scale.
-            surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
-                    cornerRadius / scale);
-        }
-        applySurfaceParams(params.syncTransactionApplier, surfaceParams);
         return params.currentRect;
     }
 
@@ -240,7 +251,7 @@
         return mCurrentRectWithInsets;
     }
 
-    private void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
+    public static void applySurfaceParams(@Nullable SyncRtSurfaceTransactionApplierCompat
             syncTransactionApplier, SurfaceParams[] params) {
         if (syncTransactionApplier != null) {
             syncTransactionApplier.scheduleApply(params);
@@ -305,7 +316,7 @@
     /**
      * Compute scale and translation y such that the specified task view fills the screen.
      */
-    public ClipAnimationHelper updateForFullscreenOverview(TaskView v) {
+    public AppWindowAnimationHelper updateForFullscreenOverview(TaskView v) {
         TaskThumbnailView thumbnailView = v.getThumbnail();
         RecentsView recentsView = v.getRecentsView();
         fromTaskThumbnailView(thumbnailView, recentsView);
@@ -325,14 +336,10 @@
     }
 
     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
-        ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-        if (sysUiProxy != null) {
-            try {
-                mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
-                return;
-            } catch (RemoteException e) {
-                // Use half screen size
-            }
+        SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
+        if (proxy.isActive()) {
+            mSourceStackBounds.set(proxy.getNonMinimizedSplitScreenSecondaryBounds());
+            return;
         }
 
         // Assume that the task size is half screen size (minus the insets and the divider size)
@@ -375,12 +382,14 @@
         float progress;
         public float offsetX;
         public float offsetScale;
-        @Nullable RectF currentRect;
+        public @Nullable RectF currentRect;
         float targetAlpha;
         boolean forLiveTile;
         float cornerRadius;
+        boolean launcherOnTop;
 
-        SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
+        public RemoteAnimationTargets targetSet;
+        public SyncRtSurfaceTransactionApplierCompat syncTransactionApplier;
 
         public TransformParams() {
             progress = 0;
@@ -390,6 +399,7 @@
             targetAlpha = 0;
             forLiveTile = false;
             cornerRadius = -1;
+            launcherOnTop = false;
         }
 
         public TransformParams setProgress(float progress) {
@@ -424,6 +434,16 @@
             return this;
         }
 
+        public TransformParams setLauncherOnTop(boolean launcherOnTop) {
+            this.launcherOnTop = launcherOnTop;
+            return this;
+        }
+
+        public TransformParams setTargetSet(RemoteAnimationTargets targetSet) {
+            this.targetSet = targetSet;
+            return this;
+        }
+
         public TransformParams setSyncTransactionApplier(
                 SyncRtSurfaceTransactionApplierCompat applier) {
             this.syncTransactionApplier = applier;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
index 3ce341d..bbb318a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java
@@ -21,9 +21,9 @@
 
 import android.content.Context;
 import android.view.Surface;
-import android.view.WindowManager;
 
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode;
 
 /**
@@ -36,8 +36,7 @@
 
     public NavBarPosition(Context context) {
         mMode = SysUINavigationMode.getMode(context);
-        mDisplayRotation = context.getSystemService(WindowManager.class)
-                .getDefaultDisplay().getRotation();
+        mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
     }
 
     public boolean isRightEdge() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
deleted file mode 100644
index b1999d7..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ /dev/null
@@ -1,115 +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.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.graphics.Rect;
-import android.util.ArraySet;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-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 java.util.function.Consumer;
-
-/**
- * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
- * on the main thread
- */
-public class RecentsAnimationListenerSet implements RecentsAnimationListener {
-
-    // The actual app surface is replaced by a screenshot upon recents animation cancelation when
-    // the thumbnailData exists. Launcher takes the responsibility to clean up this screenshot
-    // after app transition is finished. This delay is introduced to cover the app transition
-    // period of time.
-    private final int TRANSITION_DELAY = 100;
-
-    private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
-    private final boolean mShouldMinimizeSplitScreen;
-    private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
-    private RecentsAnimationControllerCompat mController;
-
-    private boolean mCancelled;
-
-    public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
-            Consumer<SwipeAnimationTargetSet> onFinishListener) {
-        mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
-        mOnFinishListener = onFinishListener;
-    }
-
-    @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) {
-        mController = controller;
-        SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
-                homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
-                mOnFinishListener);
-
-        if (mCancelled) {
-            targetSet.cancelAnimation();
-        } else {
-            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-                for (SwipeAnimationListener listener : getListeners()) {
-                    listener.onRecentsAnimationStart(targetSet);
-                }
-            });
-        }
-    }
-
-    @Override
-    public final void onAnimationCanceled(ThumbnailData thumbnailData) {
-        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-            for (SwipeAnimationListener listener : getListeners()) {
-                listener.onRecentsAnimationCanceled();
-            }
-        });
-        // TODO: handle the transition better instead of simply using a transition delay.
-        if (thumbnailData != null) {
-            MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
-                    TRANSITION_DELAY);
-        }
-    }
-
-    private SwipeAnimationListener[] getListeners() {
-        return mListeners.toArray(new SwipeAnimationListener[mListeners.size()]);
-    }
-
-    public void cancelListener() {
-        mCancelled = true;
-        onAnimationCanceled(null);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 1aa5365..d644fd6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -21,15 +21,14 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -40,14 +39,10 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.views.IconLabelDotView;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Creates an animation where all the workspace items are moved into their final location,
  * staggered row by row from the bottom up.
@@ -69,7 +64,7 @@
     // The original view of the {@link FloatingIconView}.
     private final View mOriginalView;
 
-    private final List<Animator> mAnimators = new ArrayList<>();
+    private final AnimatorSet mAnimators = new AnimatorSet();
 
     /**
      * @param floatingViewOriginalView The FloatingIconView's original view.
@@ -136,16 +131,9 @@
         addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
         addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
 
-        AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
-            int numAnimations = mAnimators.size();
-
+        mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                numAnimations--;
-                if (numAnimations > 0) {
-                    return;
-                }
-
                 workspace.setClipChildren(workspaceClipChildren);
                 workspace.setClipToPadding(workspaceClipToPadding);
                 cellLayout.setClipChildren(cellLayoutClipChildren);
@@ -153,24 +141,14 @@
                 hotseat.setClipChildren(hotseatClipChildren);
                 hotseat.setClipToPadding(hotseatClipToPadding);
             }
-        };
-
-        for (Animator a : mAnimators) {
-            a.addListener(resetClipListener);
-        }
+        });
     }
 
     /**
      * Starts the animation.
      */
     public void start() {
-        for (Animator a : mAnimators) {
-            if (a instanceof SpringObjectAnimator) {
-                ((SpringObjectAnimator) a).startSpring(1f, mVelocity, null);
-            } else {
-                a.start();
-            }
-        }
+        mAnimators.start();
     }
 
     /**
@@ -187,10 +165,16 @@
         long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS);
 
         v.setTranslationY(mSpringTransY);
-        SpringObjectAnimator springTransY = new SpringObjectAnimator<>(v, VIEW_TRANSLATE_Y,
-                1f, DAMPING_RATIO, STIFFNESS, mSpringTransY, 0);
+
+        ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
+                .setStiffness(STIFFNESS)
+                .setDampingRatio(DAMPING_RATIO)
+                .setMinimumVisibleChange(1f)
+                .setEndValue(0)
+                .setStartVelocity(mVelocity)
+                .build(v.getContext());
         springTransY.setStartDelay(startDelay);
-        mAnimators.add(springTransY);
+        mAnimators.play(springTransY);
 
         ObjectAnimator alpha = getAlphaAnimator(v, startDelay);
         if (v == mOriginalView) {
@@ -211,7 +195,7 @@
         }
 
         v.setAlpha(0);
-        mAnimators.add(alpha);
+        mAnimators.play(alpha);
     }
 
     private ObjectAnimator getAlphaAnimator(View v, long startDelay) {
@@ -229,11 +213,11 @@
         scrimAnimConfig.duration = duration;
         PropertySetter scrimPropertySetter = scrimAnimConfig.getPropertySetter(scrimAnimBuilder);
         launcher.getWorkspace().getStateTransitionAnimation().setScrim(scrimPropertySetter, state);
-        mAnimators.add(scrimAnimBuilder.build());
+        mAnimators.play(scrimAnimBuilder.build());
         Animator fadeOverviewScrim = ObjectAnimator.ofFloat(
                 launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
                 state.getOverviewScrimAlpha(launcher));
         fadeOverviewScrim.setDuration(duration);
-        mAnimators.add(fadeOverviewScrim);
+        mAnimators.play(fadeOverviewScrim);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
deleted file mode 100644
index 3619d3a..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ /dev/null
@@ -1,119 +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.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.graphics.Rect;
-
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.function.Consumer;
-
-/**
- * Extension of {@link RemoteAnimationTargetSet} with additional information about swipe
- * up animation
- */
-public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
-
-    private final boolean mShouldMinimizeSplitScreen;
-    private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
-
-    public final RecentsAnimationControllerCompat controller;
-    public final Rect homeContentInsets;
-    public final Rect minimizedHomeBounds;
-
-    public SwipeAnimationTargetSet(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
-            Rect minimizedHomeBounds, boolean shouldMinimizeSplitScreen,
-            Consumer<SwipeAnimationTargetSet> onFinishListener) {
-        super(targets, MODE_CLOSING);
-        this.controller = controller;
-        this.homeContentInsets = homeContentInsets;
-        this.minimizedHomeBounds = minimizedHomeBounds;
-        this.mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
-        this.mOnFinishListener = onFinishListener;
-    }
-
-    public boolean hasTargets() {
-        return unfilteredApps.length != 0;
-    }
-
-    /**
-     * Clones the target set without any actual targets. Used only when continuing a gesture after
-     * the actual recents animation has finished.
-     */
-    public SwipeAnimationTargetSet cloneWithoutTargets() {
-        return new SwipeAnimationTargetSet(controller, new RemoteAnimationTargetCompat[0],
-                homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
-                mOnFinishListener);
-    }
-
-    public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
-        mOnFinishListener.accept(this);
-        UI_HELPER_EXECUTOR.execute(() -> {
-            controller.setInputConsumerEnabled(false);
-            controller.finish(toRecents, sendUserLeaveHint);
-
-            if (callback != null) {
-                MAIN_EXECUTOR.execute(callback);
-            }
-        });
-    }
-
-    public void enableInputConsumer() {
-        UI_HELPER_EXECUTOR.submit(() -> {
-            controller.hideCurrentInputMethod();
-            controller.setInputConsumerEnabled(true);
-        });
-    }
-
-    public void setWindowThresholdCrossed(boolean thresholdCrossed) {
-        UI_HELPER_EXECUTOR.execute(() -> {
-            controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
-            if (mShouldMinimizeSplitScreen && thresholdCrossed) {
-                // NOTE: As a workaround for conflicting animations (Launcher animating the task
-                // leash, and SystemUI resizing the docked stack, which resizes the task), we
-                // currently only set the minimized mode, and not the inverse.
-                // TODO: Synchronize the minimize animation with the launcher animation
-                controller.setSplitScreenMinimized(thresholdCrossed);
-            }
-        });
-    }
-
-    public ThumbnailData screenshotTask(int taskId) {
-        return controller != null ? controller.screenshotTask(taskId) : null;
-    }
-
-    public void cancelAnimation() {
-        finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
-    }
-
-    public void finishAnimation() {
-        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
-    }
-
-    public interface SwipeAnimationListener {
-
-        void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet);
-
-        void onRecentsAnimationCanceled();
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 03441c8..1545ec5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -35,6 +35,7 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
+import android.widget.FrameLayout;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Hotseat;
@@ -45,11 +46,15 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.uioverrides.DejankBinderTracker;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.RecentsExtraCard;
 
 /**
  * {@link RecentsView} used in Launcher activity
@@ -57,8 +62,29 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements StateListener {
 
+    private static final Rect sTempRect = new Rect();
+
     private final TransformParams mTransformParams = new TransformParams();
 
+    private RecentsExtraCard mRecentsExtraCardPlugin;
+    private RecentsExtraViewContainer mRecentsExtraViewContainer;
+    private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
+            new PluginListener<RecentsExtraCard>() {
+        @Override
+        public void onPluginConnected(RecentsExtraCard recentsExtraCard, Context context) {
+            createRecentsExtraCard();
+            mRecentsExtraCardPlugin = recentsExtraCard;
+            mRecentsExtraCardPlugin.setupView(context, mRecentsExtraViewContainer, mActivity);
+        }
+
+        @Override
+        public void onPluginDisconnected(RecentsExtraCard plugin) {
+            removeView(mRecentsExtraViewContainer);
+            mRecentsExtraCardPlugin = null;
+            mRecentsExtraViewContainer = null;
+        }
+    };
+
     public LauncherRecentsView(Context context) {
         this(context, null);
     }
@@ -75,7 +101,12 @@
 
     @Override
     public void startHome() {
-        mActivity.getStateManager().goToState(NORMAL);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */,
+                    () -> mActivity.getStateManager().goToState(NORMAL)));
+        } else {
+            mActivity.getStateManager().goToState(NORMAL);
+        }
     }
 
     @Override
@@ -112,7 +143,7 @@
      */
     @Override
     public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv,
-            ClipAnimationHelper helper) {
+            AppWindowAnimationHelper helper) {
         AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
 
         if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
@@ -144,15 +175,33 @@
         LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
     }
 
+    /**
+     * @return The translationX to apply to this view so that the first task is just offscreen.
+     */
+    public float getOffscreenTranslationX(float recentsScale) {
+        float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX;
+        // Offset since scale pushes tasks outwards.
+        getTaskSize(sTempRect);
+        int taskWidth = sTempRect.width();
+        offscreenX += taskWidth * (recentsScale - 1) / 2;
+        if (mRunningTaskTileHidden) {
+            // The first task is hidden, so offset by its width.
+            offscreenX -= (taskWidth + getPageSpacing()) * recentsScale;
+        }
+        if (isRtl()) {
+            offscreenX = -offscreenX;
+        }
+        return offscreenX;
+    }
+
     @Override
     protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationWrapper.targetSet != null && tv.isRunningTask()) {
+            if (tv.isRunningTask()) {
                 mTransformParams.setProgress(1 - progress)
                         .setSyncTransactionApplier(mSyncTransactionApplier)
                         .setForLiveTile(true);
-                mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
-                        mTransformParams);
+                mAppWindowAnimationHelper.applyTransform(mTransformParams);
             } else {
                 redrawLiveTile(true);
             }
@@ -172,7 +221,7 @@
 
     @Override
     public boolean shouldUseMultiWindowTaskSizeStrategy() {
-        return mActivity.isInMultiWindowMode();
+        return DejankBinderTracker.whitelistIpcs(() -> mActivity.isInMultiWindowMode());
     }
 
     @Override
@@ -185,9 +234,18 @@
 
     @Override
     public void redrawLiveTile(boolean mightNeedToRefill) {
-        if (!mEnableDrawingLiveTile || mRecentsAnimationWrapper == null
-                || mClipAnimationHelper == null) {
-            return;
+        AppWindowAnimationHelper.TransformParams transformParams = getLiveTileParams(mightNeedToRefill);
+        if (transformParams != null) {
+            mAppWindowAnimationHelper.applyTransform(transformParams);
+        }
+    }
+
+    @Override
+    public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+            boolean mightNeedToRefill) {
+        if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
+                || mRecentsAnimationTargets == null || mAppWindowAnimationHelper == null) {
+            return null;
         }
         TaskView taskView = getRunningTaskView();
         if (taskView != null) {
@@ -209,12 +267,11 @@
             mTempRectF.set(mTempRect);
             mTransformParams.setProgress(1f)
                     .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
-                    .setSyncTransactionApplier(mSyncTransactionApplier);
-            if (mRecentsAnimationWrapper.targetSet != null) {
-                mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
-                        mTransformParams);
-            }
+                    .setSyncTransactionApplier(mSyncTransactionApplier)
+                    .setTargetSet(mRecentsAnimationTargets)
+                    .setLauncherOnTop(true);
         }
+        return mTransformParams;
     }
 
     @Override
@@ -264,4 +321,66 @@
         }
         return super.shouldStealTouchFromSiblingsBelow(ev);
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext())
+                .addPluginListener(mRecentsExtraCardPluginListener, RecentsExtraCard.class);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(
+                mRecentsExtraCardPluginListener);
+    }
+
+    @Override
+    protected int computeMinScrollX() {
+        if (canComputeScrollX() && !mIsRtl) {
+            return computeScrollX();
+        }
+        return super.computeMinScrollX();
+    }
+
+    @Override
+    protected int computeMaxScrollX() {
+        if (canComputeScrollX() && mIsRtl) {
+            return computeScrollX();
+        }
+        return super.computeMaxScrollX();
+    }
+
+    private boolean canComputeScrollX() {
+        return mRecentsExtraCardPlugin != null && getTaskViewCount() > 0
+                && !mDisallowScrollToClearAll;
+    }
+
+    private int computeScrollX() {
+        int scrollIndex = getTaskViewStartIndex() - 1;
+        while (scrollIndex >= 0 && getChildAt(scrollIndex) instanceof RecentsExtraViewContainer
+                && ((RecentsExtraViewContainer) getChildAt(scrollIndex)).isScrollable()) {
+            scrollIndex--;
+        }
+        return getScrollForPage(scrollIndex + 1);
+    }
+
+    private void createRecentsExtraCard() {
+        mRecentsExtraViewContainer = new RecentsExtraViewContainer(getContext());
+        FrameLayout.LayoutParams helpCardParams =
+                new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.MATCH_PARENT);
+        mRecentsExtraViewContainer.setLayoutParams(helpCardParams);
+        mRecentsExtraViewContainer.setScrollable(true);
+        addView(mRecentsExtraViewContainer, 0);
+    }
+
+    @Override
+    public void resetTaskVisuals() {
+        super.resetTaskVisuals();
+        if (mRecentsExtraViewContainer != null) {
+            mRecentsExtraViewContainer.setAlpha(mContentAlpha);
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
new file mode 100644
index 0000000..1ea6d4a
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Empty view to house recents overview extra card
+ */
+public class RecentsExtraViewContainer extends FrameLayout implements RecentsView.PageCallbacks {
+
+    private boolean mScrollable = false;
+
+    public RecentsExtraViewContainer(Context context) {
+        super(context);
+    }
+
+    public RecentsExtraViewContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public RecentsExtraViewContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * Determine whether the view should be scrolled to in the recents overview, similar to the
+     * taskviews.
+     * @return true if viewed should be scrolled to, false if not
+     */
+    public boolean isScrollable() {
+        return mScrollable;
+    }
+
+    public void setScrollable(boolean scrollable) {
+        this.mScrollable = scrollable;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 9464bd1..6120b9e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -102,12 +102,14 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.RecentsAnimationWrapper;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,8 +157,9 @@
                 }
             };
 
-    protected RecentsAnimationWrapper mRecentsAnimationWrapper;
-    protected ClipAnimationHelper mClipAnimationHelper;
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
+    protected AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
@@ -176,7 +179,7 @@
     private final ClearAllButton mClearAllButton;
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
-    protected final ClipAnimationHelper mTempClipAnimationHelper;
+    protected final AppWindowAnimationHelper mTempAppWindowAnimationHelper;
 
     private final ScrollState mScrollState = new ScrollState();
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -187,7 +190,7 @@
     private final ViewPool<TaskView> mTaskViewPool;
 
     private boolean mDwbToastShown;
-    private boolean mDisallowScrollToClearAll;
+    protected boolean mDisallowScrollToClearAll;
     private boolean mOverlayEnabled;
     private boolean mFreezeViewVisibility;
 
@@ -272,7 +275,7 @@
 
     // Only valid until the launcher state changes to NORMAL
     protected int mRunningTaskId = -1;
-    private boolean mRunningTaskTileHidden;
+    protected boolean mRunningTaskTileHidden;
     private Task mTmpRunningTask;
 
     private boolean mRunningTaskIconScaledDown = false;
@@ -289,7 +292,7 @@
     private LayoutTransition mLayoutTransition;
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mContentAlpha = 1;
+    protected float mContentAlpha = 1;
     @ViewDebug.ExportedProperty(category = "launcher")
     protected float mFullscreenProgress = 0;
 
@@ -306,6 +309,9 @@
     private Layout mEmptyTextLayout;
     private LiveTileOverlay mLiveTileOverlay;
 
+    // Keeps track of the index where the first TaskView should be
+    private int mTaskViewStartIndex = 0;
+
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
         if (!inMultiWindowMode && mOverviewStateEnabled) {
@@ -324,12 +330,11 @@
         mActivity = (T) BaseActivity.fromContext(context);
         mModel = RecentsModel.INSTANCE.get(context);
         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
-        mTempClipAnimationHelper = new ClipAnimationHelper(context);
+        mTempAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
         mClearAllButton.setOnClickListener(this::dismissAllTasks);
-
         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                 10 /* initial size */);
 
@@ -378,14 +383,23 @@
         return null;
     }
 
-    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+    /**
+     * Update the thumbnail of the task.
+     * @param refreshNow Refresh immediately if it's true.
+     */
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
         TaskView taskView = getTaskView(taskId);
         if (taskView != null) {
-            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
         }
         return taskView;
     }
 
+    /** See {@link #updateThumbnail(int, ThumbnailData, boolean)} */
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+        return updateThumbnail(taskId, thumbnailData, true /* refreshNow */);
+    }
+
     @Override
     protected void onWindowVisibilityChanged(int visibility) {
         super.onWindowVisibilityChanged(visibility);
@@ -430,7 +444,7 @@
         super.onViewRemoved(child);
 
         // Clear the task data for the removed child if it was visible
-        if (child != mClearAllButton) {
+        if (child instanceof TaskView) {
             TaskView taskView = (TaskView) child;
             mHasVisibleTaskData.delete(taskView.getTask().key.id);
             mTaskViewPool.recycle(taskView);
@@ -444,7 +458,7 @@
 
     public TaskView getTaskView(int taskId) {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView tv = (TaskView) getChildAt(i);
+            TaskView tv = getTaskViewAt(i);
             if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
                 return tv;
             }
@@ -536,28 +550,26 @@
         }
 
         if (tasks == null || tasks.isEmpty()) {
-            removeAllViews();
+            removeTasksViewsAndClearAllButton();
             onTaskStackUpdated();
             return;
         }
 
-        int oldChildCount = getChildCount();
-
         // Unload existing visible task data
         unloadVisibleTaskData();
 
-        TaskView ignoreRestTaskView =
+        TaskView ignoreResetTaskView =
                 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
 
         final int requiredTaskCount = tasks.size();
         if (getTaskViewCount() != requiredTaskCount) {
-            if (oldChildCount > 0) {
+            if (indexOfChild(mClearAllButton) != -1) {
                 removeView(mClearAllButton);
             }
-            for (int i = getChildCount(); i < requiredTaskCount; i++) {
+            for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
                 addView(mTaskViewPool.getView());
             }
-            while (getChildCount() > requiredTaskCount) {
+            while (getTaskViewCount() > requiredTaskCount) {
                 removeView(getChildAt(getChildCount() - 1));
             }
             if (requiredTaskCount > 0) {
@@ -567,17 +579,23 @@
 
         // Rebind and reset all task views
         for (int i = requiredTaskCount - 1; i >= 0; i--) {
-            final int pageIndex = requiredTaskCount - i - 1;
+            final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
             final Task task = tasks.get(i);
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
             taskView.bind(task);
         }
-        TaskView runningTaskView = getRunningTaskView();
-        if (runningTaskView != null) {
-            setCurrentPage(indexOfChild(runningTaskView));
+
+        if (mNextPage == INVALID_PAGE) {
+            // Set the current page to the running task, but not if settling on new task.
+            TaskView runningTaskView = getRunningTaskView();
+            if (runningTaskView != null) {
+                setCurrentPage(indexOfChild(runningTaskView));
+            } else if (getTaskViewCount() > 0) {
+                setCurrentPage(indexOfChild(getTaskViewAt(0)));
+            }
         }
 
-        if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreRestTaskView) {
+        if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
             // If the taskView mapping is changing, do not preserve the visuals. Since we are
             // mostly preserving the first task, and new taskViews are added to the end, it should
             // generally map to the same task.
@@ -588,17 +606,28 @@
         updateEnabledOverlays();
     }
 
+    private void removeTasksViewsAndClearAllButton() {
+        for (int i = getTaskViewCount() - 1; i >= 0; i--) {
+            removeView(getTaskViewAt(i));
+        }
+        if (indexOfChild(mClearAllButton) != -1) {
+            removeView(mClearAllButton);
+        }
+    }
+
     public int getTaskViewCount() {
-        // Account for the clear all button.
-        int childCount = getChildCount();
-        return childCount == 0 ? 0 : childCount - 1;
+        int taskViewCount = getChildCount() - mTaskViewStartIndex;
+        if (indexOfChild(mClearAllButton) != -1) {
+            taskViewCount--;
+        }
+        return taskViewCount;
     }
 
     protected void onTaskStackUpdated() { }
 
     public void resetTaskVisuals() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
-            TaskView taskView = (TaskView) getChildAt(i);
+            TaskView taskView = getTaskViewAt(i);
             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
                 taskView.resetVisualProperties();
                 taskView.setStableAlpha(mContentAlpha);
@@ -704,7 +733,7 @@
     }
 
     /**
-     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+     * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
      * and unloads the associated task data for tasks that are no longer visible.
      */
     public void loadVisibleTaskData() {
@@ -715,15 +744,16 @@
         }
 
         int centerPageIndex = getPageNearestToCenterOfScreen();
-        int numChildren = getTaskViewCount();
+        int numChildren = getChildCount();
         int lower = Math.max(0, centerPageIndex - 2);
         int upper = Math.min(centerPageIndex + 2, numChildren - 1);
 
         // Update the task data for the in/visible children
-        for (int i = 0; i < numChildren; i++) {
-            TaskView taskView = (TaskView) getChildAt(i);
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView taskView = getTaskViewAt(i);
             Task task = taskView.getTask();
-            boolean visible = lower <= i && i <= upper;
+            int index = indexOfChild(taskView);
+            boolean visible = lower <= index && index <= upper;
             if (visible) {
                 if (task == mTmpRunningTask) {
                     // Skip loading if this is the task that we are animating into
@@ -780,8 +810,9 @@
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
 
-        mRecentsAnimationWrapper = null;
-        mClipAnimationHelper = null;
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+        mAppWindowAnimationHelper = null;
 
         unloadVisibleTaskData();
         setCurrentPage(0);
@@ -798,6 +829,10 @@
         return tv == null ? -1 : indexOfChild(tv);
     }
 
+    public int getTaskViewStartIndex() {
+        return mTaskViewStartIndex;
+    }
+
     /**
      * Reloads the view if anything in recents changed.
      */
@@ -840,7 +875,9 @@
         setEnableFreeScroll(true);
         setEnableDrawingLiveTile(true);
         setOnScrollChangeListener(null);
-        setRunningTaskViewShowScreenshot(true);
+        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            setRunningTaskViewShowScreenshot(true);
+        }
         setRunningTaskHidden(false);
         animateUpRunningTaskIconScale();
     }
@@ -853,10 +890,10 @@
      */
     public void showCurrentTask(int runningTaskId) {
         if (getTaskView(runningTaskId) == null) {
-            boolean wasEmpty = getChildCount() == 0;
+            boolean wasEmpty = getTaskViewCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
-            addView(taskView, 0);
+            addView(taskView, mTaskViewStartIndex);
             if (wasEmpty) {
                 addView(mClearAllButton);
             }
@@ -922,14 +959,13 @@
         if (runningTaskView == null) {
             // Launch the first task
             if (getTaskViewCount() > 0) {
-                getTaskViewAt(0).launchTask(true /* animate */);
+                getTaskViewAt(0).launchTask(true);
             }
         } else {
-            TaskView nextTaskView = getNextTaskView();
-            if (nextTaskView != null) {
-                nextTaskView.launchTask(true /* animate */);
+            if (getNextTaskView() != null) {
+                getNextTaskView().launchTask(true);
             } else {
-                runningTaskView.launchTask(true /* animate */);
+                runningTaskView.launchTask(true);
             }
         }
     }
@@ -941,6 +977,10 @@
         }
     }
 
+    public boolean isTaskIconScaledDown(TaskView taskView) {
+        return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
+    }
+
     private void applyRunningTaskIconScale() {
         TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
@@ -1189,7 +1229,7 @@
 
         int count = getTaskViewCount();
         for (int i = 0; i < count; i++) {
-            addDismissedTaskAnimations(getChildAt(i), anim, duration);
+            addDismissedTaskAnimations(getTaskViewAt(i), anim, duration);
         }
 
         mPendingAnimation = pendingAnimation;
@@ -1197,7 +1237,7 @@
             if (onEndListener.isSuccess) {
                 // Remove all the task views now
                 ActivityManagerWrapper.getInstance().removeAllRecentTasks();
-                removeAllViews();
+                removeTasksViewsAndClearAllButton();
                 startHome();
             }
             mPendingAnimation = null;
@@ -1344,26 +1384,50 @@
         child.setAlpha(mContentAlpha);
     }
 
-    /**
-     * @return The most recent task that is older than the currently running task. If there is
-     * currently no running task or there is no task older than it, then return null.
-     */
     @Nullable
     public TaskView getNextTaskView() {
-        TaskView runningTaskView = getRunningTaskView();
-        if (runningTaskView == null) {
-            return null;
-        }
-        return getTaskViewAt(indexOfChild(runningTaskView) + 1);
+        return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1);
     }
 
+    @Nullable
+    public TaskView getPreviousTaskView() {
+        return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1);
+    }
+
+    @Nullable
+    public TaskView getCurrentPageTaskView() {
+        return getTaskViewAtByAbsoluteIndex(getCurrentPage());
+    }
+
+    @Nullable
+    public TaskView getNextPageTaskView() {
+        return getTaskViewAtByAbsoluteIndex(getNextPage());
+    }
+
+    @Nullable
+    public TaskView getTaskViewNearestToCenterOfScreen() {
+        return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen());
+    }
+
+    /**
+     * Returns null instead of indexOutOfBoundsError when index is not in range
+     */
+    @Nullable
     public TaskView getTaskViewAt(int index) {
-        View child = getChildAt(index);
-        return child == mClearAllButton ? null : (TaskView) child;
+        return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex);
+    }
+
+    @Nullable
+    private TaskView getTaskViewAtByAbsoluteIndex(int index) {
+        if (index < getChildCount() && index >= 0) {
+            View child = getChildAt(index);
+            return child instanceof TaskView ? (TaskView) child : null;
+        }
+        return null;
     }
 
     public void updateEmptyMessage() {
-        boolean isEmpty = getChildCount() == 0;
+        boolean isEmpty = getTaskViewCount() == 0;
         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
                 || mLastMeasureSize.y != getHeight();
         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
@@ -1459,14 +1523,14 @@
      * to the right.
      */
     public AnimatorSet createAdjacentPageAnimForTaskLaunch(
-            TaskView tv, ClipAnimationHelper clipAnimationHelper) {
+            TaskView tv, AppWindowAnimationHelper appWindowAnimationHelper) {
         AnimatorSet anim = new AnimatorSet();
 
         int taskIndex = indexOfChild(tv);
         int centerTaskIndex = getCurrentPage();
         boolean launchingCenterTask = taskIndex == centerTaskIndex;
 
-        LauncherState.ScaleAndTranslation toScaleAndTranslation = clipAnimationHelper
+        LauncherState.ScaleAndTranslation toScaleAndTranslation = appWindowAnimationHelper
                 .getScaleAndTranslation();
         float toScale = toScaleAndTranslation.scale;
         float toTranslationY = toScaleAndTranslation.translationY;
@@ -1497,7 +1561,7 @@
             throw new IllegalStateException("Another pending animation is still running");
         }
 
-        int count = getChildCount();
+        int count = getTaskViewCount();
         if (count == 0) {
             return new PendingAnimation(new AnimatorSet());
         }
@@ -1526,10 +1590,10 @@
             }
         });
 
-        ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(mActivity);
-        clipAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
-        clipAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
-        AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, clipAnimationHelper);
+        AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(mActivity);
+        appWindowAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
+        appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
+        AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
         anim.play(progressAnim);
         anim.setDuration(duration);
 
@@ -1632,12 +1696,16 @@
 
     public void redrawLiveTile(boolean mightNeedToRefill) { }
 
-    public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
-        mRecentsAnimationWrapper = recentsAnimationWrapper;
+    // TODO: To be removed in a follow up CL
+    public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
+            RecentsAnimationTargets recentsAnimationTargets) {
+        mRecentsAnimationController = recentsAnimationController;
+        mRecentsAnimationTargets = recentsAnimationTargets;
     }
 
-    public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) {
-        mClipAnimationHelper = clipAnimationHelper;
+    // TODO: To be removed in a follow up CL
+    public void setAppWindowAnimationHelper(AppWindowAnimationHelper appWindowAnimationHelper) {
+        mAppWindowAnimationHelper = appWindowAnimationHelper;
     }
 
     public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
@@ -1651,14 +1719,14 @@
     }
 
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
-        if (mRecentsAnimationWrapper == null) {
+        if (mRecentsAnimationController == null) {
             if (onFinishComplete != null) {
                 onFinishComplete.run();
             }
             return;
         }
 
-        mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
+        mRecentsAnimationController.finish(toRecents, onFinishComplete);
     }
 
     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
@@ -1670,18 +1738,38 @@
 
     @Override
     protected int computeMinScrollX() {
-        if (mIsRtl && mDisallowScrollToClearAll) {
-            // We aren't showing the clear all button, so use the leftmost task as the min scroll.
-            return getScrollForPage(getTaskViewCount() - 1);
+        if (getTaskViewCount() > 0) {
+            if (mDisallowScrollToClearAll) {
+                // We aren't showing the clear all button,
+                // so use the leftmost task as the min scroll.
+                if (mIsRtl) {
+                    return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+                }
+                return getScrollForPage(mTaskViewStartIndex);
+            }
+            if (mIsRtl) {
+                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+            }
+            return getScrollForPage(mTaskViewStartIndex);
         }
         return super.computeMinScrollX();
     }
 
     @Override
     protected int computeMaxScrollX() {
-        if (!mIsRtl && mDisallowScrollToClearAll) {
-            // We aren't showing the clear all button, so use the rightmost task as the max scroll.
-            return getScrollForPage(getTaskViewCount() - 1);
+        if (getTaskViewCount() > 0) {
+            if (mDisallowScrollToClearAll) {
+                // We aren't showing the clear all button,
+                // so use the rightmost task as the min scroll.
+                if (mIsRtl) {
+                    return getScrollForPage(mTaskViewStartIndex);
+                }
+                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+            }
+            if (mIsRtl) {
+                return getScrollForPage(mTaskViewStartIndex);
+            }
+            return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
         }
         return super.computeMaxScrollX();
     }
@@ -1725,15 +1813,24 @@
         }
     }
 
-    public ClipAnimationHelper getTempClipAnimationHelper() {
-        return mTempClipAnimationHelper;
+    public AppWindowAnimationHelper getClipAnimationHelper() {
+        return mAppWindowAnimationHelper;
+    }
+
+    public AppWindowAnimationHelper getTempAppWindowAnimationHelper() {
+        return mTempAppWindowAnimationHelper;
+    }
+
+    public AppWindowAnimationHelper.TransformParams getLiveTileParams(
+            boolean mightNeedToRefill) {
+        return null;
     }
 
     private void updateEnabledOverlays() {
         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            ((TaskView) getChildAt(i)).setOverlayEnabled(i == overlayEnabledPage);
+            getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage);
         }
     }
 
@@ -1753,4 +1850,41 @@
         final WindowInsets insets = getRootWindowInsets();
         return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
     }
+
+
+    /** If it's in the live tile mode, switch the running task into screenshot mode. */
+    public void switchToScreenshot(Runnable onFinishRunnable) {
+        TaskView taskView = getRunningTaskView();
+        if (taskView == null) {
+            if (onFinishRunnable != null) {
+                onFinishRunnable.run();
+            }
+            return;
+        }
+
+        taskView.setShowScreenshot(true);
+        taskView.getThumbnail().refresh();
+        ViewUtils.postDraw(taskView, onFinishRunnable);
+    }
+
+    @Override
+    public void addView(View child, int index) {
+        super.addView(child, index);
+        if (isExtraCardView(child, index)) {
+            mTaskViewStartIndex++;
+        }
+    }
+
+    @Override
+    public void removeView(View view) {
+        if (isExtraCardView(view, indexOfChild(view))) {
+            mTaskViewStartIndex--;
+        }
+        super.removeView(view);
+    }
+
+    private boolean isExtraCardView(View view, int index) {
+        return !(view instanceof TaskView) && !(view instanceof ClearAllButton)
+                && index <= mTaskViewStartIndex;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index c1f6b82..07d0796 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
@@ -40,6 +41,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.TaskOverlayFactory;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 7f1e898..adeb974 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -39,24 +39,27 @@
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.util.TaskCornerRadius;
+import com.android.systemui.plugins.OverviewScreenshotActions;
+import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * A task in the Recents view.
  */
-public class TaskThumbnailView extends View {
+public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
 
     private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
     private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
@@ -100,6 +103,7 @@
 
     private boolean mOverlayEnabled;
     private boolean mRotated;
+    private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -130,16 +134,35 @@
     }
 
     /**
-     * Updates this thumbnail.
+     * Updates the thumbnail.
+     * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
+     *                   In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
+     *                   version with {@code refreshNow} is true. The only exception is
+     *                   in the live tile case that we grab a screenshot when user enters Overview
+     *                   upon swipe up so that a usable screenshot is accessible immediately when
+     *                   recents animation needs to be finished / cancelled.
      */
-    public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+    public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
         mTask = task;
-        if (thumbnailData != null && thumbnailData.thumbnail != null) {
-            Bitmap bm = thumbnailData.thumbnail;
+        mThumbnailData =
+                (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
+        if (refreshNow) {
+            refresh();
+        }
+    }
+
+    /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
+    public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+        setThumbnail(task, thumbnailData, true /* refreshNow */);
+    }
+
+    /** Updates the shader, paint, matrix to redraw. */
+    public void refresh() {
+        if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
+            Bitmap bm = mThumbnailData.thumbnail;
             bm.prepareToDraw();
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mPaint.setShader(mBitmapShader);
-            mThumbnailData = thumbnailData;
             updateThumbnailMatrix();
         } else {
             mBitmapShader = null;
@@ -147,6 +170,10 @@
             mPaint.setShader(null);
             mOverlay.reset();
         }
+        if (mOverviewScreenshotActionsPlugin != null) {
+            mOverviewScreenshotActionsPlugin
+                .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+        }
         updateThumbnailPaintFilter();
     }
 
@@ -211,6 +238,33 @@
         canvas.restore();
     }
 
+    @Override
+    public void onPluginConnected(OverviewScreenshotActions overviewScreenshotActions,
+            Context context) {
+        mOverviewScreenshotActionsPlugin = overviewScreenshotActions;
+        mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
+    }
+
+    @Override
+    public void onPluginDisconnected(OverviewScreenshotActions plugin) {
+        if (mOverviewScreenshotActionsPlugin != null) {
+            mOverviewScreenshotActionsPlugin = null;
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext())
+            .addPluginListener(this, OverviewScreenshotActions.class);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
+    }
+
     public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
         // Don't show insets in multi window mode.
         return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
@@ -308,8 +362,7 @@
                 final Configuration configuration =
                         getContext().getResources().getConfiguration();
                 // Rotate the screenshot if not in multi-window mode
-                isRotated = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
-                        configuration.orientation != mThumbnailData.orientation &&
+                isRotated = configuration.orientation != mThumbnailData.orientation &&
                         !mActivity.isInMultiWindowMode() &&
                         mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
                 // Scale the screenshot to always fit the width of the card.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 2211eb4..bfb9613 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -28,7 +28,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.res.Resources;
@@ -54,7 +53,6 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -818,8 +816,11 @@
                     / (getWidth() + currentInsetsLeft + currentInsetsRight));
         }
 
-        // Some of the items in here are dependent on the current fullscreen params
-        setIconScaleAndDim(progress, true /* invert */);
+        if (!getRecentsView().isTaskIconScaledDown(this)) {
+            // Some of the items in here are dependent on the current fullscreen params, but don't
+            // update them if the icon is supposed to be scaled down.
+            setIconScaleAndDim(progress, true /* invert */);
+        }
 
         thumbnail.setFullscreenParams(mCurrentFullscreenParams);
         mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 5c4d6d8..98aaceb 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -16,8 +16,6 @@
 <resources>
     <string name="task_overlay_factory_class" translatable="false"></string>
 
-    <string name="overview_callbacks_class" translatable="false"></string>
-
     <!-- Activity which blocks home gesture -->
     <string name="gesture_blocking_activity" translatable="false"></string>
 
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index b0968f9..78424ca 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -69,6 +69,7 @@
     <!-- Distance from the vertical edges of the screen in which assist gestures are recognized -->
     <dimen name="gestures_assistant_width">48dp</dimen>
     <dimen name="gestures_assistant_drag_threshold">55dp</dimen>
+    <dimen name="gestures_assistant_fling_threshold">55dp</dimen>
 
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index a8e2956..96ac489 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,8 +17,7 @@
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
-import static com.android.systemui.shared.recents.utilities.Utilities
-        .postAtFrontOfQueueAsynchronously;
+import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,12 +27,12 @@
 import android.os.Build;
 import android.os.Handler;
 
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
 @TargetApi(Build.VERSION_CODES.P)
 public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
 
@@ -50,13 +49,14 @@
         mStartAtFrontOfQueue = startAtFrontOfQueue;
     }
 
+    // Called only in R+ platform
     @BinderThread
-    @Override
-    public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
+    public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
         Runnable r = () -> {
             finishExistingAnimation();
             mAnimationResult = new AnimationResult(runnable);
-            onCreateAnimation(targetCompats, mAnimationResult);
+            onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
         };
         if (mStartAtFrontOfQueue) {
             postAtFrontOfQueueAsynchronously(mHandler, r);
@@ -65,13 +65,21 @@
         }
     }
 
+    // Called only in Q platform
+    @BinderThread
+    @Deprecated
+    public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets, Runnable runnable) {
+        onAnimationStart(appTargets, new RemoteAnimationTargetCompat[0], runnable);
+    }
+
     /**
      * Called on the UI thread when the animation targets are received. The implementation must
      * call {@link AnimationResult#setAnimation} with the target animation to be run.
      */
     @UiThread
     public abstract void onCreateAnimation(
-            RemoteAnimationTargetCompat[] targetCompats, AnimationResult result);
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result);
 
     @UiThread
     private void finishExistingAnimation() {
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index b9ce1ce..663b125 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -19,30 +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.OverviewCallbacks;
+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();
@@ -50,7 +45,7 @@
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
             CancellationSignal cancellationSignal = new CancellationSignal();
-            appTransitionManager.setRemoteAnimationProvider((targets) -> {
+            appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
 
                 // On the first call clear the reference.
                 cancellationSignal.cancel();
@@ -58,34 +53,25 @@
                 mRemoteAnimationProvider = null;
 
                 if (provider != null && launcher.getStateManager().getState().overviewUi) {
-                    return provider.createWindowAnimation(targets);
+                    return provider.createWindowAnimation(appTargets, wallpaperTargets);
                 }
                 return null;
             }, cancellationSignal);
         }
-        OverviewCallbacks.get(launcher).onInitOverviewTransition();
-        return mOnInitListener.test(launcher, alreadyOnHome);
-    }
-
-    @Override
-    public void register() {
-        initWhenReady();
+        launcher.deferOverlayCallbacksUntilNextResumeOrStop();
+        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/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 991408c..d4db05a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -57,6 +57,9 @@
 import android.util.Pair;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.Interpolators;
@@ -68,7 +71,7 @@
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -80,9 +83,6 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 /**
  * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
  * home and/or all-apps.
@@ -202,17 +202,19 @@
                     true /* startAtFrontOfQueue */) {
 
                 @Override
-                public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
-                        AnimationResult result) {
+                public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                        RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
                     AnimatorSet anim = new AnimatorSet();
 
                     boolean launcherClosing =
-                            launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
+                            launcherIsATargetWithMode(appTargets, MODE_CLOSING);
 
-                    if (isLaunchingFromRecents(v, targetCompats)) {
-                        composeRecentsLaunchAnimator(anim, v, targetCompats, launcherClosing);
+                    if (isLaunchingFromRecents(v, appTargets)) {
+                        composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+                                launcherClosing);
                     } else {
-                        composeIconLaunchAnimator(anim, v, targetCompats, launcherClosing);
+                        composeIconLaunchAnimator(anim, v, appTargets, wallpaperTargets,
+                                launcherClosing);
                     }
 
                     if (launcherClosing) {
@@ -255,36 +257,39 @@
      *
      * @param anim the animator set to add to
      * @param v the launching view
-     * @param targets the apps that are opening/closing
+     * @param appTargets the apps that are opening/closing
      * @param launcherClosing true if the launcher app is closing
      */
     protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
-            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing);
+            @NonNull RemoteAnimationTargetCompat[] appTargets,
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing);
 
     /**
      * Compose the animations for a launch from the app icon.
      *
      * @param anim the animation to add to
      * @param v the launching view with the icon
-     * @param targets the list of opening/closing apps
+     * @param appTargets the list of opening/closing apps
      * @param launcherClosing true if launcher is closing
      */
     private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
-            @NonNull RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
+            @NonNull RemoteAnimationTargetCompat[] appTargets,
+            @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
+            boolean launcherClosing) {
         // Set the state animation first so that any state listeners are called
         // before our internal listeners.
         mLauncher.getStateManager().setCurrentAnimation(anim);
 
-        Rect windowTargetBounds = getWindowTargetBounds(targets);
+        Rect windowTargetBounds = getWindowTargetBounds(appTargets);
         boolean isAllOpeningTargetTrs = true;
-        for (int i = 0; i < targets.length; i++) {
-            RemoteAnimationTargetCompat target = targets[i];
+        for (int i = 0; i < appTargets.length; i++) {
+            RemoteAnimationTargetCompat target = appTargets[i];
             if (target.mode == MODE_OPENING) {
                 isAllOpeningTargetTrs &= target.isTranslucent;
             }
             if (!isAllOpeningTargetTrs) break;
         }
-        anim.play(getOpeningWindowAnimators(v, targets, windowTargetBounds,
+        anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds,
                 !isAllOpeningTargetTrs));
         if (launcherClosing) {
             Pair<AnimatorSet, Runnable> launcherContentAnimator =
@@ -305,10 +310,10 @@
      * In multiwindow mode, we need to get the final size of the opening app window target to help
      * figure out where the floating view should animate to.
      */
-    private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) {
+    private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) {
         Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
         if (mLauncher.isInMultiWindowMode()) {
-            for (RemoteAnimationTargetCompat target : targets) {
+            for (RemoteAnimationTargetCompat target : appTargets) {
                 if (target.mode == MODE_OPENING) {
                     bounds.set(target.sourceContainerBounds);
                     bounds.offsetTo(target.position.x, target.position.y);
@@ -418,7 +423,9 @@
     /**
      * @return Animator that controls the window of the opening targets.
      */
-    private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
+    private ValueAnimator getOpeningWindowAnimators(View v,
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect windowTargetBounds, boolean toggleVisibility) {
         RectF bounds = new RectF();
         FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v,
@@ -426,8 +433,8 @@
         Rect crop = new Rect();
         Matrix matrix = new Matrix();
 
-        RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
-                MODE_OPENING);
+        RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
+                wallpaperTargets, MODE_OPENING);
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(floatingView);
         openingTargets.addDependentTransactionApplier(surfaceApplier);
@@ -551,9 +558,9 @@
 
                 float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale;
                 float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale;
-                SurfaceParams[] params = new SurfaceParams[targets.length];
-                for (int i = targets.length - 1; i >= 0; i--) {
-                    RemoteAnimationTargetCompat target = targets[i];
+                SurfaceParams[] params = new SurfaceParams[appTargets.length];
+                for (int i = appTargets.length - 1; i >= 0; i--) {
+                    RemoteAnimationTargetCompat target = appTargets[i];
                     Rect targetCrop;
                     final float alpha;
                     final float cornerRadius;
@@ -619,7 +626,8 @@
     /**
      * Animator that controls the transformations of the windows when unlocking the device.
      */
-    private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] targets) {
+    private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
         ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1);
@@ -629,9 +637,9 @@
         unlockAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                SurfaceParams[] params = new SurfaceParams[targets.length];
-                for (int i = targets.length - 1; i >= 0; i--) {
-                    RemoteAnimationTargetCompat target = targets[i];
+                SurfaceParams[] params = new SurfaceParams[appTargets.length];
+                for (int i = appTargets.length - 1; i >= 0; i--) {
+                    RemoteAnimationTargetCompat target = appTargets[i];
                     params[i] = new SurfaceParams(target.leash, 1f, null,
                             target.sourceContainerBounds,
                             RemoteAnimationProvider.getLayer(target, MODE_OPENING), cornerRadius);
@@ -645,7 +653,8 @@
     /**
      * Animator that controls the transformations of the windows the targets that are closing.
      */
-    private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
+    private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
         Matrix matrix = new Matrix();
@@ -661,9 +670,9 @@
 
             @Override
             public void onUpdate(float percent) {
-                SurfaceParams[] params = new SurfaceParams[targets.length];
-                for (int i = targets.length - 1; i >= 0; i--) {
-                    RemoteAnimationTargetCompat target = targets[i];
+                SurfaceParams[] params = new SurfaceParams[appTargets.length];
+                for (int i = appTargets.length - 1; i >= 0; i--) {
+                    RemoteAnimationTargetCompat target = appTargets[i];
                     final float alpha;
                     final float cornerRadius;
                     if (target.mode == MODE_CLOSING) {
@@ -764,13 +773,21 @@
         }
 
         @Override
-        public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+        public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                RemoteAnimationTargetCompat[] wallpaperTargets,
                 LauncherAnimationRunner.AnimationResult result) {
+            if (mLauncher.isDestroyed()) {
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets));
+                result.setAnimation(anim, mLauncher.getApplicationContext());
+                return;
+            }
+
             if (!mLauncher.hasBeenResumed()) {
                 // If launcher is not resumed, wait until new async-frame after resume
                 mLauncher.addOnResumeCallback(() ->
                         postAsyncCallback(mHandler, () ->
-                                onCreateAnimation(targetCompats, result)));
+                                onCreateAnimation(appTargets, wallpaperTargets, result)));
                 return;
             }
 
@@ -782,14 +799,14 @@
             AnimatorSet anim = null;
             RemoteAnimationProvider provider = mRemoteAnimationProvider;
             if (provider != null) {
-                anim = provider.createWindowAnimation(targetCompats);
+                anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
             }
 
             if (anim == null) {
                 anim = new AnimatorSet();
                 anim.play(mFromUnlock
-                        ? getUnlockWindowAnimator(targetCompats)
-                        : getClosingWindowAnimators(targetCompats));
+                        ? getUnlockWindowAnimator(appTargets, wallpaperTargets)
+                        : getClosingWindowAnimators(appTargets, wallpaperTargets));
 
                 // Normally, we run the launcher content animation when we are transitioning
                 // home, but if home is already visible, then we don't want to animate the
@@ -799,7 +816,7 @@
                 // targets list because it is already visible). In that case, we force
                 // invisibility on touch down, and only reset it after the animation to home
                 // is initialized.
-                if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+                if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
                         || mLauncher.isForceInvisible()) {
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 693ae60..aa0dfc3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -24,18 +24,18 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
 
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
     private static final String TAG = "BackButtonAlphaHandler";
 
     private final Launcher mLauncher;
-    private final OverviewInteractionState mOverviewInteractionState;
 
     public BackButtonAlphaHandler(Launcher launcher) {
         mLauncher = launcher;
-        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
     }
 
     @Override
@@ -49,14 +49,23 @@
         if (!config.playNonAtomicComponent()) {
             return;
         }
-        float fromAlpha = mOverviewInteractionState.getBackButtonAlpha();
+
+        if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
+            // If the nav mode is not gestural, then force back button alpha to be 1
+            UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, 1f,
+                    true /* animate */);
+            return;
+        }
+
+        float fromAlpha = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
         float toAlpha = toState.hideBackButton ? 0 : 1;
         if (Float.compare(fromAlpha, toAlpha) != 0) {
             ValueAnimator anim = ValueAnimator.ofFloat(fromAlpha, toAlpha);
             anim.setDuration(config.duration);
             anim.addUpdateListener(valueAnimator -> {
                 final float alpha = (float) valueAnimator.getAnimatedValue();
-                mOverviewInteractionState.setBackButtonAlpha(alpha, false);
+                UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA,
+                        alpha, false /* animate */);
             });
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
new file mode 100644
index 0000000..d8aa235
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java
@@ -0,0 +1,159 @@
+/**
+ * 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.uioverrides;
+
+import static android.os.IBinder.FLAG_ONEWAY;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.MainThread;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+/**
+ * A binder proxy transaction listener for tracking non-whitelisted binder calls.
+ */
+public class DejankBinderTracker implements Binder.ProxyTransactListener {
+    private static final String TAG = "DejankBinderTracker";
+
+    private static final Object sLock = new Object();
+    private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
+    static {
+        // Common IPCs that are ok to block the main thread.
+        sWhitelistedFrameworkClasses.add("android.view.IWindowSession");
+        sWhitelistedFrameworkClasses.add("android.os.IPowerManager");
+    }
+    private static boolean sTemporarilyIgnoreTracking = false;
+
+    // Used by the client to limit binder tracking to specific regions
+    private static boolean sTrackingAllowed = false;
+
+    private BiConsumer<String, Integer> mUnexpectedTransactionCallback;
+    private boolean mIsTracking = false;
+
+    /**
+     * Temporarily ignore blocking binder calls for the duration of this {@link Runnable}.
+     */
+    @MainThread
+    public static void whitelistIpcs(Runnable runnable) {
+        sTemporarilyIgnoreTracking = true;
+        runnable.run();
+        sTemporarilyIgnoreTracking = false;
+    }
+
+    /**
+     * Temporarily ignore blocking binder calls for the duration of this {@link Supplier}.
+     */
+    @MainThread
+    public static <T> T whitelistIpcs(Supplier<T> supplier) {
+        sTemporarilyIgnoreTracking = true;
+        T value = supplier.get();
+        sTemporarilyIgnoreTracking = false;
+        return value;
+    }
+
+    /**
+     * Enables binder tracking during a test.
+     */
+    @MainThread
+    public static void allowBinderTrackingInTests() {
+        sTrackingAllowed = true;
+    }
+
+    /**
+     * Disables binder tracking during a test.
+     */
+    @MainThread
+    public static void disallowBinderTrackingInTests() {
+        sTrackingAllowed = false;
+    }
+
+    public DejankBinderTracker(BiConsumer<String, Integer> unexpectedTransactionCallback) {
+        mUnexpectedTransactionCallback = unexpectedTransactionCallback;
+    }
+
+    @MainThread
+    public void startTracking() {
+        if (!Build.TYPE.toLowerCase(Locale.ROOT).contains("debug")
+                && !Build.TYPE.toLowerCase(Locale.ROOT).equals("eng")) {
+            Log.wtf(TAG, "Unexpected use of binder tracker in non-debug build", new Exception());
+            return;
+        }
+        if (mIsTracking) {
+            return;
+        }
+        mIsTracking = true;
+        Binder.setProxyTransactListener(this);
+    }
+
+    @MainThread
+    public void stopTracking() {
+        if (!mIsTracking) {
+            return;
+        }
+        mIsTracking = false;
+        Binder.setProxyTransactListener(null);
+    }
+
+    // Override the hidden Binder#onTransactStarted method
+    public synchronized Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
+        if (!mIsTracking
+                || !sTrackingAllowed
+                || sTemporarilyIgnoreTracking
+                || (flags & FLAG_ONEWAY) == FLAG_ONEWAY
+                || !isMainThread()) {
+            return null;
+        }
+
+        String descriptor;
+        try {
+            descriptor = binder.getInterfaceDescriptor();
+            if (sWhitelistedFrameworkClasses.contains(descriptor)) {
+                return null;
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            descriptor = binder.getClass().getSimpleName();
+        }
+
+        mUnexpectedTransactionCallback.accept(descriptor, transactionCode);
+        return null;
+    }
+
+    @Override
+    public Object onTransactStarted(IBinder binder, int transactionCode) {
+        // Do nothing
+        return null;
+    }
+
+    @Override
+    public void onTransactEnded(Object session) {
+        // Do nothing
+    }
+
+    public static boolean isMainThread() {
+        return Thread.currentThread() == Looper.getMainLooper().getThread();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
index e425088..27d81ef 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -16,17 +16,35 @@
 
 package com.android.launcher3.uioverrides;
 
+import android.content.Context;
 import android.provider.DeviceConfig;
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+
+import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag;
 
 public class TogglableFlag extends BaseTogglableFlag {
+    public static final String NAMESPACE_LAUNCHER = "launcher";
+    public static final String TAG = "TogglableFlag";
 
     public TogglableFlag(String key, boolean defaultValue, String description) {
         super(key, defaultValue, description);
     }
 
     @Override
-    public boolean getInitialValue(boolean value) {
-        return DeviceConfig.getBoolean("launcher", getKey(), value);
+    public boolean getOverridenDefaultValue(boolean value) {
+        return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value);
+    }
+
+    @Override
+    public void addChangeListener(Context context, Runnable r) {
+        DeviceConfig.addOnPropertiesChangedListener(
+                NAMESPACE_LAUNCHER,
+                context.getMainExecutor(),
+                (properties) -> {
+                    if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) {
+                        return;
+                    }
+                    initialize(context);
+                    r.run();
+                });
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index c02df93..17c681b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -51,11 +51,12 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
 
@@ -65,6 +66,15 @@
 
 public class UiFactory extends RecentsUiFactory {
 
+    /**
+     * Reusable command for applying the back button alpha on the background thread.
+     */
+    public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
+            (context, arg1, arg2) -> {
+        SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(Float.intBitsToFloat(arg1),
+                arg2 != 0);
+    };
+
     public static Runnable enableLiveUIChanges(Launcher launcher) {
         NavigationModeChangeListener listener = m -> {
             launcher.getDragLayer().recreateControllers();
@@ -88,7 +98,9 @@
      * Sets the back button visibility based on the current state/window focus.
      */
     public static void onLauncherStateOrFocusChanged(Launcher launcher) {
-        boolean shouldBackButtonBeHidden = launcher != null
+        Mode mode = SysUINavigationMode.getMode(launcher);
+        boolean shouldBackButtonBeHidden = mode.hasGestures
+                && launcher != null
                 && launcher.getStateManager().getState().hideBackButton
                 && launcher.hasWindowFocus();
         if (shouldBackButtonBeHidden) {
@@ -96,8 +108,8 @@
             shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
                     TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
         }
-        OverviewInteractionState.INSTANCE.get(launcher)
-                .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
+        UiThreadHelper.setBackButtonAlphaAsync(launcher, UiFactory.SET_BACK_BUTTON_ALPHA,
+                shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
         if (launcher != null && launcher.getDragLayer() != null) {
             launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
         }
@@ -165,13 +177,14 @@
             CancellationSignal cancellationSignal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) launcher.getAppTransitionManager();
-        appTransitionManager.setRemoteAnimationProvider((targets) -> {
+        appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
 
             // On the first call clear the reference.
             cancellationSignal.cancel();
 
             ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
-            fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(targets));
+            fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
+                    wallpaperTargets));
             AnimatorSet anim = new AnimatorSet();
             anim.play(fadeAnimation);
             return anim;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b81edfa..ef6a5e2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -47,8 +47,8 @@
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
 
@@ -137,8 +137,7 @@
         } else if (fromState == OVERVIEW) {
             return isDragTowardPositive ? ALL_APPS : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
-            int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher)
-                    .getSystemUiStateFlags();
+            int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
             return mAllowDragToOverview && TouchInteractionService.isConnected()
                     && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
                     ? OVERVIEW : ALL_APPS;
@@ -177,6 +176,20 @@
         return builder;
     }
 
+    private AnimatorSetBuilder getNormalToAllAppsAnimation() {
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+                0, ALL_APPS_CONTENT_FADE_THRESHOLD));
+        return builder;
+    }
+
+    private AnimatorSetBuilder getAllAppsToNormalAnimation() {
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
+                1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
+        return builder;
+    }
+
     @Override
     protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
             LauncherState toState) {
@@ -187,6 +200,10 @@
             builder = getOverviewToAllAppsAnimation();
         } else if (fromState == ALL_APPS && toState == OVERVIEW) {
             builder = getAllAppsToOverviewAnimation();
+        } else if (fromState == NORMAL && toState == ALL_APPS) {
+            builder = getNormalToAllAppsAnimation();
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            builder = getAllAppsToNormalAnimation();
         }
         return builder;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 11a8043..16bd9ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -21,8 +21,6 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 
 import android.graphics.PointF;
-import android.os.RemoteException;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -37,9 +35,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsModel;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 
+import com.android.quickstep.SystemUiProxy;
 import java.io.PrintWriter;
 
 /**
@@ -62,9 +59,9 @@
      */
     private static final int FLAG_SLIPPERY = 0x20000000;
 
-    protected final Launcher mLauncher;
+    private final Launcher mLauncher;
+    private final SystemUiProxy mSystemUiProxy;
     private final float mTouchSlop;
-    private ISystemUiProxy mSysUiProxy;
     private int mLastAction;
     private final SparseArray<PointF> mDownEvents;
 
@@ -73,6 +70,7 @@
 
     public StatusBarTouchController(Launcher l) {
         mLauncher = l;
+        mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher);
         // Guard against TAPs by increasing the touch slop.
         mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
         mDownEvents = new SparseArray<>();
@@ -82,17 +80,14 @@
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "mCanIntercept:" + mCanIntercept);
         writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
-        writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
+        writer.println(prefix + "mSysUiProxy available:"
+                + SystemUiProxy.INSTANCE.get(mLauncher).isActive());
     }
 
     private void dispatchTouchEvent(MotionEvent ev) {
-        try {
-            if (mSysUiProxy != null) {
-                mLastAction = ev.getActionMasked();
-                mSysUiProxy.onStatusBarMotionEvent(ev);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception on sysUiProxy.", e);
+        if (mSystemUiProxy.isActive()) {
+            mLastAction = ev.getActionMasked();
+            mSystemUiProxy.onStatusBarMotionEvent(ev);
         }
     }
 
@@ -170,7 +165,6 @@
                 return false;
             }
         }
-        mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
-        return mSysUiProxy != null;
+        return SystemUiProxy.INSTANCE.get(mLauncher).isActive();
     }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
similarity index 87%
rename from quickstep/src/com/android/quickstep/ActivityControlHelper.java
rename to quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 5c9c7d4..409bec6 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -17,12 +17,9 @@
 
 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,8 +31,8 @@
 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.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.BiPredicate;
@@ -45,7 +42,7 @@
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public interface ActivityControlHelper<T extends BaseDraggingActivity> {
+public interface BaseActivityInterface<T extends BaseDraggingActivity> {
 
     void onTransitionCancelled(T activity, boolean activityVisible);
 
@@ -82,7 +79,7 @@
 
     boolean shouldMinimizeSplitScreen();
 
-    default boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
+    default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
         return true;
     }
 
@@ -97,15 +94,9 @@
 
     void onLaunchTaskSuccess(T activity);
 
-    interface ActivityInitListener {
+    default void closeOverlay() { }
 
-        void register();
-
-        void unregister();
-
-        void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
-                Context context, Handler handler, long duration);
-    }
+    default void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {}
 
     interface AnimationFactory {
 
@@ -119,9 +110,9 @@
             public final boolean shouldPreformHaptic;
         }
 
-        default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
+        default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
 
-        void createActivityController(long transitionLength);
+        void createActivityInterface(long transitionLength);
 
         default void adjustActivityControllerInterpolators() { }
 
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index c840132..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
@@ -157,6 +159,6 @@
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
         writer.println(prefix + "Misc:");
-        dumpMisc(writer);
+        dumpMisc(prefix + "\t", writer);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
new file mode 100644
index 0000000..de64227
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -0,0 +1,36 @@
+/*
+ * 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.BaseDraggingActivity;
+
+/**
+ * Manages the state for an active system gesture, listens for events from the system and Launcher,
+ * and fires events when the states change.
+ */
+public class GestureState {
+
+    // Needed to interact with the current activity
+    private BaseActivityInterface mActivityInterface;
+
+    public GestureState(BaseActivityInterface activityInterface) {
+        mActivityInterface = activityInterface;
+    }
+
+    public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
+        return mActivityInterface;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
rename to quickstep/src/com/android/quickstep/InputConsumer.java
index a1e5d47..62c0ded 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep.inputconsumers;
+package com.android.quickstep;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -33,6 +33,7 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
+    int TYPE_QUICK_CAPTURE = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -44,6 +45,7 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
+            "TYPE_QUICK_CAPTURE",           // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/OverviewCallbacks.java b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
deleted file mode 100644
index f5573ba..0000000
--- a/quickstep/src/com/android/quickstep/OverviewCallbacks.java
+++ /dev/null
@@ -1,43 +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 android.content.Context;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Callbacks related to overview/quicksteps.
- */
-public class OverviewCallbacks implements ResourceBasedOverride {
-
-    private static OverviewCallbacks sInstance;
-
-    public static OverviewCallbacks get(Context context) {
-        Preconditions.assertUIThread();
-        if (sInstance == null) {
-            sInstance = Overrides.getObject(OverviewCallbacks.class,
-                    context.getApplicationContext(), R.string.overview_callbacks_class);
-        }
-        return sInstance;
-    }
-
-    public void onInitOverviewTransition() { }
-
-    public void closeAllWindows() { }
-}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 88a4eb6..73b78db 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -57,19 +56,21 @@
         }
     };
     private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final Intent mCurrentHomeIntent;
     private final Intent mMyHomeIntent;
     private final Intent mFallbackIntent;
     private final SparseIntArray mConfigChangesMap = new SparseIntArray();
     private String mUpdateRegisteredPackage;
-    private ActivityControlHelper mActivityControlHelper;
+    private BaseActivityInterface mActivityInterface;
     private Intent mOverviewIntent;
-    private int mSystemUiStateFlags;
     private boolean mIsHomeAndOverviewSame;
     private boolean mIsDefaultHome;
+    private boolean mIsHomeDisabled;
 
-    public OverviewComponentObserver(Context context) {
+    public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
         mContext = context;
+        mDeviceState = deviceState;
 
         mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
@@ -98,36 +99,33 @@
         updateOverviewTargets();
     }
 
-    public void onSystemUiStateChanged(int stateFlags) {
-        boolean homeDisabledChanged = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED)
-                != (stateFlags & SYSUI_STATE_HOME_DISABLED);
-        mSystemUiStateFlags = stateFlags;
-        if (homeDisabledChanged) {
+    public void onSystemUiStateChanged() {
+        if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
             updateOverviewTargets();
         }
     }
 
     /**
-     * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
+     * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
      * component.
      */
     private void updateOverviewTargets() {
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
 
+        mIsHomeDisabled = mDeviceState.isHomeDisabled();
         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
 
         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
         // launcher made invisible become visible again before the new activity control helper
         // becomes active.
-        if (mActivityControlHelper != null) {
-            mActivityControlHelper.onAssistantVisibilityChanged(0.f);
+        if (mActivityInterface != null) {
+            mActivityInterface.onAssistantVisibilityChanged(0.f);
         }
 
-        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
-                && (defaultHome == null || mIsDefaultHome)) {
+        if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            mActivityControlHelper = new LauncherActivityControllerHelper();
+            mActivityInterface = new LauncherActivityInterface();
             mIsHomeAndOverviewSame = true;
             mOverviewIntent = mMyHomeIntent;
             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -140,7 +138,7 @@
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
 
-            mActivityControlHelper = new FallbackActivityControllerHelper();
+            mActivityInterface = new FallbackActivityInterface();
             mIsHomeAndOverviewSame = false;
             mOverviewIntent = mFallbackIntent;
             mCurrentHomeIntent.setComponent(defaultHome);
@@ -232,7 +230,7 @@
      *
      * @return the current activity control helper
      */
-    public ActivityControlHelper getActivityControlHelper() {
-        return mActivityControlHelper;
+    public BaseActivityInterface getActivityInterface() {
+        return mActivityInterface;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
deleted file mode 100644
index 858c3b6..0000000
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ /dev/null
@@ -1,146 +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.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-
-/**
- * Sets alpha for the back button
- */
-public class OverviewInteractionState {
-
-    private static final String TAG = "OverviewFlags";
-
-    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
-
-    // We do not need any synchronization for this variable as its only written on UI thread.
-    public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
-            new MainThreadInitializedObject<>(OverviewInteractionState::new);
-
-    private static final int MSG_SET_PROXY = 200;
-    private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-
-    private final Context mContext;
-    private final Handler mUiHandler;
-    private final Handler mBgHandler;
-
-    // These are updated on the background thread
-    private ISystemUiProxy mISystemUiProxy;
-    private float mBackButtonAlpha = 1;
-
-    private int mSystemUiStateFlags;
-
-    private OverviewInteractionState(Context context) {
-        mContext = context;
-
-        // Data posted to the uihandler will be sent to the bghandler. Data is sent to uihandler
-        // because of its high send frequency and data may be very different than the previous value
-        // For example, send back alpha on uihandler to avoid flickering when setting its visibility
-        mUiHandler = new Handler(this::handleUiMessage);
-        mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
-
-        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
-                .addModeChangeListener(this::onNavigationModeChanged));
-    }
-
-    public float getBackButtonAlpha() {
-        return mBackButtonAlpha;
-    }
-
-    public void setBackButtonAlpha(float alpha, boolean animate) {
-        if (!modeSupportsGestures()) {
-            alpha = 1;
-        }
-        mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
-        mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_ALPHA, animate ? 1 : 0, 0, alpha)
-                .sendToTarget();
-    }
-
-    public void setSystemUiProxy(ISystemUiProxy proxy) {
-        mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
-    }
-
-    public void setSystemUiStateFlags(int stateFlags) {
-        mSystemUiStateFlags = stateFlags;
-    }
-
-    public int getSystemUiStateFlags() {
-        return mSystemUiStateFlags;
-    }
-
-    private boolean handleUiMessage(Message msg) {
-        if (msg.what == MSG_SET_BACK_BUTTON_ALPHA) {
-            mBackButtonAlpha = (float) msg.obj;
-        }
-        mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2, msg.obj).sendToTarget();
-        return true;
-    }
-
-    private boolean handleBgMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_SET_PROXY:
-                mISystemUiProxy = (ISystemUiProxy) msg.obj;
-                break;
-            case MSG_SET_BACK_BUTTON_ALPHA:
-                applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
-                return true;
-        }
-        return true;
-    }
-
-    @WorkerThread
-    private void applyBackButtonAlpha(float alpha, boolean animate) {
-        if (mISystemUiProxy == null) {
-            return;
-        }
-        try {
-            mISystemUiProxy.setBackButtonAlpha(alpha, animate);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Unable to update overview back button alpha", e);
-        }
-    }
-
-    private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
-        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-    }
-
-    private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
-        if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean(
-                HAS_ENABLED_QUICKSTEP_ONCE, true)) {
-            Utilities.getPrefs(mContext).edit()
-                .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
-                .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
-                .apply();
-        }
-    }
-
-    private boolean modeSupportsGestures() {
-        return SysUINavigationMode.getMode(mContext).hasGestures;
-    }
-}
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/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
new file mode 100644
index 0000000..2918879
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -0,0 +1,140 @@
+/*
+ * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.Set;
+
+/**
+ * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which
+ * delegates callbacks to multiple listeners on the main thread
+ */
+public class RecentsAnimationCallbacks implements
+        com.android.systemui.shared.system.RecentsAnimationListener {
+
+    private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
+    private final boolean mShouldMinimizeSplitScreen;
+
+    // TODO(141886704): Remove these references when they are no longer needed
+    private RecentsAnimationController mController;
+
+    private boolean mCancelled;
+
+    public RecentsAnimationCallbacks(boolean shouldMinimizeSplitScreen) {
+        mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+    }
+
+    @UiThread
+    public void addListener(RecentsAnimationListener listener) {
+        Preconditions.assertUIThread();
+        mListeners.add(listener);
+    }
+
+    @UiThread
+    public void removeListener(RecentsAnimationListener listener) {
+        Preconditions.assertUIThread();
+        mListeners.remove(listener);
+    }
+
+    public void notifyAnimationCanceled() {
+        mCancelled = true;
+        onAnimationCanceled(null);
+    }
+
+    // Called only in Q platform
+    @BinderThread
+    @Deprecated
+    public final void onAnimationStart(RecentsAnimationControllerCompat controller,
+            RemoteAnimationTargetCompat[] appTargets, Rect homeContentInsets,
+            Rect minimizedHomeBounds) {
+        onAnimationStart(controller, appTargets, new RemoteAnimationTargetCompat[0],
+                homeContentInsets, minimizedHomeBounds);
+    }
+
+    // Called only in R+ platform
+    @BinderThread
+    public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets,
+            Rect homeContentInsets, Rect minimizedHomeBounds) {
+        RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
+                wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+        mController = new RecentsAnimationController(animationController,
+                mShouldMinimizeSplitScreen, this::onAnimationFinished);
+
+        if (mCancelled) {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
+                    mController::finishAnimationToApp);
+        } else {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                for (RecentsAnimationListener listener : getListeners()) {
+                    listener.onRecentsAnimationStart(mController, targets);
+                }
+            });
+        }
+    }
+
+    @BinderThread
+    @Override
+    public final void onAnimationCanceled(ThumbnailData thumbnailData) {
+        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            for (RecentsAnimationListener listener : getListeners()) {
+                listener.onRecentsAnimationCanceled(thumbnailData);
+            }
+        });
+    }
+
+    private final void onAnimationFinished(RecentsAnimationController controller) {
+        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            for (RecentsAnimationListener listener : getListeners()) {
+                listener.onRecentsAnimationFinished(controller);
+            }
+        });
+    }
+
+    private RecentsAnimationListener[] getListeners() {
+        return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
+    }
+
+    /**
+     * Listener for the recents animation callbacks.
+     */
+    public interface RecentsAnimationListener {
+        default void onRecentsAnimationStart(RecentsAnimationController controller,
+                RecentsAnimationTargets targetSet) {}
+
+        /**
+         * Callback from the system when the recents animation is canceled. {@param thumbnailData}
+         * is passed back for rendering screenshot to replace live tile.
+         */
+        default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
+
+        default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
new file mode 100644
index 0000000..d938dc5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Wrapper around RecentsAnimationController to help with some synchronization
+ */
+public class RecentsAnimationController {
+
+    private static final String TAG = "RecentsAnimationController";
+
+    private final RecentsAnimationControllerCompat mController;
+    private final Consumer<RecentsAnimationController> mOnFinishedListener;
+    private final boolean mShouldMinimizeSplitScreen;
+
+    private boolean mWindowThresholdCrossed = false;
+
+    private InputConsumerController mInputConsumerController;
+    private Supplier<InputConsumer> mInputProxySupplier;
+    private InputConsumer mInputConsumer;
+    private boolean mTouchInProgress;
+    private boolean mFinishPending;
+
+    public RecentsAnimationController(RecentsAnimationControllerCompat controller,
+            boolean shouldMinimizeSplitScreen,
+            Consumer<RecentsAnimationController> onFinishedListener) {
+        mController = controller;
+        mOnFinishedListener = onFinishedListener;
+        mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+
+        setWindowThresholdCrossed(mWindowThresholdCrossed);
+    }
+
+    /**
+     * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
+     * currently being animated.
+     */
+    public ThumbnailData screenshotTask(int taskId) {
+        return mController != null ? mController.screenshotTask(taskId) : null;
+    }
+
+    /**
+     * Indicates that the gesture has crossed the window boundary threshold and system UI can be
+     * update the represent the window behind
+     */
+    public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
+        if (mWindowThresholdCrossed != windowThresholdCrossed) {
+            mWindowThresholdCrossed = windowThresholdCrossed;
+            UI_HELPER_EXECUTOR.execute(() -> {
+                mController.setAnimationTargetsBehindSystemBars(!windowThresholdCrossed);
+                if (mShouldMinimizeSplitScreen && windowThresholdCrossed) {
+                    // NOTE: As a workaround for conflicting animations (Launcher animating the task
+                    // leash, and SystemUI resizing the docked stack, which resizes the task), we
+                    // currently only set the minimized mode, and not the inverse.
+                    // TODO: Synchronize the minimize animation with the launcher animation
+                    mController.setSplitScreenMinimized(windowThresholdCrossed);
+                }
+            });
+        }
+    }
+
+    /**
+     * Notifies the controller that we want to defer cancel until the next app transition starts.
+     * If {@param screenshot} is set, then we will receive a screenshot on the next
+     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
+     * {@link #cleanupScreenshot()} when that screenshot is no longer used.
+     */
+    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+        mController.setDeferCancelUntilNextTransition(defer, screenshot);
+    }
+
+    /**
+     * Cleans up the screenshot previously returned from
+     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
+     */
+    public void cleanupScreenshot() {
+        UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
+    }
+
+    @UiThread
+    public void finishAnimationToHome() {
+        finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
+    @UiThread
+    public void finishAnimationToApp() {
+        finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
+    /** See {@link #finish(boolean, Runnable, boolean)} */
+    @UiThread
+    public void finish(boolean toRecents, Runnable onFinishComplete) {
+        finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
+    }
+
+    /**
+     * @param onFinishComplete A callback that runs on the main thread after the animation
+     *                         controller has finished on the background thread.
+     * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
+     *                          activity. If userLeaveHint is true, the activity will enter into
+     *                          picture-in-picture mode upon being paused.
+     */
+    @UiThread
+    public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
+        Preconditions.assertUIThread();
+        if (!toRecents) {
+            finishAndClear(false, onFinishComplete, sendUserLeaveHint);
+        } else {
+            if (mTouchInProgress) {
+                mFinishPending = true;
+                // Execute the callback
+                if (onFinishComplete != null) {
+                    onFinishComplete.run();
+                }
+            } else {
+                finishAndClear(true, onFinishComplete, sendUserLeaveHint);
+            }
+        }
+    }
+
+    private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+            boolean sendUserLeaveHint) {
+        disableInputProxy();
+        finishController(toRecents, onFinishComplete, sendUserLeaveHint);
+    }
+
+    @UiThread
+    public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
+        mOnFinishedListener.accept(this);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            mController.setInputConsumerEnabled(false);
+            mController.finish(toRecents, sendUserLeaveHint);
+            if (callback != null) {
+                MAIN_EXECUTOR.execute(callback);
+            }
+        });
+    }
+
+    /**
+     * Enables the input consumer to start intercepting touches in the app window.
+     */
+    public void enableInputConsumer() {
+        UI_HELPER_EXECUTOR.submit(() -> {
+            mController.hideCurrentInputMethod();
+            mController.setInputConsumerEnabled(true);
+        });
+    }
+
+    public void enableInputProxy(InputConsumerController inputConsumerController,
+            Supplier<InputConsumer> inputProxySupplier) {
+        mInputProxySupplier = inputProxySupplier;
+        mInputConsumerController = inputConsumerController;
+        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();
+        }
+        if (mInputConsumerController != null) {
+            mInputConsumerController.setInputListener(null);
+        }
+    }
+
+    private boolean onInputConsumerEvent(InputEvent ev) {
+        if (ev instanceof MotionEvent) {
+            onInputConsumerMotionEvent((MotionEvent) ev);
+        } else if (ev instanceof KeyEvent) {
+            if (mInputConsumer == null) {
+                mInputConsumer = mInputProxySupplier.get();
+            }
+            mInputConsumer.onKeyEvent((KeyEvent) ev);
+            return true;
+        }
+        return false;
+    }
+
+    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) {
+                mInputConsumer = mInputProxySupplier.get();
+            }
+        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+            // Finish any pending actions
+            mTouchInProgress = false;
+            if (mFinishPending) {
+                mFinishPending = false;
+                finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+            }
+        }
+        if (mInputConsumer != null) {
+            mInputConsumer.onMotionEvent(ev);
+        }
+
+        return true;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
new file mode 100644
index 0000000..9b094f6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -0,0 +1,426 @@
+/*
+ * 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 static android.content.Intent.ACTION_USER_UNLOCKED;
+import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
+import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Process;
+import android.text.TextUtils;
+import android.view.MotionEvent;
+import android.view.Surface;
+import androidx.annotation.BinderThread;
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Manages the state of the system during a swipe up gesture.
+ */
+public class RecentsAnimationDeviceState implements
+        SysUINavigationMode.NavigationModeChangeListener,
+        DefaultDisplay.DisplayInfoChangeListener {
+
+    private Context mContext;
+    private UserManagerCompat mUserManager;
+    private SysUINavigationMode mSysUiNavMode;
+    private DefaultDisplay mDefaultDisplay;
+    private int mDisplayId;
+
+    private @SystemUiStateFlags int mSystemUiStateFlags;
+    private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+
+    private final RectF mSwipeUpTouchRegion = new RectF();
+    private final Region mDeferredGestureRegion = new Region();
+    private final RectF mAssistantLeftRegion = new RectF();
+    private final RectF mAssistantRightRegion = new RectF();
+    private boolean mAssistantAvailable;
+    private float mAssistantVisibility;
+
+    private boolean mIsUserUnlocked;
+    private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
+    private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                mIsUserUnlocked = true;
+                notifyUserUnlocked();
+            }
+        }
+    };
+
+    private Region mExclusionRegion;
+    private SystemGestureExclusionListenerCompat mExclusionListener;
+
+    private ComponentName mGestureBlockedActivity;
+
+    public RecentsAnimationDeviceState(Context context) {
+        mContext = context;
+        mUserManager = UserManagerCompat.getInstance(context);
+        mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
+        mDisplayId = mDefaultDisplay.getInfo().id;
+
+        // Register for user unlocked if necessary
+        mIsUserUnlocked = mUserManager.isUserUnlocked(Process.myUserHandle());
+        if (!mIsUserUnlocked) {
+            mContext.registerReceiver(mUserUnlockedReceiver,
+                    new IntentFilter(ACTION_USER_UNLOCKED));
+        }
+
+        // Register for exclusion updates
+        mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
+            @Override
+            @BinderThread
+            public void onExclusionChanged(Region region) {
+                // Assignments are atomic, it should be safe on binder thread
+                mExclusionRegion = region;
+            }
+        };
+        onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+
+        // Add any blocked activities
+        String blockingActivity = context.getString(R.string.gesture_blocking_activity);
+        if (!TextUtils.isEmpty(blockingActivity)) {
+            mGestureBlockedActivity = ComponentName.unflattenFromString(blockingActivity);
+        }
+    }
+
+    /**
+     * Cleans up all the registered listeners and receivers.
+     */
+    public void destroy() {
+        Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
+        mSysUiNavMode.removeModeChangeListener(this);
+        mDefaultDisplay.removeChangeListener(this);
+        mExclusionListener.unregister();
+    }
+
+    @Override
+    public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        mDefaultDisplay.removeChangeListener(this);
+        if (newMode.hasGestures) {
+            mDefaultDisplay.addChangeListener(this);
+        }
+
+        if (mMode == NO_BUTTON) {
+            mExclusionListener.register();
+        } else {
+            mExclusionListener.unregister();
+        }
+        mMode = newMode;
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+        if (info.id != getDisplayId()) {
+            return;
+        }
+
+        updateGestureTouchRegions();
+    }
+
+    /**
+     * @return the display id for the display that Launcher is running on.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
+     * will be called back immediately.
+     */
+    public void runOnUserUnlocked(Runnable action) {
+        if (mIsUserUnlocked) {
+            action.run();
+        } else {
+            mUserUnlockedActions.add(action);
+        }
+    }
+
+    /**
+     * @return whether the user is unlocked.
+     */
+    public boolean isUserUnlocked() {
+        return mIsUserUnlocked;
+    }
+
+    private void notifyUserUnlocked() {
+        for (Runnable action : mUserUnlockedActions) {
+            action.run();
+        }
+        mUserUnlockedActions.clear();
+        Utilities.unregisterReceiverSafely(mContext, mUserUnlockedReceiver);
+    }
+
+    /**
+     * @return whether the given running task info matches the gesture-blocked activity.
+     */
+    public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
+        return runningTaskInfo != null
+                && mGestureBlockedActivity.equals(runningTaskInfo.topActivity);
+    }
+
+    /**
+     * @return the package of the gesture-blocked activity or {@code null} if there is none.
+     */
+    public String getGestureBlockedActivityPackage() {
+        return (mGestureBlockedActivity != null)
+                ? mGestureBlockedActivity.getPackageName()
+                : null;
+    }
+
+    /**
+     * Updates the system ui state flags from SystemUI.
+     */
+    public void setSystemUiFlags(int stateFlags) {
+        mSystemUiStateFlags = stateFlags;
+    }
+
+    /**
+     * @return the system ui state flags.
+     */
+    // TODO(141886704): See if we can remove this
+    public @SystemUiStateFlags int getSystemUiStateFlags() {
+        return mSystemUiStateFlags;
+    }
+
+    /**
+     * @return whether SystemUI is in a state where we can start a system gesture.
+     */
+    public boolean canStartSystemGesture() {
+        return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
+                && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                        || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0);
+    }
+
+    /**
+     * @return whether the keyguard is showing and is occluded by an app showing above the keyguard
+     *         (like camera or maps)
+     */
+    public boolean isKeyguardShowingOccluded() {
+        return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
+    }
+
+    /**
+     * @return whether screen pinning is enabled and active
+     */
+    public boolean isScreenPinningActive() {
+        return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+    }
+
+    /**
+     * @return whether lock-task mode is active
+     */
+    public boolean isLockToAppActive() {
+        return ActivityManagerWrapper.getInstance().isLockToAppActive();
+    }
+
+    /**
+     * @return whether the accessibility menu is available.
+     */
+    public boolean isAccessibilityMenuAvailable() {
+        return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+    }
+
+    /**
+     * @return whether the accessibility menu shortcut is available.
+     */
+    public boolean isAccessibilityMenuShortcutAvailable() {
+        return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+    }
+
+    /**
+     * @return whether home is disabled (either by SUW/SysUI/device policy)
+     */
+    public boolean isHomeDisabled() {
+        return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+    }
+
+    /**
+     * @return whether overview is disabled (either by SUW/SysUI/device policy)
+     */
+    public boolean isOverviewDisabled() {
+        return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+    }
+
+    /**
+     * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+     */
+    public void updateGestureTouchRegions() {
+        if (!mMode.hasGestures) {
+            return;
+        }
+
+        Resources res = mContext.getResources();
+        DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+        Point realSize = new Point(displayInfo.realSize);
+        mSwipeUpTouchRegion.set(0, 0, realSize.x, realSize.y);
+        if (mMode == NO_BUTTON) {
+            int touchHeight = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
+            mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom - touchHeight;
+
+            final int assistantWidth = res.getDimensionPixelSize(R.dimen.gestures_assistant_width);
+            final float assistantHeight = Math.max(touchHeight,
+                    QuickStepContract.getWindowCornerRadius(res));
+            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeUpTouchRegion.bottom;
+            mAssistantLeftRegion.top = mAssistantRightRegion.top =
+                    mSwipeUpTouchRegion.bottom - assistantHeight;
+
+            mAssistantLeftRegion.left = 0;
+            mAssistantLeftRegion.right = assistantWidth;
+
+            mAssistantRightRegion.right = mSwipeUpTouchRegion.right;
+            mAssistantRightRegion.left = mSwipeUpTouchRegion.right - assistantWidth;
+        } else {
+            mAssistantLeftRegion.setEmpty();
+            mAssistantRightRegion.setEmpty();
+            switch (displayInfo.rotation) {
+                case Surface.ROTATION_90:
+                    mSwipeUpTouchRegion.left = mSwipeUpTouchRegion.right
+                            - ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
+                    break;
+                case Surface.ROTATION_270:
+                    mSwipeUpTouchRegion.right = mSwipeUpTouchRegion.left
+                            + ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
+                    break;
+                default:
+                    mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom
+                            - ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
+            }
+        }
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+        return mSwipeUpTouchRegion.contains(event.getX(), event.getY());
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
+     *         is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+        return mSwipeUpTouchRegion.contains(event.getX(pointerIndex), event.getY(pointerIndex));
+    }
+
+    /**
+     * Sets the region in screen space where the gestures should be deferred (ie. due to specific
+     * nav bar ui).
+     */
+    public void setDeferredGestureRegion(Region deferredGestureRegion) {
+        mDeferredGestureRegion.set(deferredGestureRegion);
+    }
+
+    /**
+     * @return whether the given {@param event} is in the deferred gesture region indicating that
+     *         the Launcher should not immediately start the recents animation until the gesture
+     *         passes a certain threshold.
+     */
+    public boolean isInDeferredGestureRegion(MotionEvent event) {
+        return mDeferredGestureRegion.contains((int) event.getX(), (int) event.getY());
+    }
+
+    /**
+     * @return whether the given {@param event} is in the app-requested gesture-exclusion region.
+     *         This is only used for quickswitch, and not swipe up.
+     */
+    public boolean isInExclusionRegion(MotionEvent event) {
+        // mExclusionRegion can change on binder thread, use a local instance here.
+        Region exclusionRegion = mExclusionRegion;
+        return mMode == NO_BUTTON && exclusionRegion != null
+                && exclusionRegion.contains((int) event.getX(), (int) event.getY());
+    }
+
+    /**
+     * Sets whether the assistant is available.
+     */
+    public void setAssistantAvailable(boolean assistantAvailable) {
+        mAssistantAvailable = assistantAvailable;
+    }
+
+    /**
+     * Sets the visibility fraction of the assistant.
+     */
+    public void setAssistantVisibility(float visibility) {
+        mAssistantVisibility = visibility;
+    }
+
+    /**
+     * @return the visibility fraction of the assistant.
+     */
+    public float getAssistantVisibility() {
+        return mAssistantVisibility;
+    }
+
+    /**
+     * @param ev An ACTION_DOWN motion event
+     * @return whether the given motion event can trigger the assistant.
+     */
+    public boolean canTriggerAssistantAction(MotionEvent ev) {
+        return mAssistantAvailable
+                && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+                && (mAssistantLeftRegion.contains(ev.getX(), ev.getY())
+                        || mAssistantRightRegion.contains(ev.getX(), ev.getY()))
+                && !isLockToAppActive();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("DeviceState:");
+        pw.println("  canStartSystemGesture=" + canStartSystemGesture());
+        pw.println("  systemUiFlags=" + mSystemUiStateFlags);
+        pw.println("  systemUiFlagsDesc="
+                + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
+        pw.println("  assistantAvailable=" + mAssistantAvailable);
+        pw.println("  assistantDisabled="
+                + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
new file mode 100644
index 0000000..9353759
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -0,0 +1,53 @@
+/*
+ * 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 static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Rect;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Extension of {@link RemoteAnimationTargets} with additional information about swipe
+ * up animation
+ */
+public class RecentsAnimationTargets extends RemoteAnimationTargets {
+
+    public final Rect homeContentInsets;
+    public final Rect minimizedHomeBounds;
+
+    public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
+            Rect minimizedHomeBounds) {
+        super(apps, wallpapers, MODE_CLOSING);
+        this.homeContentInsets = homeContentInsets;
+        this.minimizedHomeBounds = minimizedHomeBounds;
+    }
+
+    public boolean hasTargets() {
+        return unfilteredApps.length != 0;
+    }
+
+    /**
+     * Clones the target set without any actual targets. Used only when continuing a gesture after
+     * the actual recents animation has finished.
+     */
+    public RecentsAnimationTargets cloneWithoutTargets() {
+        return new RecentsAnimationTargets(new RemoteAnimationTargetCompat[0],
+                new RemoteAnimationTargetCompat[0], homeContentInsets, minimizedHomeBounds);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 2e59ed5..465d464 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -25,17 +25,13 @@
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.os.Build;
 import android.os.Looper;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -61,8 +57,6 @@
     private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
     private final Context mContext;
 
-    private ISystemUiProxy mSystemUiProxy;
-
     private final RecentTasksList mTaskList;
     private final TaskIconCache mIconCache;
     private final TaskThumbnailCache mThumbnailCache;
@@ -178,14 +172,6 @@
         mIconCache.onTaskRemoved(dummyKey);
     }
 
-    public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
-        mSystemUiProxy = systemUiProxy;
-    }
-
-    public ISystemUiProxy getSystemUiProxy() {
-        return mSystemUiProxy;
-    }
-
     public void onTrimMemory(int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
             mThumbnailCache.getHighResLoadingState().setVisible(false);
@@ -198,31 +184,32 @@
     }
 
     public void onOverviewShown(boolean fromHome, String tag) {
-        if (mSystemUiProxy == null) {
-            return;
-        }
-        try {
-            mSystemUiProxy.onOverviewShown(fromHome);
-        } catch (RemoteException e) {
-            Log.w(tag,
-                    "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
-                            + ": ", e);
-        }
+        SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(fromHome, tag);
     }
 
     private void setupPackageListener() {
-        LauncherAppsCompat.getInstance(mContext)
-                .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() {
-                    @Override
-                    public void onPackageRemoved(String packageName, UserHandle user) {
-                        mIconCache.invalidatePackage(packageName);
-                    }
+        mContext.getSystemService(LauncherApps.class).registerCallback(new LauncherApps.Callback() {
+            @Override
+            public void onPackageRemoved(String packageName, UserHandle user) {
+                mIconCache.invalidatePackage(packageName);
+            }
 
-                    @Override
-                    public void onPackageChanged(String packageName, UserHandle user) {
-                        mIconCache.invalidatePackage(packageName);
-                    }
-                });
+            @Override
+            public void onPackageChanged(String packageName, UserHandle user) {
+                mIconCache.invalidatePackage(packageName);
+            }
+
+            @Override
+            public void onPackageAdded(String packageName, UserHandle user) { }
+
+            @Override
+            public void onPackagesAvailable(
+                    String[] packageNames, UserHandle user, boolean replacing) { }
+
+            @Override
+            public void onPackagesUnavailable(
+                    String[] packageNames, UserHandle user, boolean replacing) { }
+        });
     }
 
     public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
similarity index 86%
rename from quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
rename to quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index 1229293..5fa6bc7 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.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.quickstep;
 
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -25,17 +25,19 @@
 /**
  * Holds a collection of RemoteAnimationTargets, filtered by different properties.
  */
-public class RemoteAnimationTargetSet {
+public class RemoteAnimationTargets {
 
     private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
             new ArrayDeque<>(1);
 
     public final RemoteAnimationTargetCompat[] unfilteredApps;
     public final RemoteAnimationTargetCompat[] apps;
+    public final RemoteAnimationTargetCompat[] wallpapers;
     public final int targetMode;
     public final boolean hasRecents;
 
-    public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
+    public RemoteAnimationTargets(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, int targetMode) {
         ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
         boolean hasRecents = false;
         if (apps != null) {
@@ -51,6 +53,7 @@
 
         this.unfilteredApps = apps;
         this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
+        this.wallpapers = wallpapers;
         this.targetMode = targetMode;
         this.hasRecents = hasRecents;
     }
@@ -83,6 +86,9 @@
             for (RemoteAnimationTargetCompat target : unfilteredApps) {
                 target.release();
             }
+            for (RemoteAnimationTargetCompat target : wallpapers) {
+                target.release();
+            }
         } else {
             applier.addAfterApplyCallback(this::release);
         }
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index b67c6f8..5902672 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -57,7 +57,7 @@
 
     private static final String TAG = "SysUINavigationMode";
 
-    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+    private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
             "config_navBarInteractionMode";
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
new file mode 100644
index 0000000..5539b3e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -0,0 +1,285 @@
+/*
+ * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MotionEvent;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.quickstep.util.SharedApiCompat;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+
+/**
+ * Holds the reference to SystemUI.
+ */
+public class SystemUiProxy implements ISystemUiProxy {
+    private static final String TAG = SystemUiProxy.class.getSimpleName();
+
+    public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
+            new MainThreadInitializedObject<>(SystemUiProxy::new);
+
+    private ISystemUiProxy mSystemUiProxy;
+    private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
+        MAIN_EXECUTOR.execute(() -> setProxy(null));
+    };
+
+    // Used to dedupe calls to SystemUI
+    private int mLastShelfHeight;
+    private boolean mLastShelfVisible;
+    private float mLastBackButtonAlpha;
+    private boolean mLastBackButtonAnimate;
+
+    // TODO(141886704): Find a way to remove this
+    private int mLastSystemUiStateFlags;
+
+    public SystemUiProxy(Context context) {
+        // Do nothing
+    }
+
+    @Override
+    public IBinder asBinder() {
+        // Do nothing
+        return null;
+    }
+
+    public void setProxy(ISystemUiProxy proxy) {
+        unlinkToDeath();
+        mSystemUiProxy = proxy;
+        linkToDeath();
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public void setLastSystemUiStateFlags(int stateFlags) {
+        mLastSystemUiStateFlags = stateFlags;
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public int getLastSystemUiStateFlags() {
+        return mLastSystemUiStateFlags;
+    }
+
+    public boolean isActive() {
+        return mSystemUiProxy != null;
+    }
+
+    private void linkToDeath() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to link sysui proxy death recipient");
+            }
+        }
+    }
+
+    private void unlinkToDeath() {
+        if (mSystemUiProxy != null) {
+            mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+        }
+    }
+
+    @Override
+    public void startScreenPinning(int taskId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startScreenPinning(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startScreenPinning", e);
+            }
+        }
+    }
+
+    @Override
+    public void onSplitScreenInvoked() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onSplitScreenInvoked();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onSplitScreenInvoked", e);
+            }
+        }
+    }
+
+    @Override
+    public void onOverviewShown(boolean fromHome) {
+        onOverviewShown(fromHome, TAG);
+    }
+
+    public void onOverviewShown(boolean fromHome, String tag) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onOverviewShown(fromHome);
+            } catch (RemoteException e) {
+                Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e);
+            }
+        }
+    }
+
+    @Override
+    public Rect getNonMinimizedSplitScreenSecondaryBounds() {
+        if (mSystemUiProxy != null) {
+            try {
+                return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setBackButtonAlpha(float alpha, boolean animate) {
+        boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
+                || animate != mLastBackButtonAnimate;
+        if (mSystemUiProxy != null && changed) {
+            mLastBackButtonAlpha = alpha;
+            mLastBackButtonAnimate = animate;
+            try {
+                mSystemUiProxy.setBackButtonAlpha(alpha, animate);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setBackButtonAlpha", e);
+            }
+        }
+    }
+
+    public float getLastBackButtonAlpha() {
+        return mLastBackButtonAlpha;
+    }
+
+    @Override
+    public void setNavBarButtonAlpha(float alpha, boolean animate) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+            }
+        }
+    }
+
+    @Override
+    public void onStatusBarMotionEvent(MotionEvent event) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onStatusBarMotionEvent(event);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistantProgress(float progress) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onAssistantProgress(progress);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistantGestureCompletion(float velocity) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onAssistantGestureCompletion(velocity);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onAssistantGestureCompletion", e);
+            }
+        }
+    }
+
+    @Override
+    public void startAssistant(Bundle args) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startAssistant(args);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startAssistant", e);
+            }
+        }
+    }
+
+    @Override
+    public Bundle monitorGestureInput(String name, int displayId) {
+        if (mSystemUiProxy != null) {
+            try {
+                return mSystemUiProxy.monitorGestureInput(name, displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call monitorGestureInput: " + name, e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void notifyAccessibilityButtonClicked(int displayId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.notifyAccessibilityButtonClicked(displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e);
+            }
+        }
+    }
+
+    @Override
+    public void notifyAccessibilityButtonLongClicked() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.notifyAccessibilityButtonLongClicked();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e);
+            }
+        }
+    }
+
+    @Override
+    public void stopScreenPinning() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.stopScreenPinning();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopScreenPinning", e);
+            }
+        }
+    }
+
+    /**
+     * See SharedApiCompat#setShelfHeight()
+     */
+    public void setShelfHeight(boolean visible, int shelfHeight) {
+        boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
+        if (mSystemUiProxy != null && changed) {
+            mLastShelfVisible = visible;
+            mLastShelfHeight = shelfHeight;
+            try {
+                SharedApiCompat.setShelfHeight(mSystemUiProxy, visible, shelfHeight);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setShelfHeight visible: " + visible
+                        + " height: " + shelfHeight, e);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 5f76ca7..230a22a 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -23,9 +23,9 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -44,15 +44,14 @@
      * TODO: remove this once we switch to getting the icon and label from IconCache.
      */
     public static CharSequence getTitle(Context context, Task task) {
-        LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
-        PackageManager packageManager = context.getPackageManager();
         UserHandle user = UserHandle.of(task.key.userId);
-        ApplicationInfo applicationInfo = launcherAppsCompat.getApplicationInfo(
-            task.getTopComponent().getPackageName(), 0, user);
+        ApplicationInfo applicationInfo = new PackageManagerHelper(context)
+                .getApplicationInfo(task.getTopComponent().getPackageName(), user, 0);
         if (applicationInfo == null) {
             Log.e(TAG, "Failed to get title for task " + task);
             return "";
         }
+        PackageManager packageManager = context.getPackageManager();
         return packageManager.getUserBadgedLabel(
             applicationInfo.loadLabel(packageManager), user);
     }
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index bf3cd8a..8e5ed1a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -29,12 +29,16 @@
 import android.stats.launcher.nano.Launcher;
 import android.stats.launcher.nano.LauncherExtension;
 import android.stats.launcher.nano.LauncherTarget;
+import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.ComponentKey;
 import com.android.systemui.shared.system.StatsLogCompat;
 import com.google.protobuf.nano.MessageNano;
@@ -50,6 +54,8 @@
 public class StatsLogCompatManager extends StatsLogManager {
 
     private static final int SUPPORTED_TARGET_DEPTH = 2;
+    private static final String TAG = "StatsLogCompatManager";
+    private static final boolean DEBUG = false;
 
     public StatsLogCompatManager(Context context) { }
 
@@ -59,6 +65,9 @@
         ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
         int srcState = mStateProvider.getCurrentState();
         fillInLauncherExtension(v, ext);
+        if (ext.srcTarget[0] != null) {
+            ext.srcTarget[0].item = LauncherTarget.APP_ICON;
+        }
         StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
                 MessageNano.toByteArray(ext), true);
     }
@@ -95,28 +104,132 @@
     }
 
     public static boolean fillInLauncherExtension(View v, LauncherExtension extension) {
+        if (DEBUG) {
+            Log.d(TAG, "fillInLauncherExtension");
+        }
+
         StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
+            if (DEBUG) {
+                Log.d(TAG, "View or provider is null, or view doesn't have an ItemInfo tag.");
+            }
+
             return false;
         }
         ItemInfo itemInfo = (ItemInfo) v.getTag();
         Target child = new Target();
         Target parent = new Target();
         provider.fillInLogContainerData(v, itemInfo, child, parent);
+        extension.srcTarget[0] = new LauncherTarget();
+        extension.srcTarget[1] = new LauncherTarget();
         copy(child, extension.srcTarget[0]);
         copy(parent, extension.srcTarget[1]);
         return true;
     }
 
     public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) {
+        if (DEBUG) {
+            Log.d(TAG, "fillInLauncherExtensionWithPageId, pageId = " + pageId);
+        }
+
         Target target = new Target();
         target.pageIndex = pageId;
+        ext.srcTarget[0] = new LauncherTarget();
         copy(target, ext.srcTarget[0]);
         return true;
     }
 
     private static void copy(Target src, LauncherTarget dst) {
-        // fill in
+        if (DEBUG) {
+            Log.d(TAG, "copy target information from clearcut Target to LauncherTarget.");
+        }
+
+        // Fill in type
+        switch (src.type) {
+            case Target.Type.ITEM:
+                dst.type = LauncherTarget.ITEM_TYPE;
+                break;
+            case Target.Type.CONTROL:
+                dst.type = LauncherTarget.CONTROL_TYPE;
+                break;
+            case Target.Type.CONTAINER:
+                dst.type = LauncherTarget.CONTAINER_TYPE;
+                break;
+            default:
+                dst.type = LauncherTarget.NONE;
+                break;
+        }
+
+        // Fill in item
+        switch (src.itemType) {
+            case ItemType.APP_ICON:
+                dst.item = LauncherTarget.APP_ICON;
+                break;
+            case ItemType.SHORTCUT:
+                dst.item = LauncherTarget.SHORTCUT;
+                break;
+            case ItemType.WIDGET:
+                dst.item = LauncherTarget.WIDGET;
+                break;
+            case ItemType.FOLDER_ICON:
+                dst.item = LauncherTarget.FOLDER_ICON;
+                break;
+            case ItemType.DEEPSHORTCUT:
+                dst.item = LauncherTarget.DEEPSHORTCUT;
+                break;
+            case ItemType.SEARCHBOX:
+                dst.item = LauncherTarget.SEARCHBOX;
+                break;
+            case ItemType.EDITTEXT:
+                dst.item = LauncherTarget.EDITTEXT;
+                break;
+            case ItemType.NOTIFICATION:
+                dst.item = LauncherTarget.NOTIFICATION;
+                break;
+            case ItemType.TASK:
+                dst.item = LauncherTarget.TASK;
+                break;
+            default:
+                dst.item = LauncherTarget.DEFAULT_ITEM;
+                break;
+        }
+
+        // Fill in container
+        switch (src.containerType) {
+            case ContainerType.HOTSEAT:
+                dst.container = LauncherTarget.HOTSEAT;
+                break;
+            case ContainerType.FOLDER:
+                dst.container = LauncherTarget.FOLDER;
+                break;
+            case ContainerType.PREDICTION:
+                dst.container = LauncherTarget.PREDICTION;
+                break;
+            case ContainerType.SEARCHRESULT:
+                dst.container = LauncherTarget.SEARCHRESULT;
+                break;
+            default:
+                dst.container = LauncherTarget.DEFAULT_CONTAINER;
+                break;
+        }
+
+        // Fill in control
+        switch (src.controlType) {
+            case ControlType.UNINSTALL_TARGET:
+                dst.control = LauncherTarget.UNINSTALL;
+                break;
+            case ControlType.REMOVE_TARGET:
+                dst.control = LauncherTarget.REMOVE;
+                break;
+            default:
+                dst.control = LauncherTarget.DEFAULT_CONTROL;
+                break;
+        }
+
+        // Fill in other fields
+        dst.pageId = src.pageIndex;
+        dst.gridX = src.gridX;
+        dst.gridY = src.gridY;
     }
 
     @Override
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/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 4503a43..6210fc2 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -31,16 +31,17 @@
 
     static final int Z_BOOST_BASE = 800570000;
 
-    AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
+    AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets);
 
     default ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
         LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
                 false /* startAtFrontOfQueue */) {
 
             @Override
-            public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
-                    AnimationResult result) {
-                result.setAnimation(createWindowAnimation(targetCompats), context);
+            public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+                    RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+                result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
index 40dd74b..fa2d338 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 
@@ -29,11 +30,12 @@
  */
 public class RemoteFadeOutAnimationListener implements AnimatorUpdateListener {
 
-    private final RemoteAnimationTargetSet mTarget;
+    private final RemoteAnimationTargets mTarget;
     private boolean mFirstFrame = true;
 
-    public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] targets) {
-        mTarget = new RemoteAnimationTargetSet(targets, MODE_CLOSING);
+    public RemoteFadeOutAnimationListener(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets) {
+        mTarget = new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index dc6b56e..26e9eaf 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -161,7 +161,7 @@
                 mMidProgress =  OVERVIEW.getVerticalProgress(mLauncher);
                 Rect hotseatPadding = dp.getHotseatLayoutPadding();
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
-                        - hotseatPadding.bottom - hotseatPadding.top;
+                        + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
                         Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 7801775..8e4762d 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -24,6 +24,7 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.os.Process;
 import android.view.View;
 
@@ -35,7 +36,6 @@
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.model.AppLaunchTracker;
 
 import org.junit.After;
@@ -60,7 +60,7 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(mTargetContext)
+        List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
                 .getActivityList(null, Process.myUserHandle());
         mSampleApp1 = activities.get(0);
         mSampleApp2 = activities.get(1);
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index ec3d49a..a7c33a9 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -69,10 +69,9 @@
     private DigitalWellBeingToast getToast() {
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
         waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
-        waitForLauncherCondition("No latest task", launcher -> getLatestTask(launcher) != null);
+        final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
 
         return getFromLauncher(launcher -> {
-            final TaskView task = getLatestTask(launcher);
             assertTrue("Latest task is not Calculator",
                     CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName()));
             return task.getDigitalWellBeingToast();
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e5f949b..aa5fce1 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -22,14 +22,22 @@
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
 import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
 import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_ACTIVITY_TIMEOUT;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT;
 import static com.android.launcher3.ui.AbstractLauncherUiTest.resolveSystemApp;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.startAppFast;
+import static com.android.launcher3.ui.AbstractLauncherUiTest.startTestActivity;
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
 import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
-import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -41,10 +49,16 @@
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.Utilities;
+import com.android.launcher3.tapl.BaseOverview;
 import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.views.RecentsView;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -53,11 +67,11 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-/**
- * TODO: Fix fallback when quickstep is enabled
- */
 public class FallbackRecentsTest {
 
     private final UiDevice mDevice;
@@ -78,11 +92,14 @@
         Context context = instrumentation.getContext();
         mDevice = UiDevice.getInstance(instrumentation);
         mDevice.setOrientationNatural();
-        mLauncher = new LauncherInstrumentation(instrumentation);
+        mLauncher = new LauncherInstrumentation();
 
-        mOrderSensitiveRules = RuleChain.
-                outerRule(new NavigationModeSwitchRule(mLauncher)).
-                around(new FailureWatcher(mDevice));
+        if (TestHelpers.isInLauncherProcess()) {
+            Utilities.enableRunningInTestHarnessForTests();
+        }
+
+        mOrderSensitiveRules = RuleChain.outerRule(new NavigationModeSwitchRule(mLauncher))
+                        .around(new FailureWatcher(mDevice));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
@@ -103,9 +120,14 @@
                 }
             }
         };
+        if (TestHelpers.isInLauncherProcess()) {
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
+        }
     }
 
-    @NavigationModeSwitch(mode = THREE_BUTTON)
+    @NavigationModeSwitch
     @Test
     public void goToOverviewFromHome() {
         mDevice.pressHome();
@@ -115,23 +137,115 @@
         mLauncher.getBackground().switchToOverview();
     }
 
-    @NavigationModeSwitch(mode = THREE_BUTTON)
+    @NavigationModeSwitch
     @Test
     public void goToOverviewFromApp() {
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
         mLauncher.getBackground().switchToOverview();
     }
 
-    private void startAppFast(String packageName) {
-        final Instrumentation instrumentation = getInstrumentation();
-        final Intent intent = instrumentation.getContext().getPackageManager().
-                getLaunchIntentForPackage(packageName);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        instrumentation.getTargetContext().startActivity(intent);
-        assertTrue(packageName + " didn't start",
-                mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), WAIT_TIME_MS));
+    protected void executeOnRecents(Consumer<RecentsActivity> f) {
+        getFromRecents(r -> {
+            f.accept(r);
+            return true;
+        });
     }
 
+    protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
+        if (!TestHelpers.isInLauncherProcess()) return null;
+        Object[] result = new Object[1];
+        Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
+            RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity();
+            if (activity == null) {
+                return false;
+            }
+            result[0] = f.apply(activity);
+            return true;
+        }).get(), DEFAULT_UI_TIMEOUT);
+        return (T) result[0];
+    }
+
+    private BaseOverview pressHomeAndGoToOverview() {
+        mDevice.pressHome();
+        return mLauncher.getBackground().switchToOverview();
+    }
+
+    @NavigationModeSwitch
+    @Test
+    public void testOverview() {
+        startAppFastAndWaitForRecentTask(getAppPackageName());
+        startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startTestActivity(2);
+        Wait.atMost("Expected three apps in the task list",
+                () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT);
+
+        BaseOverview overview = mLauncher.getBackground().switchToOverview();
+        executeOnRecents(recents ->
+                assertTrue("Don't have at least 3 tasks", getTaskCount(recents) >= 3));
+
+        // Test flinging forward and backward.
+        overview.flingForward();
+        final Integer currentTaskAfterFlingForward = getFromRecents(this::getCurrentOverviewPage);
+        executeOnRecents(recents -> assertTrue("Current task in Overview is still 0",
+                currentTaskAfterFlingForward > 0));
+
+        overview.flingBackward();
+        executeOnRecents(recents -> assertTrue("Flinging back in Overview did nothing",
+                getCurrentOverviewPage(recents) < currentTaskAfterFlingForward));
+
+        // Test opening a task.
+        overview = pressHomeAndGoToOverview();
+
+        OverviewTask task = overview.getCurrentTask();
+        assertNotNull("overview.getCurrentTask() returned null (1)", task);
+        assertNotNull("OverviewTask.open returned null", task.open());
+        assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
+                By.pkg(getAppPackageName()).text("TestActivity2")),
+                DEFAULT_UI_TIMEOUT));
+
+
+        // Test dismissing a task.
+        overview = pressHomeAndGoToOverview();
+        final Integer numTasks = getFromRecents(this::getTaskCount);
+        task = overview.getCurrentTask();
+        assertNotNull("overview.getCurrentTask() returned null (2)", task);
+        task.dismiss();
+        executeOnRecents(
+                recents -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(recents)));
+
+        // Test dismissing all tasks.
+        pressHomeAndGoToOverview().dismissAllTasks();
+        assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
+                mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
+    }
+
+    private int getCurrentOverviewPage(RecentsActivity recents) {
+        return recents.<RecentsView>getOverviewPanel().getCurrentPage();
+    }
+
+    private int getTaskCount(RecentsActivity recents) {
+        return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
+    }
+
+    /**
+     * Workaround for b/141580748, there was an issue where the recent task is only updated when the
+     * activity starting the task is resumed.  In this case, we should wait until the task is in
+     * the recents task list before continuing.
+     */
+    private void startAppFastAndWaitForRecentTask(String packageName) {
+        startAppFast(packageName);
+        Wait.atMost("Expected app in task list",
+                () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT);
+    }
+
+    private boolean containsRecentTaskWithPackage(String packageName) {
+        for (ComponentName cn : mLauncher.getRecentTasks()) {
+            if (cn.getPackageName().equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index e295527..c2197ab 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -181,7 +181,7 @@
                                 SysUINavigationMode.INSTANCE.get(targetContext);
                         targetContext.getMainExecutor().execute(() ->
                                 sysUINavigationMode.addModeChangeListener(listener));
-                        latch.await(10, TimeUnit.SECONDS);
+                        latch.await(60, TimeUnit.SECONDS);
                         targetContext.getMainExecutor().execute(() ->
                                 sysUINavigationMode.removeModeChangeListener(listener));
                         assertTrue("Navigation mode didn't change to " + expectedMode,
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index c5b560c..2111e2c 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,7 +25,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -80,8 +79,6 @@
     @Test
     @NavigationModeSwitch
     public void testStressPressHome() {
-        if (LauncherInstrumentation.isAvd()) return; // b/136278866
-
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
@@ -94,8 +91,6 @@
     @Test
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
-        if (LauncherInstrumentation.isAvd()) return; // b/136278866
-
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
new file mode 100644
index 0000000..8b7c7b7
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -0,0 +1,276 @@
+/*
+ * 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 static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
+import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo;
+import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.tapl.Background;
+import com.android.launcher3.testcomponent.ListViewService;
+import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
+import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.ui.TestViewHelpers;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Field;
+import java.util.function.IntConsumer;
+
+/**
+ * Test to verify view inflation does not happen during swipe up.
+ * To verify view inflation, we setup a dummy ViewConfiguration and check if any call to that class
+ * does from a View.init method or not.
+ *
+ * Alternative approaches considered:
+ *    Overriding LayoutInflater: This does not cover views initialized
+ *        directly (ex: new LinearLayout)
+ *    Using ExtendedMockito: Mocking static methods from platform classes (loaded in zygote) makes
+ *        the main thread extremely slow and untestable
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
+
+    private ContentResolver mResolver;
+    private SparseArray<ViewConfiguration> mConfigMap;
+    private InitTracker mInitTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // Workaround for b/142351228, when there are no activities, the system may not destroy the
+        // activity correctly for activities under instrumentation, which can leave two concurrent
+        // activities, which changes the order in which the activities are cleaned up (overlapping
+        // stop and start) leading to all sort of issues. To workaround this, ensure that the test
+        // is started only after starting another app.
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+
+        TaplTestsLauncher3.initialize(this);
+
+        mResolver = mTargetContext.getContentResolver();
+        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+        // Get static configuration map
+        Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
+        field.setAccessible(true);
+        mConfigMap = (SparseArray<ViewConfiguration>) field.get(null);
+
+        mInitTracker = new InitTracker();
+    }
+
+    @Test
+    @NavigationModeSwitch(mode = ZERO_BUTTON)
+    public void testSwipeUpFromApp() throws Exception {
+        try {
+            // Go to overview once so that all views are initialized and cached
+            startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+            mLauncher.getBackground().switchToOverview().dismissAllTasks();
+
+            // Track view creations
+            mInitTracker.startTracking();
+
+            startTestActivity(2);
+            mLauncher.getBackground().switchToOverview();
+
+            assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
+        } finally {
+            mConfigMap.clear();
+        }
+    }
+
+    @Test
+    @NavigationModeSwitch(mode = ZERO_BUTTON)
+    public void testSwipeUpFromApp_widget_update() {
+        String dummyText = "Some random dummy text";
+
+        executeSwipeUpTestWithWidget(
+                widgetId -> { },
+                widgetId -> AppWidgetManager.getInstance(getContext())
+                        .updateAppWidget(widgetId, createMainWidgetViews(dummyText)),
+                dummyText);
+    }
+
+    @Test
+    @NavigationModeSwitch(mode = ZERO_BUTTON)
+    public void testSwipeUp_with_list_widgets() {
+        SimpleViewsFactory viewFactory = new SimpleViewsFactory();
+        viewFactory.viewCount = 1;
+        Bundle args = new Bundle();
+        args.putBinder(EXTRA_VALUE, viewFactory.toBinder());
+        TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, args);
+
+        try {
+            executeSwipeUpTestWithWidget(
+                    widgetId -> {
+                        // Initialize widget
+                        RemoteViews views = createMainWidgetViews("List widget title");
+                        views.setRemoteAdapter(android.R.id.list,
+                                new Intent(getContext(), ListViewService.class));
+                        AppWidgetManager.getInstance(getContext()).updateAppWidget(widgetId, views);
+                        verifyWidget(viewFactory.getLabel(0));
+                    },
+                    widgetId -> {
+                        // Update widget
+                        viewFactory.viewCount = 2;
+                        AppWidgetManager.getInstance(getContext())
+                                .notifyAppWidgetViewDataChanged(widgetId, android.R.id.list);
+                    },
+                    viewFactory.getLabel(1)
+            );
+        } finally {
+            TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, new Bundle());
+        }
+    }
+
+    private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
+            IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
+        try {
+            // Clear all existing data
+            LauncherSettings.Settings.call(mResolver,
+                    LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+            LauncherSettings.Settings.call(mResolver,
+                    LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+            LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
+            LauncherAppWidgetInfo item = createWidgetInfo(info, true);
+
+            addItemToScreen(item);
+            assertTrue("Widget is not present",
+                    mLauncher.pressHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
+            int widgetId = item.appWidgetId;
+
+            // Verify widget id
+            widgetIdCreationCallback.accept(widgetId);
+
+            // Go to overview once so that all views are initialized and cached
+            startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+            mLauncher.getBackground().switchToOverview().dismissAllTasks();
+
+            // Track view creations
+            mInitTracker.startTracking();
+
+            startTestActivity(2);
+            Background background = mLauncher.getBackground();
+
+            // Update widget
+            updateBeforeSwipeUp.accept(widgetId);
+
+            background.switchToOverview();
+            assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
+
+            // Widget is updated when going home
+            mInitTracker.disableLog();
+            mLauncher.pressHome();
+            verifyWidget(finalWidgetText);
+            assertNotEquals(1, mInitTracker.viewInitCount);
+        } finally {
+            mConfigMap.clear();
+        }
+    }
+
+    private void verifyWidget(String text) {
+        assertNotNull("Widget not updated",
+                UiDevice.getInstance(getInstrumentation())
+                        .wait(Until.findObject(By.text(text)), DEFAULT_UI_TIMEOUT));
+    }
+
+    private RemoteViews createMainWidgetViews(String title) {
+        Context c = getContext();
+        int layoutId = c.getResources().getIdentifier(
+                "test_layout_widget_list", "layout", c.getPackageName());
+        RemoteViews views = new RemoteViews(c.getPackageName(), layoutId);
+        views.setTextViewText(android.R.id.text1, title);
+        return views;
+    }
+
+    private class InitTracker implements Answer {
+
+        public int viewInitCount = 0;
+
+        public boolean log = true;
+
+        @Override
+        public Object answer(InvocationOnMock invocation) throws Throwable {
+            Exception ex = new Exception();
+
+            boolean found = false;
+            for (StackTraceElement ste : ex.getStackTrace()) {
+                if ("<init>".equals(ste.getMethodName())
+                        && View.class.getName().equals(ste.getClassName())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (found) {
+                viewInitCount++;
+                if (log) {
+                    Log.d("InitTracker", "New view inflated", ex);
+                }
+
+            }
+            return invocation.callRealMethod();
+        }
+
+        public void disableLog() {
+            log = false;
+        }
+
+        public void startTracking() {
+            ViewConfiguration vc = ViewConfiguration.get(mTargetContext);
+            ViewConfiguration spyVC = spy(vc);
+            mConfigMap.put(mConfigMap.keyAt(mConfigMap.indexOfValue(vc)), spyVC);
+            doAnswer(this).when(spyVC).getScaledTouchSlop();
+        }
+    }
+}
diff --git a/res/anim/slide_in_right.xml b/res/anim/slide_in_right.xml
new file mode 100644
index 0000000..55d3e54
--- /dev/null
+++ b/res/anim/slide_in_right.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false" >
+  <translate
+      android:duration="@android:integer/config_shortAnimTime"
+      android:fromXDelta="100%"
+      android:toXDelta="0%"
+      />
+</set>
\ No newline at end of file
diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml
index c156e11..32a5419 100644
--- a/res/layout/folder_application.xml
+++ b/res/layout/folder_application.xml
@@ -20,4 +20,5 @@
     style="@style/BaseIcon"
     android:textColor="?attr/folderTextColor"
     android:includeFontPadding="false"
+    android:hapticFeedbackEnabled="false"
     launcher:iconDisplay="folder" />
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d942e9e..0396df6 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"ስራ"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"መተግበሪያ አልተጫነም።"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"መተግበሪያ አይገኝም"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 5528a19..7c1ce84 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"İş"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Tətbiq quraşdırılmayıb."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Tətbiq əlçatmazdır"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 5650457..65ad91e 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Posao"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 50f92da..4ef9ec3 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Feina"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"L\'aplicació no s\'ha instal·lat."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"L\'aplicació no està disponible."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 2363e61..d80e905 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Εργασία"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Η εφαρμογή δεν έχει εγκατασταθεί."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Η εφαρμογή δεν είναι διαθέσιμη"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 0c48b5d..7adc218 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 9ba46c1..10aebe7 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Lana"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikazioa instalatu gabe dago."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Ez dago erabilgarri aplikazioa"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 7b4bcb1..65db47e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Android Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
@@ -136,7 +135,7 @@
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Retrouvez ici vos applications professionnelles"</string>
     <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Les applications professionnelles sont accompagnées d\'un badge et sont sécurisées par votre organisation. Vous pouvez les déplacer vers votre écran d\'accueil pour y accéder plus facilement."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"Géré par votre organisation"</string>
-    <string name="work_mode_off_label" msgid="3194894777601421047">"Les notifications et les applications sont désactivées"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications et applications désactivées"</string>
     <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Fermer"</string>
     <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Fermé"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index e115a72..ca5ba3c 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Traballo"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"A aplicación non está instalada"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"A aplicación non está dispoñible"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 7def3ac..3228dea 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"કાર્યાલય"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"ઍપ્લિકેશન ઇન્સ્ટોલ થઈ નથી."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"ઍપ્લિકેશન ઉપલબ્ધ નથી"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 1caa27f..b738bc0 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Posao"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 211f735..92c7acc 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Kantor"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikasi tidak tersedia"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 14c8300..67ea6a8 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"ಕೆಲಸ"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 9550a5f..ab14e89 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Стартер3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Работа"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Апликацијата не е инсталирана."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Апликацијата не е достапна"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index fd1f7d9..78856f6 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"ဖွင့်တင်စက်၃"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"အလုပ်"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"အက်ပ်မထည့်သွင်းထားပါ"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"အက်ပ်လက်လှမ်း မမှီပါ"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e88ff18..bb4834f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Tela de início 3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Trabalho"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"O app não está instalado."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"O app não está disponível"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 1251d7e..f8a61f1 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplicația nu este instalată."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplicația nu este disponibilă"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 340a128..5bbf7c3 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Pracovné"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikácia nie je nainštalovaná."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikácia nie je k dispozícii"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 6f33c4f..445e382 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Kazini"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Programu haijasakinishwa."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Programu haipatikani"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c926bc1..6e933a1 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"பணியிடம்"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"ஆப்ஸ் நிறுவப்படவில்லை."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"ஆப்ஸ் இல்லை"</string>
@@ -36,7 +35,7 @@
     <string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string>
     <string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"பயன்பாடுகளில் தேடுக"</string>
-    <string name="all_apps_loading_message" msgid="5813968043155271636">"பயன்பாடுகளை ஏற்றுகிறது…"</string>
+    <string name="all_apps_loading_message" msgid="5813968043155271636">"ஆப்ஸை ஏற்றுகிறது…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string>
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string>
     <string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string>
@@ -45,7 +44,7 @@
     <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string>
     <string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string>
-    <string name="all_apps_button_label" msgid="8130441508702294465">"பயன்பாடுகளின் பட்டியல்"</string>
+    <string name="all_apps_button_label" msgid="8130441508702294465">"ஆப்ஸின் பட்டியல்"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"தனிப்பட்ட ஆப்ஸ் பட்டியல்"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"பணி ஆப்ஸ் பட்டியல்"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"முகப்பு"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 03c02ad..5ab9b86 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Launcher3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"งาน"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"ไม่ได้ติดตั้งแอป"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"แอปไม่พร้อมใช้งาน"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 83178fe..b937764 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="649227358658669779">"Isiqalisi3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Umsebenzi"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Uhlelo lokusebenza alufakiwe."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Uhlelo lokusebenza alutholakali"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 2b265fa..7be584e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -115,7 +115,8 @@
         <attr name="numFolderColumns" format="integer" />
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
-
+        <!-- numAllAppsColumns defaults to numColumns, if not specified -->
+        <attr name="numAllAppsColumns" format="integer" />
         <attr name="defaultLayoutId" format="reference" />
         <attr name="demoModeLayoutId" format="reference" />
     </declare-styleable>
@@ -131,6 +132,12 @@
         <attr name="iconTextSize" format="float" />
         <!-- If true, this display option is used to determine the default grid -->
         <attr name="canBeDefault" format="boolean" />
+
+        <!-- The following values are only enabled if grid is supported. -->
+        <!-- allAppsIconSize defaults to iconSize, if not specified -->
+        <attr name="allAppsIconSize" format="float" />
+        <!-- allAppsIconTextSize defaults to iconTextSize, if not specified -->
+        <attr name="allAppsIconTextSize" format="float" />
     </declare-styleable>
 
     <declare-styleable name="CellLayout">
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
index a3d1216..4bb9a53 100644
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
+++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
@@ -1,12 +1,12 @@
 package com.android.launcher3.config;
 
 
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag;
 import com.android.launcher3.uioverrides.TogglableFlag;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
-import org.robolectric.RuntimeEnvironment;
 
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
@@ -14,6 +14,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Test rule that makes overriding flags in Robolectric tests easier. This rule clears all flags
@@ -51,68 +55,48 @@
         boolean value();
     }
 
-    private boolean ruleInProgress;
-
     @Override
     public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                FeatureFlags.initialize(RuntimeEnvironment.application.getApplicationContext());
-                ruleInProgress = true;
-                try {
-                    clearOverrides();
-                    applyAnnotationOverrides(description);
-                    base.evaluate();
-                } finally {
-                    ruleInProgress = false;
-                    clearOverrides();
+        return new MyStatement(base, description);
+    }
+
+    private class MyStatement extends Statement {
+
+        private final Statement mBase;
+        private final Description mDescription;
+
+
+        MyStatement(Statement base, Description description) {
+            mBase = base;
+            mDescription = description;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            Map<String, BaseTogglableFlag> allFlags = FeatureFlags.getTogglableFlags().stream()
+                    .collect(Collectors.toMap(TogglableFlag::getKey, Function.identity()));
+
+            HashMap<BaseTogglableFlag, Boolean> changedValues = new HashMap<>();
+            FlagOverride[] overrides = new FlagOverride[0];
+            try {
+                for (Annotation annotation : mDescription.getAnnotations()) {
+                    if (annotation.annotationType() == FlagOverride.class) {
+                        overrides = new FlagOverride[] { (FlagOverride) annotation };
+                    } else if (annotation.annotationType() == FlagOverrides.class) {
+                        // Note: this branch is hit if the annotation is repeated
+                        overrides = ((FlagOverrides) annotation).value();
+                    }
                 }
-            }
-        };
-    }
-
-    private void override(BaseTogglableFlag flag, boolean newValue) {
-        if (!ruleInProgress) {
-            throw new IllegalStateException(
-                    "Rule isn't in progress. Did you remember to mark it with @Rule?");
-        }
-        flag.setForTests(newValue);
-    }
-
-    private void applyAnnotationOverrides(Description description) {
-        for (Annotation annotation : description.getAnnotations()) {
-            if (annotation.annotationType() == FlagOverride.class) {
-                applyAnnotation((FlagOverride) annotation);
-            } else if (annotation.annotationType() == FlagOverrides.class) {
-                // Note: this branch is hit if the annotation is repeated
-                for (FlagOverride flagOverride : ((FlagOverrides) annotation).value()) {
-                    applyAnnotation(flagOverride);
+                for (FlagOverride override : overrides) {
+                    BaseTogglableFlag flag = allFlags.get(override.key());
+                    changedValues.put(flag, flag.get());
+                    flag.setForTests(override.value());
                 }
+                mBase.evaluate();
+            } finally {
+                // Clear the values
+                changedValues.forEach(BaseTogglableFlag::setForTests);
             }
         }
     }
-
-    private void applyAnnotation(FlagOverride flagOverride) {
-        boolean found = false;
-        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
-            if (flag.getKey().equals(flagOverride.key())) {
-                override(flag, flagOverride.value());
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            throw new IllegalStateException("Flag " + flagOverride.key() + " not found");
-        }
-    }
-
-    /**
-     * Resets all flags to their default values.
-     */
-    private void clearOverrides() {
-        for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
-            flag.setForTests(flag.getDefaultValue());
-        }
-    }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
index 1351348..31a037b 100644
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
+++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
@@ -1,5 +1,8 @@
 package com.android.launcher3.config;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
 
 import org.junit.Rule;
@@ -7,9 +10,6 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Sample Robolectric test that demonstrates flag-overriding.
  */
@@ -21,16 +21,21 @@
     @Rule
     public final FlagOverrideRule flags = new FlagOverrideRule();
 
-    @FlagOverride(key = "EXAMPLE_FLAG", value = true)
+    /**
+     * Test if flag can be overriden to true via annoation.
+     */
+    @FlagOverride(key = "FAKE_LANDSCAPE_UI", value = true)
     @Test
     public void withFlagOn() {
-        assertTrue(FeatureFlags.EXAMPLE_FLAG.get());
+        assertTrue(FeatureFlags.FAKE_LANDSCAPE_UI.get());
     }
 
-
-    @FlagOverride(key = "EXAMPLE_FLAG", value = false)
+    /**
+     * Test if flag can be overriden to false via annoation.
+     */
+    @FlagOverride(key = "FAKE_LANDSCAPE_UI", value = false)
     @Test
     public void withFlagOff() {
-        assertFalse(FeatureFlags.EXAMPLE_FLAG.get());
+        assertFalse(FeatureFlags.FAKE_LANDSCAPE_UI.get());
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 096db57..410a077 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -1,20 +1,22 @@
 package com.android.launcher3.logging;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.util.Scheduler;
 
 import java.io.File;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Calendar;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Tests for {@link FileLog}
  */
@@ -22,9 +24,10 @@
 public class FileLogTest {
 
     private File mTempDir;
+    private boolean mTestActive;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         int count = 0;
         do {
             mTempDir = new File(RuntimeEnvironment.application.getCacheDir(),
@@ -32,14 +35,24 @@
         } while (!mTempDir.mkdir());
 
         FileLog.setDir(mTempDir);
+
+        mTestActive = true;
+        Scheduler scheduler = Shadows.shadowOf(FileLog.getHandler().getLooper()).getScheduler();
+        new Thread(() -> {
+            while (mTestActive) {
+                scheduler.advanceToLastPostedRunnable();
+            }
+        }).start();
     }
 
     @After
-    public void tearDown() throws Exception {
+    public void tearDown() {
         // Clear existing logs
         new File(mTempDir, "log-0").delete();
         new File(mTempDir, "log-1").delete();
         mTempDir.delete();
+
+        mTestActive = false;
     }
 
     @Test
@@ -49,12 +62,12 @@
         }
         FileLog.print("Testing", "hoolalala");
         StringWriter writer = new StringWriter();
-        FileLog.flushAll(new PrintWriter(writer));
+        assertTrue(FileLog.flushAll(new PrintWriter(writer)));
         assertTrue(writer.toString().contains("hoolalala"));
 
         FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
         writer = new StringWriter();
-        FileLog.flushAll(new PrintWriter(writer));
+        assertTrue(FileLog.flushAll(new PrintWriter(writer)));
         assertTrue(writer.toString().contains("abracadabra"));
         // Exception is also printed
         assertTrue(writer.toString().contains("cat! cat!"));
@@ -70,7 +83,7 @@
         }
         FileLog.print("Testing", "hoolalala");
         StringWriter writer = new StringWriter();
-        FileLog.flushAll(new PrintWriter(writer));
+        assertTrue(FileLog.flushAll(new PrintWriter(writer)));
         assertTrue(writer.toString().contains("hoolalala"));
 
         Calendar threeDaysAgo = Calendar.getInstance();
@@ -80,7 +93,7 @@
 
         FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
         writer = new StringWriter();
-        FileLog.flushAll(new PrintWriter(writer));
+        assertTrue(FileLog.flushAll(new PrintWriter(writer)));
         assertTrue(writer.toString().contains("abracadabra"));
         // Exception is also printed
         assertTrue(writer.toString().contains("cat! cat!"));
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index bc936b7..32eb2ec 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -15,17 +15,20 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.TestLauncherProvider;
 
@@ -44,8 +47,6 @@
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-import androidx.annotation.NonNull;
-
 /**
  * Base class for writing tests for Model update tasks.
  */
@@ -79,6 +80,7 @@
         model = mock(LauncherModel.class);
         modelWriter = mock(ModelWriter.class);
 
+        LauncherAppState.INSTANCE.initializeForTesting(appState);
         when(appState.getModel()).thenReturn(model);
         when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter);
         when(model.getCallback()).thenReturn(callbacks);
@@ -216,5 +218,10 @@
         public Bitmap newIcon() {
             return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
         }
+
+        @Override
+        public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
+            return BitmapInfo.fromBitmap(newIcon());
+        }
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 42848f4..81b9043 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -11,7 +11,6 @@
 import com.android.launcher3.WorkspaceItemInfo;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -41,7 +40,6 @@
     }
 
     @Test
-    @Ignore("This test fails with resource errors") // b/131115553
     public void testCacheUpdate_update_apps() throws Exception {
         // Clear all icons from apps list so that its easy to check what was updated
         for (AppInfo info : allAppsList.data) {
@@ -66,7 +64,6 @@
     }
 
     @Test
-    @Ignore("This test fails with resource errors") // b/131115553
     public void testSessionUpdate_ignores_normal_apps() throws Exception {
         executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
 
@@ -75,7 +72,6 @@
     }
 
     @Test
-    @Ignore("This test fails with resource errors") // b/131115553
     public void testSessionUpdate_updates_pending_apps() throws Exception {
         executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
 
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index a46617e..b7340cf 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -130,7 +130,7 @@
         }
         helper.close();
 
-        helper = new DatabaseHelper(mContext, null, DB_FILE) {
+        helper = new DatabaseHelper(mContext, DB_FILE) {
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
@@ -161,7 +161,7 @@
 
         DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
 
-        DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, DB_FILE) {
+        DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE) {
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index 42a4f5c..a1a4561 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -5,8 +5,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import com.android.launcher3.pm.PackageInstallInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -28,7 +27,7 @@
     }
 
     private PackageInstallStateChangedTask newTask(String pkg, int progress) {
-        int state = PackageInstallerCompat.STATUS_INSTALLING;
+        int state = PackageInstallInfo.STATUS_INSTALLING;
         PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress,
                 android.os.Process.myUserHandle());
         return new PackageInstallStateChangedTask(installInfo);
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
index 31e303e..a9c1a7c 100644
--- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
+++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
@@ -2,7 +2,6 @@
 
 import android.content.Context;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
 
 import com.android.launcher3.LauncherProvider;
 
@@ -28,12 +27,9 @@
         return mOpenHelper.getWritableDatabase();
     }
 
-    @Override
-    protected void notifyListeners() { }
-
     private static class MyDatabaseHelper extends DatabaseHelper {
         public MyDatabaseHelper(Context context) {
-            super(context, null, null);
+            super(context, null);
             initIds();
         }
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 8fddf3c..af2cdc3 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
@@ -30,6 +29,7 @@
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
 import androidx.annotation.IntDef;
@@ -171,7 +171,7 @@
                 targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second);
 
         if (mIsOpen) {
-            sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
+            performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
         }
         ActivityContext.lookupContext(getContext()).getDragLayer()
                 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index e3ef5d6..5f1be94 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -1,5 +1,7 @@
 package com.android.launcher3;
 
+import static android.os.Process.myUserHandle;
+
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -12,15 +14,13 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.ContentWriter;
 
 import androidx.annotation.WorkerThread;
 
-import static android.os.Process.myUserHandle;
-
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
 
     private static final String TAG = "AWRestoredReceiver";
@@ -50,7 +50,7 @@
     @WorkerThread
     public static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
         AppWidgetHost appWidgetHost = new LauncherAppWidgetHost(context);
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             Log.e(TAG, "Skipping widget ID remap as widgets not supported");
             appWidgetHost.deleteHost();
             return;
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6455056..b28077f 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -27,6 +27,8 @@
 import android.content.res.Configuration;
 import android.view.ContextThemeWrapper;
 
+import androidx.annotation.IntDef;
+
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogUtils;
@@ -44,8 +46,6 @@
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 
-import androidx.annotation.IntDef;
-
 public abstract class BaseActivity extends Activity
         implements UserEventDelegate, LogStateProvider, ActivityContext {
 
@@ -265,12 +265,13 @@
         }
     }
 
-    protected void dumpMisc(PrintWriter writer) {
-        writer.println(" deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout());
-        writer.println(" orientation=" + getResources().getConfiguration().orientation);
-        writer.println(" mSystemUiController: " + mSystemUiController);
-        writer.println(" mActivityFlags: " + mActivityFlags);
-        writer.println(" mForceInvisible: " + mForceInvisible);
+    protected void dumpMisc(String prefix, PrintWriter writer) {
+        writer.println(prefix + "deviceProfile isTransposed="
+                + getDeviceProfile().isVerticalBarLayout());
+        writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation);
+        writer.println(prefix + "mSystemUiController: " + mSystemUiController);
+        writer.println(prefix + "mActivityFlags: " + mActivityFlags);
+        writer.println(prefix + "mForceInvisible: " + mForceInvisible);
     }
 
     public static <T extends BaseActivity> T fromContext(Context context) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index f61051f..00a06ae 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -19,6 +19,7 @@
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -30,17 +31,17 @@
 import android.view.View;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.uioverrides.DejankBinderTracker;
 import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 
-import androidx.annotation.Nullable;
-
 /**
  * Extension of BaseActivity allowing support for drag-n-drop
  */
@@ -65,7 +66,8 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mIsSafeModeEnabled = getPackageManager().isSafeMode();
+        mIsSafeModeEnabled = DejankBinderTracker.whitelistIpcs(() ->
+                getPackageManager().isSafeMode());
         mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged);
 
         // Update theme
@@ -166,7 +168,7 @@
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                         Process.myUserHandle(), sourceContainer);
             } else {
-                LauncherAppsCompat.getInstance(this).startActivityForProfile(
+                getSystemService(LauncherApps.class).startMainActivity(
                         intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
                         sourceContainer);
@@ -241,14 +243,14 @@
     protected void onDeviceProfileInitiated() {
         if (mDeviceProfile.isVerticalBarLayout()) {
             mRotationListener.enable();
-            mDeviceProfile.updateIsSeascape(getWindowManager());
+            mDeviceProfile.updateIsSeascape(this);
         } else {
             mRotationListener.disable();
         }
     }
 
     private void onDeviceRotationChanged() {
-        if (mDeviceProfile.updateIsSeascape(getWindowManager())) {
+        if (mDeviceProfile.updateIsSeascape(this)) {
             reapplyUi();
         }
     }
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index c84be4d..864fa6e 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import androidx.recyclerview.widget.RecyclerView;
@@ -171,4 +172,13 @@
      * <p>Override in each subclass of this base class.
      */
     public void onFastScrollCompleted() {}
+
+    @Override
+    public void onScrollStateChanged(int state) {
+        super.onScrollStateChanged(state);
+
+        if (state == SCROLL_STATE_IDLE) {
+            AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index b113249..7adb6a4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -104,6 +104,8 @@
     private Drawable mIcon;
     private final boolean mCenterVertically;
 
+    private final int mDisplay;
+
     private final CheckLongPressHelper mLongPressHelper;
     private final StylusEventHelper mStylusEventHelper;
     private final float mSlop;
@@ -133,6 +135,9 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDisableRelayout = false;
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final boolean mIgnorePaddingTouch;
+
     private IconLoadRequest mIconLoadRequest;
 
     public BubbleTextView(Context context) {
@@ -152,26 +157,32 @@
                 R.styleable.BubbleTextView, defStyle, 0);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
 
-        int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
+        mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
         final int defaultIconSize;
-        if (display == DISPLAY_WORKSPACE) {
+        if (mDisplay == DISPLAY_WORKSPACE) {
             DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
-        } else if (display == DISPLAY_ALL_APPS) {
+            mIgnorePaddingTouch = true;
+        } else if (mDisplay == DISPLAY_ALL_APPS) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
-        } else if (display == DISPLAY_FOLDER) {
+            mIgnorePaddingTouch = true;
+        } else if (mDisplay == DISPLAY_FOLDER) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+            mIgnorePaddingTouch = true;
         } else {
+            // widget_selection or shortcut_popup
             defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
+            mIgnorePaddingTouch = false;
         }
+
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
 
         mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
@@ -319,6 +330,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        // ignore events if they happen in padding area
+        if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
+                && (event.getY() < getPaddingTop()
+                || event.getX() < getPaddingLeft()
+                || event.getY() > getHeight() - getPaddingBottom()
+                || event.getX() > getWidth() - getPaddingRight())) {
+            return false;
+        }
+
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
         boolean result = super.onTouchEvent(event);
@@ -564,7 +584,11 @@
             mDotInfo = mActivity.getDotInfoForItem(itemInfo);
             boolean isDotted = mDotInfo != null;
             float newDotScale = isDotted ? 1f : 0;
-            mDotRenderer = mActivity.getDeviceProfile().mDotRenderer;
+            if (mDisplay == DISPLAY_ALL_APPS) {
+                mDotRenderer = mActivity.getDeviceProfile().mDotRendererAllApps;
+            } else {
+                mDotRenderer = mActivity.getDeviceProfile().mDotRendererWorkSpace;
+            }
             if (wasDotted || isDotted) {
                 // Animate when a dot is first added or when it is removed.
                 if (animate && (wasDotted ^ isDotted) && isShown()) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 883e8c6..736142f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,12 +24,12 @@
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Surface;
-import android.view.WindowManager;
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.util.DefaultDisplay;
 
 public class DeviceProfile {
 
@@ -111,6 +111,7 @@
 
     // All apps
     public int allAppsCellHeightPx;
+    public int allAppsCellWidthPx;
     public int allAppsIconSizePx;
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
@@ -129,7 +130,8 @@
     private boolean mIsSeascape;
 
     // Notification dots
-    public DotRenderer mDotRenderer;
+    public DotRenderer mDotRendererWorkSpace;
+    public DotRenderer mDotRendererAllApps;
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
             Point minSize, Point maxSize,
@@ -230,8 +232,11 @@
         updateWorkspacePadding();
 
         // This is done last, after iconSizePx is calculated above.
-        mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
+        mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
                 IconShape.DEFAULT_PATH_SIZE);
+        mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
+                new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
+                        IconShape.DEFAULT_PATH_SIZE);
     }
 
     public DeviceProfile copy(Context context) {
@@ -307,11 +312,16 @@
         updateAvailableFolderCellDimensions(dm, res);
     }
 
+    /**
+     * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx,
+     * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
+     * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
+     */
     private void updateIconSize(float scale, Resources res, DisplayMetrics dm) {
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
-        float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
-        iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizePx, dm) * scale));
+        float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
+        iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale));
         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
 
@@ -329,12 +339,20 @@
         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
 
         // All apps
-        allAppsIconTextSizePx = iconTextSizePx;
-        allAppsIconSizePx = iconSizePx;
-        allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
-        allAppsCellHeightPx = getCellSize().y;
+        if (allAppsHasDifferentNumColumns()) {
+            allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm);
+            allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm);
+            allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
+            allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+        } else {
+            allAppsIconSizePx = iconSizePx;
+            allAppsIconTextSizePx = iconTextSizePx;
+            allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
+            allAppsCellHeightPx = getCellSize().y;
+        }
+        allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
 
-        if (isVerticalLayout) {
+        if (isVerticalBarLayout()) {
             // Always hide the Workspace text with vertical bar layout.
             adjustToHideWorkspaceLabels();
         }
@@ -419,14 +437,18 @@
     }
 
     public Point getCellSize() {
+        return getCellSize(inv.numColumns, inv.numRows);
+    }
+
+    private Point getCellSize(int numColumns, int numRows) {
         Point result = new Point();
         // Since we are only concerned with the overall padding, layout direction does
         // not matter.
         Point padding = getTotalWorkspacePadding();
         result.x = calculateCellWidth(availableWidthPx - padding.x
-                - cellLayoutPaddingLeftRightPx * 2, inv.numColumns);
+                - cellLayoutPaddingLeftRightPx * 2, numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.y
-                - cellLayoutBottomPaddingPx, inv.numRows);
+                - cellLayoutBottomPaddingPx, numRows);
         return result;
     }
 
@@ -542,11 +564,19 @@
     }
 
     /**
+     * Returns true when the number of workspace columns and all apps columns differs.
+     */
+    private boolean allAppsHasDifferentNumColumns() {
+        return inv.numAllAppsColumns != inv.numColumns;
+    }
+
+    /**
      * Updates orientation information and returns true if it has changed from the previous value.
      */
-    public boolean updateIsSeascape(WindowManager wm) {
+    public boolean updateIsSeascape(Context context) {
         if (isVerticalBarLayout()) {
-            boolean isSeascape = wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270;
+            boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
+                    == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
                 return true;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 67d5ab0..e2b7b68 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -93,13 +93,6 @@
         itemsChanged(animate);
     }
 
-    public void setTitle(CharSequence title) {
-        this.title = title;
-        for (int i = 0; i < mListeners.size(); i++) {
-            mListeners.get(i).onTitleChanged(title);
-        }
-    }
-
     @Override
     public void onAddToDatabase(ContentWriter writer) {
         super.onAddToDatabase(writer);
@@ -121,18 +114,10 @@
         }
     }
 
-    public void prepareAutoUpdate() {
-        for (int i = 0; i < mListeners.size(); i++) {
-            mListeners.get(i).prepareAutoUpdate();
-        }
-    }
-
     public interface FolderListener {
         public void onAdd(WorkspaceItemInfo item, int rank);
         public void onRemove(WorkspaceItemInfo item);
-        public void onTitleChanged(CharSequence title);
         public void onItemsChanged(boolean animate);
-        public void prepareAutoUpdate();
     }
 
     public boolean hasOption(int optionFlag) {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 00acdcd..03ee707 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -105,8 +105,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        // Don't let if follow through to workspace
-        return true;
+        return event.getY() > getCellHeight();
     }
 
     @Override
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index c9e7d91..8ebf464 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -26,6 +26,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
@@ -40,7 +41,6 @@
 
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
@@ -69,7 +69,6 @@
     public static final int FLAG_ACTIVITY_PAUSED = 1;
     public static final int FLAG_LOADER_RUNNING = 2;
     public static final int FLAG_DRAG_AND_DROP = 4;
-    public static final int FLAG_BULK_ADD = 4;
 
     // Determines whether to defer installing shortcuts immediately until
     // processAllPendingInstalls() is called.
@@ -110,8 +109,7 @@
 
     @WorkerThread
     private static void flushQueueInBackground(Context context) {
-        LauncherModel model = LauncherAppState.getInstance(context).getModel();
-        if (model.getCallback() == null) {
+        if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) {
             // Launcher not loaded
             return;
         }
@@ -124,7 +122,7 @@
             return;
         }
 
-        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
         for (String encoded : strings) {
             PendingInstallShortcutInfo info = decode(encoded, context);
             if (info == null) {
@@ -133,7 +131,7 @@
 
             String pkg = getIntentPackage(info.launchIntent);
             if (!TextUtils.isEmpty(pkg)
-                    && !launcherApps.isPackageEnabledForProfile(pkg, info.user)
+                    && !launcherApps.isPackageEnabled(pkg, info.user)
                     && !info.isActivity) {
                 if (DBG) {
                     Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
@@ -146,7 +144,8 @@
         }
         prefs.edit().remove(APPS_PENDING_INSTALL).apply();
         if (!installQueue.isEmpty()) {
-            model.addAndBindAddedWorkspaceItems(installQueue);
+            LauncherAppState.getInstance(context).getModel()
+                    .addAndBindAddedWorkspaceItems(installQueue);
         }
     }
 
@@ -520,7 +519,7 @@
         try {
             Decoder decoder = new Decoder(encoded, context);
             if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+                LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
                         .resolveActivity(decoder.launcherIntent, decoder.user);
                 if (info != null) {
                     return new PendingInstallShortcutInfo(info, context);
@@ -610,7 +609,7 @@
             return original;
         }
 
-        LauncherActivityInfo info = LauncherAppsCompat.getInstance(original.mContext)
+        LauncherActivityInfo info = original.mContext.getSystemService(LauncherApps.class)
                 .resolveActivity(original.launchIntent, original.user);
         if (info == null) {
             return original;
@@ -620,6 +619,11 @@
     }
 
     private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+        if (data == null) {
+            Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
+            return null;
+        }
+
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 8ee530f..310a9e9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.annotation.TargetApi;
@@ -40,14 +41,13 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.Xml;
-import android.view.Display;
-import android.view.WindowManager;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
+import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Themes;
@@ -103,6 +103,8 @@
     public int iconBitmapSize;
     public int fillResIconDpi;
     public float iconTextSize;
+    public float allAppsIconSize;
+    public float allAppsIconTextSize;
 
     private SparseArray<TypedValue> mExtraAttrs;
 
@@ -111,6 +113,11 @@
      */
     public int numHotseatIcons;
 
+    /**
+     * Number of columns in the all apps list.
+     */
+    public int numAllAppsColumns;
+
     public int defaultLayoutId;
     int demoModeLayoutId;
 
@@ -137,6 +144,9 @@
         landscapeIconSize = p.landscapeIconSize;
         iconTextSize = p.iconTextSize;
         numHotseatIcons = p.numHotseatIcons;
+        numAllAppsColumns = p.numAllAppsColumns;
+        allAppsIconSize = p.allAppsIconSize;
+        allAppsIconTextSize = p.allAppsIconTextSize;
         defaultLayoutId = p.defaultLayoutId;
         demoModeLayoutId = p.demoModeLayoutId;
         mExtraAttrs = p.mExtraAttrs;
@@ -145,7 +155,10 @@
 
     @TargetApi(23)
     private InvariantDeviceProfile(Context context) {
-        initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null));
+        String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+                : null;
+        initGrid(context, gridName);
         mConfigMonitor = new ConfigMonitor(context,
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
         mOverlayMonitor = new OverlayMonitor(context);
@@ -173,19 +186,17 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        Display display = wm.getDefaultDisplay();
-        DisplayMetrics dm = new DisplayMetrics();
-        display.getMetrics(dm);
+        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
 
-        Point smallestSize = new Point();
-        Point largestSize = new Point();
-        display.getCurrentSizeRange(smallestSize, largestSize);
+        Point smallestSize = new Point(displayInfo.smallestSize);
+        Point largestSize = new Point(displayInfo.largestSize);
 
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
         // This guarantees that width < height
-        float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
-        float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
+        float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
+                displayInfo.metrics);
+        float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
+                displayInfo.metrics);
         // Sort the profiles based on the closeness to the device size
         Collections.sort(allOptions, (a, b) ->
                 Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps),
@@ -201,6 +212,8 @@
         demoModeLayoutId = closestProfile.demoModeLayoutId;
         numFolderRows = closestProfile.numFolderRows;
         numFolderColumns = closestProfile.numFolderColumns;
+        numAllAppsColumns = closestProfile.numAllAppsColumns;
+
         mExtraAttrs = closestProfile.extraAttrs;
 
         if (!closestProfile.name.equals(gridName)) {
@@ -211,16 +224,23 @@
         iconSize = interpolatedDisplayOption.iconSize;
         iconShapePath = getIconShapePath(context);
         landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
-        iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
+        iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics);
         iconTextSize = interpolatedDisplayOption.iconTextSize;
         fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
 
+        if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) {
+            allAppsIconSize = interpolatedDisplayOption.allAppsIconSize;
+            allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize;
+        } else {
+            allAppsIconSize = iconSize;
+            allAppsIconTextSize = iconTextSize;
+        }
+
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
-        applyPartnerDeviceProfileOverrides(context, dm);
+        applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
 
-        Point realSize = new Point();
-        display.getRealSize(realSize);
+        Point realSize = new Point(displayInfo.realSize);
         // The real size never changes. smallSide and largeSide will remain the
         // same in any orientation.
         int smallSide = Math.min(realSize.x, realSize.y);
@@ -289,9 +309,10 @@
         InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this);
 
         // Re-init grid
-        // TODO(b/131867841): We pass in null here so that we can calculate the closest profile
-        // without the bias of the grid name.
-        initGrid(context, null);
+        String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+                : null;
+        initGrid(context, gridName);
 
         int changeFlags = 0;
         if (numRows != oldProfile.numRows ||
@@ -493,6 +514,8 @@
 
         private final int numHotseatIcons;
 
+        private final int numAllAppsColumns;
+
         private final int defaultLayoutId;
         private final int demoModeLayoutId;
 
@@ -515,6 +538,9 @@
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
             numFolderColumns = a.getInt(
                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
+            numAllAppsColumns = a.getInt(
+                    R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
+
             a.recycle();
 
             extraAttrs = Themes.createValueMap(context, attrs,
@@ -531,8 +557,10 @@
         private final boolean canBeDefault;
 
         private float iconSize;
-        private float landscapeIconSize;
         private float iconTextSize;
+        private float landscapeIconSize;
+        private float allAppsIconSize;
+        private float allAppsIconTextSize;
 
         DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
             this.grid = grid;
@@ -550,6 +578,11 @@
             landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
                     iconSize);
             iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+
+            allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
+                    iconSize);
+            allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
+                    iconTextSize);
             a.recycle();
         }
 
@@ -564,14 +597,18 @@
         private DisplayOption multiply(float w) {
             iconSize *= w;
             landscapeIconSize *= w;
+            allAppsIconSize *= w;
             iconTextSize *= w;
+            allAppsIconTextSize *= w;
             return this;
         }
 
         private DisplayOption add(DisplayOption p) {
             iconSize += p.iconSize;
             landscapeIconSize += p.landscapeIconSize;
+            allAppsIconSize += p.allAppsIconSize;
             iconTextSize += p.iconTextSize;
+            allAppsIconTextSize += p.allAppsIconTextSize;
             return this;
         }
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e6ab70e..c92d917 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
+import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
@@ -79,6 +80,7 @@
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -88,7 +90,6 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
@@ -108,18 +109,21 @@
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.pm.PinRequestHelper;
 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;
 import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 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;
@@ -148,6 +152,12 @@
 import com.android.launcher3.widget.WidgetListRowEntry;
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.shared.LauncherExterns;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -157,17 +167,18 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.function.Predicate;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
+import java.util.function.Supplier;
 
 /**
  * Default launcher application.
  */
 public class Launcher extends BaseDraggingActivity implements LauncherExterns,
-        Callbacks, LauncherProviderChangeListener, UserEventDelegate,
-        InvariantDeviceProfile.OnIDPChangeListener {
+        Callbacks, 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;
@@ -295,8 +306,14 @@
     private DeviceProfile mStableDeviceProfile;
     private RotationMode mRotationMode = RotationMode.NORMAL;
 
+    protected LauncherOverlayManager mOverlayManager;
+    // If true, overlay callbacks are deferred
+    private boolean mDeferOverlayCallbacks;
+    private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        DejankBinderTracker.allowBinderTrackingInTests();
         RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
         if (DEBUG_STRICT_MODE) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -334,8 +351,8 @@
         UiFactory.onCreate(this);
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
-
-        mAppWidgetHost = new LauncherAppWidgetHost(this);
+        mAppWidgetHost = new LauncherAppWidgetHost(this,
+                appWidgetId -> getWorkspace().removeWidget(appWidgetId));
         mAppWidgetHost.startListening();
 
         mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
@@ -345,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.
@@ -391,6 +408,10 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onCreate(savedInstanceState);
         }
+        mOverlayManager = getDefaultOverlay();
+        PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
+                OverlayPlugin.class, false /* allowedMultiple */);
+
         mRotationHelper.initialize();
 
         TraceHelper.endSection("Launcher-onCreate");
@@ -414,6 +435,39 @@
                 }
             }
         });
+        DejankBinderTracker.disallowBinderTrackingInTests();
+    }
+
+    protected LauncherOverlayManager getDefaultOverlay() {
+        return new LauncherOverlayManager() { };
+    }
+
+    @Override
+    public void onPluginConnected(OverlayPlugin overlayManager, Context context) {
+        switchOverlay(() -> overlayManager.createOverlayManager(this, this));
+    }
+
+    @Override
+    public void onPluginDisconnected(OverlayPlugin plugin) {
+        switchOverlay(this::getDefaultOverlay);
+    }
+
+    private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
+        if (mOverlayManager != null) {
+            mOverlayManager.onActivityDestroyed(this);
+        }
+        mOverlayManager = overlaySupplier.get();
+        if (getRootView().isAttachedToWindow()) {
+            mOverlayManager.onAttachedToWindow();
+        }
+        mDeferOverlayCallbacks = true;
+        checkIfOverlayStillDeferred();
+    }
+
+    @Override
+    protected void dispatchDeviceProfileChanged() {
+        super.dispatchDeviceProfileChanged();
+        mOverlayManager.onDeviceProvideChanged();
     }
 
     @Override
@@ -502,6 +556,8 @@
         // Load configuration-specific DeviceProfile
         mDeviceProfile = idp.getDeviceProfile(this);
         if (isInMultiWindowMode()) {
+            // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return
+            // the app window size
             Display display = getWindowManager().getDefaultDisplay();
             Point mwSize = new Point();
             display.getSize(mwSize);
@@ -558,18 +614,12 @@
         return mLauncherView.findViewById(id);
     }
 
-    @Override
-    public void onAppWidgetHostReset() {
-        if (mAppWidgetHost != null) {
-            mAppWidgetHost.startListening();
-        }
-    }
-
     private LauncherCallbacks mLauncherCallbacks;
 
     /**
      * Call this after onCreate to set or clear overlay.
      */
+    @Override
     public void setLauncherOverlay(LauncherOverlay overlay) {
         if (overlay != null) {
             overlay.setOverlayCallbacks(new LauncherOverlayCallbacksImpl());
@@ -577,18 +627,16 @@
         mWorkspace.setLauncherOverlay(overlay);
     }
 
+    @Override
+    public void runOnOverlayHidden(Runnable runnable) {
+        getWorkspace().runOnOverlayHidden(runnable);
+    }
+
     public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
         mLauncherCallbacks = callbacks;
         return true;
     }
 
-    @Override
-    public void onLauncherProviderChanged() {
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onLauncherProviderChange();
-        }
-    }
-
     public boolean isDraggingEnabled() {
         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
         // that is subsequently removed from the workspace in startBinding().
@@ -787,9 +835,6 @@
             final int requestCode, final int resultCode, final Intent data) {
         mPendingActivityRequestCode = -1;
         handleActivityResult(requestCode, resultCode, data);
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onActivityResult(requestCode, resultCode, data);
-        }
     }
 
     @Override
@@ -816,10 +861,6 @@
                         getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
             }
         }
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onRequestPermissionsResult(requestCode, permissions,
-                    grantResults);
-        }
     }
 
     /**
@@ -878,9 +919,12 @@
     protected void onStop() {
         super.onStop();
 
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onStop();
+        if (mDeferOverlayCallbacks) {
+            checkIfOverlayStillDeferred();
+        } else {
+            mOverlayManager.onActivityStopped(this);
         }
+
         logStopAndResume(Action.Command.STOP);
 
         mAppWidgetHost.setListenIfResumed(false);
@@ -896,13 +940,16 @@
 
     @Override
     protected void onStart() {
+        DejankBinderTracker.allowBinderTrackingInTests();
         RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
         super.onStart();
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onStart();
+        if (!mDeferOverlayCallbacks) {
+            mOverlayManager.onActivityStarted(this);
         }
+
         mAppWidgetHost.setListenIfResumed(true);
         RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
+        DejankBinderTracker.disallowBinderTrackingInTests();
     }
 
     private void handleDeferredResume() {
@@ -942,21 +989,62 @@
         } else {
             getUserEventDispatcher().logActionCommand(command, containerType, -1);
         }
-
     }
 
-    protected void onStateSet(LauncherState state) {
-        getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+    private void scheduleDeferredCheck() {
+        mHandler.removeCallbacks(mDeferredOverlayCallbacks);
+        postAsyncCallback(mHandler, mDeferredOverlayCallbacks);
+    }
+
+    private void checkIfOverlayStillDeferred() {
+        if (!mDeferOverlayCallbacks) {
+            return;
+        }
+        if (isStarted() && (!hasBeenResumed() || mStateManager.getState().disableInteraction)) {
+            return;
+        }
+        mDeferOverlayCallbacks = false;
+
+        // Move the client to the correct state. Calling the same method twice is no-op.
+        if (isStarted()) {
+            mOverlayManager.onActivityStarted(this);
+        }
+        if (hasBeenResumed()) {
+            mOverlayManager.onActivityResumed(this);
+        } else {
+            mOverlayManager.onActivityPaused(this);
+        }
+        if (!isStarted()) {
+            mOverlayManager.onActivityStopped(this);
+        }
+    }
+
+    public void deferOverlayCallbacksUntilNextResumeOrStop() {
+        mDeferOverlayCallbacks = true;
+    }
+
+    public LauncherOverlayManager getOverlayManager() {
+        return mOverlayManager;
+    }
+
+    public void onStateSetStart(LauncherState state) {
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onStateChanged();
+        if (mDeferOverlayCallbacks) {
+            scheduleDeferredCheck();
         }
     }
 
+    public void onStateSetEnd(LauncherState state) {
+        getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+        getWorkspace().setClipChildren(!state.disablePageClipping);
+        finishAutoCancelActionMode();
+    }
+
     @Override
     protected void onResume() {
+        DejankBinderTracker.allowBinderTrackingInTests();
         RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
         TraceHelper.beginSection("ON_RESUME");
         super.onResume();
@@ -974,12 +1062,15 @@
             resumeCallbacks.clear();
         }
 
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onResume();
+        if (mDeferOverlayCallbacks) {
+            scheduleDeferredCheck();
+        } else {
+            mOverlayManager.onActivityResumed(this);
         }
 
         TraceHelper.endSection("ON_RESUME");
         RaceConditionTracker.onEvent(ON_RESUME_EVT, EXIT);
+        DejankBinderTracker.disallowBinderTrackingInTests();
     }
 
     @Override
@@ -991,8 +1082,9 @@
         mDragController.cancelDrag();
         mDragController.resetLastGestureUpTime();
         mDropTargetBar.animateToVisibility(false);
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onPause();
+
+        if (!mDeferOverlayCallbacks) {
+            mOverlayManager.onActivityPaused(this);
         }
     }
 
@@ -1008,35 +1100,6 @@
         mStateManager.onWindowFocusChanged();
     }
 
-    public interface LauncherOverlay {
-
-        /**
-         * Touch interaction leading to overscroll has begun
-         */
-        void onScrollInteractionBegin();
-
-        /**
-         * Touch interaction related to overscroll has ended
-         */
-        void onScrollInteractionEnd();
-
-        /**
-         * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
-         * screen (or in the case of RTL, the rightmost screen).
-         */
-        void onScrollChange(float progress, boolean rtl);
-
-        /**
-         * Called when the launcher is ready to use the overlay
-         * @param callbacks A set of callbacks provided by Launcher in relation to the overlay
-         */
-        void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
-    }
-
-    public interface LauncherOverlayCallbacks {
-        void onScrollChanged(float progress);
-    }
-
     class LauncherOverlayCallbacksImpl implements LauncherOverlayCallbacks {
 
         public void onScrollChanged(float progress) {
@@ -1167,8 +1230,8 @@
 
         WorkspaceItemInfo info = null;
         if (Utilities.ATLEAST_OREO) {
-            info = LauncherAppsCompatVO.createWorkspaceItemFromPinItemRequest(
-                    this, LauncherAppsCompatVO.getPinItemRequest(data), 0);
+            info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
+                    this, PinRequestHelper.getPinItemRequest(data), 0);
         }
 
         if (info == null) {
@@ -1294,19 +1357,14 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onAttachedToWindow();
-        }
+        mOverlayManager.onAttachedToWindow();
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onDetachedFromWindow();
-        }
+        mOverlayManager.onDetachedFromWindow();
+        closeContextMenu();
     }
 
     public AllAppsTransitionController getAllAppsController() {
@@ -1355,10 +1413,16 @@
         return mModelWriter;
     }
 
+    @Override
     public SharedPreferences getSharedPrefs() {
         return mSharedPrefs;
     }
 
+    @Override
+    public SharedPreferences getDevicePrefs() {
+        return Utilities.getDevicePrefs(this);
+    }
+
     public int getOrientation() {
         return mOldConfig.orientation;
     }
@@ -1376,8 +1440,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) {
@@ -1415,6 +1478,7 @@
             if (mLauncherCallbacks != null) {
                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
             }
+            mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
         }
 
         TraceHelper.endSection("NEW_INTENT");
@@ -1460,18 +1524,17 @@
         }
 
         super.onSaveInstanceState(outState);
-
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onSaveInstanceState(outState);
-        }
+        mOverlayManager.onActivitySaveInstanceState(this, outState);
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
+        ACTIVITY_TRACKER.onActivityDestroyed(this);
 
         unregisterReceiver(mScreenOffReceiver);
         mWorkspace.removeFolderListeners();
+        PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
 
         if (mCancelTouchController != null) {
             mCancelTouchController.run();
@@ -1496,9 +1559,8 @@
         TextKeyListener.getInstance().release();
         clearPendingBinds();
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onDestroy();
-        }
+
+        mOverlayManager.onActivityDestroyed(this);
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1744,9 +1806,6 @@
         if (finishAutoCancelActionMode()) {
             return;
         }
-        if (mLauncherCallbacks != null && mLauncherCallbacks.handleBackPressed()) {
-            return;
-        }
 
         if (mDragController.isDragging()) {
             mDragController.cancelDrag();
@@ -1830,7 +1889,7 @@
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
             addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
-            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
+            UiFactory.clearSwipeSharedState(this, true /* finishAnimation */);
             return true;
         }
 
@@ -1871,9 +1930,6 @@
             // This clears all widget bitmaps from the widget tray
             // TODO(hyunyoungs)
         }
-        if (mLauncherCallbacks != null) {
-            mLauncherCallbacks.onTrimMemory(level);
-        }
         UiFactory.onTrimMemory(this, level);
     }
 
@@ -2341,6 +2397,11 @@
         // override the previous page so we don't log the page switch.
         mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
 
+        // Cache one page worth of icons
+        getViewCache().setCacheSize(R.layout.folder_application,
+                mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
+        getViewCache().setCacheSize(R.layout.folder_page, 2);
+
         TraceHelper.endSection("finishBindingItems");
     }
 
@@ -2466,14 +2527,16 @@
         }
 
         writer.println(prefix + "Misc:");
-        writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
-        writer.print(" mPendingRequestArgs=" + mPendingRequestArgs);
-        writer.println(" mPendingActivityResult=" + mPendingActivityResult);
-        writer.println(" mRotationHelper: " + mRotationHelper);
+        dumpMisc(prefix + "\t", writer);
+        writer.println(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
+        writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs
+                + " mPendingActivityResult=" + mPendingActivityResult);
+        writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
+        writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
+
         // Extra logging for b/116853349
         mDragLayer.dump(prefix, writer);
         mStateManager.dump(prefix, writer);
-        dumpMisc(writer);
 
         try {
             FileLog.flushAll(writer);
@@ -2486,6 +2549,7 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.dump(prefix, fd, writer, args);
         }
+        mOverlayManager.dump(prefix, writer);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 82b1ea7..c717d1a 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,26 +17,27 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.content.ComponentName;
-import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
 import android.os.Handler;
 import android.util.Log;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.pm.InstallSessionTracker;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 public class LauncherAppState {
@@ -44,7 +45,7 @@
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
-    private static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
+    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
             new MainThreadInitializedObject<>(LauncherAppState::new);
 
     private final Context mContext;
@@ -54,6 +55,9 @@
     private final InvariantDeviceProfile mInvariantDeviceProfile;
     private final SecureSettingsObserver mNotificationDotsObserver;
 
+    private final InstallSessionTracker mInstallSessionTracker;
+    private final SimpleBroadcastReceiver mModelChangeReceiver;
+
     public static LauncherAppState getInstance(final Context context) {
         return INSTANCE.get(context);
     }
@@ -67,10 +71,6 @@
     }
 
     private LauncherAppState(Context context) {
-        if (getLocalProvider(context) == null) {
-            throw new RuntimeException(
-                    "Initializing LauncherAppState in the absence of LauncherProvider");
-        }
         Log.v(Launcher.TAG, "LauncherAppState initiated");
         Preconditions.assertUIThread();
         mContext = context;
@@ -80,27 +80,30 @@
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
         mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
 
-        LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
+        mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
 
-        // Register intent receivers
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        // For handling managed profiles
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-
+        mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
+        mModelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
+                Intent.ACTION_MANAGED_PROFILE_ADDED,
+                Intent.ACTION_MANAGED_PROFILE_REMOVED,
+                Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
+                Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
         if (FeatureFlags.IS_DOGFOOD_BUILD) {
-            filter.addAction(ACTION_FORCE_ROLOAD);
+            mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
         }
+        // TODO: remove listener on terminate
+        FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
+        CustomWidgetManager.INSTANCE.get(mContext)
+                .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
 
-        mContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(mContext).enableAndResetCache();
         mInvariantDeviceProfile.addOnChangeListener(this::onIdpChanged);
         new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
 
+        mInstallSessionTracker = PackageInstallerCompat.getInstance(context)
+                .registerInstallTracker(mModel, MODEL_EXECUTOR);
+
         if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) {
             mNotificationDotsObserver = null;
         } else {
@@ -137,20 +140,18 @@
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
-        mContext.unregisterReceiver(mModel);
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);
-        launcherApps.removeOnAppsChangedCallback(mModel);
-        PackageInstallerCompat.getInstance(mContext).onStop();
+        mContext.unregisterReceiver(mModelChangeReceiver);
+        mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
+        mInstallSessionTracker.unregister();
+        CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
+
         if (mNotificationDotsObserver != null) {
             mNotificationDotsObserver.unregister();
         }
     }
 
     LauncherModel setLauncher(Launcher launcher) {
-        getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
-        CustomWidgetManager.INSTANCE.get(launcher)
-                .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
         return mModel;
     }
 
@@ -176,11 +177,4 @@
     public static InvariantDeviceProfile getIDP(Context context) {
         return InvariantDeviceProfile.INSTANCE.get(context);
     }
-
-    private static LauncherProvider getLocalProvider(Context context) {
-        try (ContentProviderClient cl = context.getContentResolver()
-                .acquireContentProviderClient(LauncherProvider.AUTHORITY)) {
-            return (LauncherProvider) cl.getLocalContentProvider();
-        }
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 1139f29..4e29a95 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -29,12 +29,13 @@
 import android.util.SparseArray;
 import android.widget.Toast;
 
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.widget.DeferredAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
+import java.util.function.IntConsumer;
 
 
 /**
@@ -56,9 +57,17 @@
     private final Context mContext;
     private int mFlags = FLAG_RESUMED;
 
+    private IntConsumer mAppWidgetRemovedCallback = null;
+
     public LauncherAppWidgetHost(Context context) {
+        this(context, null);
+    }
+
+    public LauncherAppWidgetHost(Context context,
+            IntConsumer appWidgetRemovedCallback) {
         super(context, APPWIDGET_HOST_ID);
         mContext = context;
+        mAppWidgetRemovedCallback = appWidgetRemovedCallback;
     }
 
     @Override
@@ -71,7 +80,7 @@
 
     @Override
     public void startListening() {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return;
         }
         mFlags |= FLAG_LISTENING;
@@ -98,13 +107,17 @@
 
     @Override
     public void stopListening() {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return;
         }
         mFlags &= ~FLAG_LISTENING;
         super.stopListening();
     }
 
+    public boolean isListening() {
+        return (mFlags & FLAG_LISTENING) != 0;
+    }
+
     /**
      * Updates the resumed state of the host.
      * When a host is not resumed, it defers calls to startListening until host is resumed again.
@@ -153,7 +166,7 @@
 
     @Override
     public int allocateAppWidgetId() {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return AppWidgetManager.INVALID_APPWIDGET_ID;
         }
 
@@ -205,7 +218,7 @@
                 }
                 view.setAppWidget(appWidgetId, appWidget);
                 view.switchToErrorView();
-                return  view;
+                return view;
             }
         }
     }
@@ -223,6 +236,18 @@
         info.initSpans(mContext);
     }
 
+    /**
+     * Called on an appWidget is removed for a widgetId
+     * @param appWidgetId
+     * TODO: make this override when SDK is updated
+     */
+    public void onAppWidgetRemoved(int appWidgetId) {
+        if (mAppWidgetRemovedCallback == null) {
+            return;
+        }
+        mAppWidgetRemovedCallback.accept(appWidgetId);
+    }
+
     @Override
     public void deleteAppWidgetId(int appWidgetId) {
         super.deleteAppWidgetId(appWidgetId);
@@ -238,7 +263,7 @@
     public void startBindFlow(BaseActivity activity,
             int appWidgetId, AppWidgetProviderInfo info, int requestCode) {
 
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             sendActionCancelled(activity, requestCode);
             return;
         }
@@ -254,7 +279,7 @@
 
 
     public void startConfigActivity(BaseActivity activity, int widgetId, int requestCode) {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             sendActionCancelled(activity, requestCode);
             return;
         }
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index dfe75ec..0e529bd 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import android.content.Intent;
 import android.os.Bundle;
 
 import java.io.FileDescriptor;
@@ -36,31 +35,8 @@
      * the code in the corresponding Launcher method is executed.
      */
     void onCreate(Bundle savedInstanceState);
-    void onResume();
-    void onStart();
-    void onStop();
-    void onPause();
-    void onDestroy();
-    void onSaveInstanceState(Bundle outState);
-    void onActivityResult(int requestCode, int resultCode, Intent data);
-    void onRequestPermissionsResult(int requestCode, String[] permissions,
-            int[] grantResults);
-    void onAttachedToWindow();
-    void onDetachedFromWindow();
     void dump(String prefix, FileDescriptor fd, PrintWriter w, String[] args);
     void onHomeIntent(boolean internalStateHandled);
-    boolean handleBackPressed();
-    void onTrimMemory(int level);
-
-    /**
-     * Called when the launcher state changed
-     */
-    default void onStateChanged() { }
-
-    /*
-     * Extension points for providing custom behavior on certain user interactions.
-     */
-    void onLauncherProviderChange();
 
     /**
      * Starts a search with {@param initialQuery}. Return false if search was not started.
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 8433a91..bea25e0 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -21,11 +21,10 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -33,11 +32,11 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
@@ -51,7 +50,11 @@
 import com.android.launcher3.model.PackageUpdatedTask;
 import com.android.launcher3.model.ShortcutsChangedTask;
 import com.android.launcher3.model.UserLockStateChangedTask;
+import com.android.launcher3.pm.InstallSessionTracker;
+import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
@@ -71,8 +74,7 @@
  * LauncherModel object held in a static. Also provide APIs for updating the database state
  * for the Launcher.
  */
-public class LauncherModel extends BroadcastReceiver
-        implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
+public class LauncherModel extends LauncherApps.Callback implements InstallSessionTracker.Callback {
     private static final boolean DEBUG_RECEIVER = false;
 
     static final String TAG = "Launcher.Model";
@@ -124,20 +126,6 @@
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
     }
 
-    public void setPackageState(PackageInstallInfo installInfo) {
-        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
-    }
-
-    /**
-     * Updates the icons and label of all pending icons for the provided package name.
-     */
-    public void updateSessionDisplayInfo(final String packageName) {
-        HashSet<String> packages = new HashSet<>();
-        packages.add(packageName);
-        enqueueModelUpdateTask(new CacheDataUpdatedTask(
-                CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
-    }
-
     /**
      * Adds the provided items to the workspace.
      */
@@ -177,6 +165,7 @@
 
     public void onPackagesRemoved(UserHandle user, String... packages) {
         int op = PackageUpdatedTask.OP_REMOVE;
+        FileLog.d(TAG, "package removed received " + String.join("," + packages));
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages));
     }
 
@@ -225,21 +214,15 @@
         enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false));
     }
 
-    /**
-     * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
-     * ACTION_PACKAGE_CHANGED.
-     */
-    @Override
-    public void onReceive(Context context, Intent intent) {
+    public void onBroadcastIntent(Intent intent) {
         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
-
         final String action = intent.getAction();
         if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
         } else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)
                 || Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
-            UserManagerCompat.getInstance(context).enableAndResetCache();
+            UserManagerCompat.getInstance(mApp.getContext()).enableAndResetCache();
             forceReload();
         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) ||
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) ||
@@ -365,16 +348,65 @@
         }
     }
 
+    @Override
     public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
+        if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
+            enqueueModelUpdateTask(new BaseModelUpdateTask() {
+                @Override
+                public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                    apps.addPromiseApp(app.getContext(), sessionInfo);
+                    bindApplicationsIfNeeded();
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onSessionFailure(String packageName, UserHandle user) {
+        if (!FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) {
+            return;
+        }
         enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                apps.addPromiseApp(app.getContext(), sessionInfo);
-                bindApplicationsIfNeeded();
+                final IntSparseArrayMap<Boolean> removedIds = new IntSparseArrayMap<>();
+                synchronized (dataModel) {
+                    for (ItemInfo info : dataModel.itemsIdMap) {
+                        if (info instanceof WorkspaceItemInfo
+                                && ((WorkspaceItemInfo) info).hasPromiseIconUi()
+                                && user.equals(info.user)
+                                && info.getIntent() != null
+                                && TextUtils.equals(packageName, info.getIntent().getPackage())) {
+                            removedIds.put(info.id, true /* remove */);
+                        }
+                    }
+                }
+
+                if (!removedIds.isEmpty()) {
+                    deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false));
+                }
             }
         });
     }
 
+    @Override
+    public void onPackageStateChanged(PackageInstallInfo installInfo) {
+        enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
+    }
+
+    /**
+     * Updates the icons and label of all pending icons for the provided package name.
+     */
+    @Override
+    public void onUpdateSessionDisplay(PackageUserKey key, PackageInstaller.SessionInfo info) {
+        mApp.getIconCache().updateSessionCache(key, info);
+
+        HashSet<String> packages = new HashSet<>();
+        packages.add(key.mPackageName);
+        enqueueModelUpdateTask(new CacheDataUpdatedTask(
+                CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
+    }
+
     public class LoaderTransaction implements AutoCloseable {
 
         private final LoaderTask mTask;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 6081300..42927ea 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -45,8 +45,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
@@ -69,7 +67,6 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.NoLocaleSQLiteHelper;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -100,9 +97,6 @@
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
-    private final ChangeListenerWrapper mListenerWrapper = new ChangeListenerWrapper();
-    private Handler mListenerHandler;
-
     protected DatabaseHelper mOpenHelper;
 
     /**
@@ -122,7 +116,6 @@
         if (FeatureFlags.IS_DOGFOOD_BUILD) {
             Log.d(TAG, "Launcher process started");
         }
-        mListenerHandler = new Handler(mListenerWrapper);
 
         // The content provider exists for the entire duration of the launcher main process and
         // is the first component to get created.
@@ -130,14 +123,6 @@
         return true;
     }
 
-    /**
-     * Sets a provider listener.
-     */
-    public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
-        Preconditions.assertUIThread();
-        mListenerWrapper.mListener = listener;
-    }
-
     @Override
     public String getType(Uri uri) {
         SqlArguments args = new SqlArguments(uri, null, null);
@@ -153,7 +138,7 @@
      */
     protected synchronized void createDbIfNotExists() {
         if (mOpenHelper == null) {
-            mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
+            mOpenHelper = new DatabaseHelper(getContext());
 
             if (RestoreDbTask.isPending(getContext())) {
                 if (!RestoreDbTask.performRestore(getContext(), mOpenHelper,
@@ -223,7 +208,6 @@
         mOpenHelper.onAddOrDeleteOp(db);
 
         uri = ContentUris.withAppendedId(uri, rowId);
-        notifyListeners();
         reloadLauncherIfExternal();
         return uri;
     }
@@ -283,7 +267,6 @@
             t.commit();
         }
 
-        notifyListeners();
         reloadLauncherIfExternal();
         return values.length;
     }
@@ -329,7 +312,6 @@
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) {
             mOpenHelper.onAddOrDeleteOp(db);
-            notifyListeners();
             reloadLauncherIfExternal();
         }
         return count;
@@ -343,8 +325,6 @@
         addModifiedTime(values);
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         int count = db.update(args.table, values, args.where, args.args);
-        if (count > 0) notifyListeners();
-
         reloadLauncherIfExternal();
         return count;
     }
@@ -438,13 +418,6 @@
         }
     }
 
-    /**
-     * Overridden in tests
-     */
-    protected void notifyListeners() {
-        mListenerHandler.sendEmptyMessage(ChangeListenerWrapper.MSG_LAUNCHER_PROVIDER_CHANGED);
-    }
-
     @Thunk static void addModifiedTime(ContentValues values) {
         values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
     }
@@ -564,15 +537,13 @@
      * The class is subclassed in tests to create an in-memory db.
      */
     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
-        private final BackupManager mBackupManager;
-        private final Handler mWidgetHostResetHandler;
         private final Context mContext;
         private int mMaxItemId = -1;
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
 
-        DatabaseHelper(Context context, Handler widgetHostResetHandler) {
-            this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
+        DatabaseHelper(Context context) {
+            this(context, LauncherFiles.LAUNCHER_DB);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
@@ -589,12 +560,9 @@
         /**
          * Constructor used in tests and for restore.
          */
-        public DatabaseHelper(
-                Context context, Handler widgetHostResetHandler, String tableName) {
+        public DatabaseHelper(Context context, String tableName) {
             super(context, tableName, SCHEMA_VERSION);
             mContext = context;
-            mWidgetHostResetHandler = widgetHostResetHandler;
-            mBackupManager = new BackupManager(mContext);
         }
 
         protected void initIds() {
@@ -633,13 +601,6 @@
          * Overriden in tests.
          */
         protected void onEmptyDbCreated() {
-            // Database was just created, so wipe any previous widgets
-            if (mWidgetHostResetHandler != null) {
-                newLauncherWidgetHost().deleteHost();
-                mWidgetHostResetHandler.sendEmptyMessage(
-                        ChangeListenerWrapper.MSG_APP_WIDGET_HOST_RESET);
-            }
-
             // Set the flag for empty DB
             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
         }
@@ -1039,27 +1000,4 @@
             }
         }
     }
-
-    private static class ChangeListenerWrapper implements Handler.Callback {
-
-        private static final int MSG_LAUNCHER_PROVIDER_CHANGED = 1;
-        private static final int MSG_APP_WIDGET_HOST_RESET = 2;
-
-        private LauncherProviderChangeListener mListener;
-
-        @Override
-        public boolean handleMessage(Message msg) {
-            if (mListener != null) {
-                switch (msg.what) {
-                    case MSG_LAUNCHER_PROVIDER_CHANGED:
-                        mListener.onLauncherProviderChanged();
-                        break;
-                    case MSG_APP_WIDGET_HOST_RESET:
-                        mListener.onAppWidgetHostReset();
-                        break;
-                }
-            }
-            return true;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/LauncherProviderChangeListener.java b/src/com/android/launcher3/LauncherProviderChangeListener.java
deleted file mode 100644
index 0243088..0000000
--- a/src/com/android/launcher3/LauncherProviderChangeListener.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.android.launcher3;
-
-/**
- * This class is a listener for {@link LauncherProvider} changes. It gets notified in the
- * sendNotify method. This listener is needed because by default the Launcher suppresses
- * standard data change callbacks.
- */
-public interface LauncherProviderChangeListener {
-
-    void onLauncherProviderChanged();
-
-    void onAppWidgetHostReset();
-}
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/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 242e099..c509680 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -59,6 +59,10 @@
         public static final String ITEM_TYPE = "itemType";
 
         /**
+         * The gesture is a package
+         */
+        public static final int ITEM_TYPE_NON_ACTIONABLE = -1;
+        /**
          * The gesture is an application
          */
         public static final int ITEM_TYPE_APPLICATION = 0;
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 63914b0..848e19f 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -136,7 +136,7 @@
     }
 
     public void dump(String prefix, PrintWriter writer) {
-        writer.println(prefix + "LauncherState");
+        writer.println(prefix + "LauncherState:");
         writer.println(prefix + "\tmLastStableState:" + mLastStableState);
         writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
         writer.println(prefix + "\tmState:" + mState);
@@ -227,11 +227,6 @@
 
     private void goToState(LauncherState state, boolean animated, long delay,
             final Runnable onCompleteRunnable) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " +
-                    state.getClass().getSimpleName() +
-                    " @ " + Log.getStackTraceString(new Throwable()));
-        }
         animated &= Utilities.areAnimationsEnabled(mLauncher);
         if (mLauncher.isInState(state)) {
             if (mConfig.mCurrentAnimation == null) {
@@ -412,13 +407,8 @@
             mState.onStateDisabled(mLauncher);
         }
         mState = state;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " +
-                    state.getClass().getSimpleName() +
-                    " @ " + Log.getStackTraceString(new Throwable()));
-        }
         mState.onStateEnabled(mLauncher);
-        mLauncher.onStateSet(mState);
+        mLauncher.onStateSetStart(mState);
 
         if (state.disablePageClipping) {
             // Only disable clipping if needed, otherwise leave it as previous value.
@@ -436,16 +426,10 @@
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
             mCurrentStableState = state;
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " +
-                        state.getClass().getSimpleName() +
-                        " @ " + Log.getStackTraceString(new Throwable()));
-            }
         }
 
         state.onStateTransitionEnd(mLauncher);
-        mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
-        mLauncher.finishAutoCancelActionMode();
+        mLauncher.onStateSetEnd(state);
 
         if (state == NORMAL) {
             setRestState(null);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f9a326f..ff2b400 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -890,23 +890,7 @@
                 mTotalMotionX = 0;
                 mActivePointerId = ev.getPointerId(0);
 
-                /*
-                 * If being flinged and user touches the screen, initiate drag;
-                 * otherwise don't.  mScroller.isFinished should be false when
-                 * being flinged.
-                 */
-                final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
-                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
-
-                if (finishedScrolling) {
-                    mIsBeingDragged = false;
-                    if (!mScroller.isFinished() && !mFreeScroll) {
-                        setCurrentPage(getNextPage());
-                        pageEndTransition();
-                    }
-                } else {
-                    mIsBeingDragged = true;
-                }
+                updateIsBeingDraggedOnTouchDown();
 
                 break;
             }
@@ -929,6 +913,25 @@
         return mIsBeingDragged;
     }
 
+    /**
+     * If being flinged and user touches the screen, initiate drag; otherwise don't.
+     */
+    private void updateIsBeingDraggedOnTouchDown() {
+        // mScroller.isFinished should be false when being flinged.
+        final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos());
+        final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
+
+        if (finishedScrolling) {
+            mIsBeingDragged = false;
+            if (!mScroller.isFinished() && !mFreeScroll) {
+                setCurrentPage(getNextPage());
+                pageEndTransition();
+            }
+        } else {
+            mIsBeingDragged = true;
+        }
+    }
+
     public boolean isHandlingTouch() {
         return mIsBeingDragged;
     }
@@ -1085,7 +1088,9 @@
         if (mFreeScroll) {
             setCurrentPage(getNextPage());
         } else if (wasFreeScroll) {
-            snapToPage(getNextPage());
+            if (getScrollForPage(getNextPage()) != getScrollX()) {
+                snapToPage(getNextPage());
+            }
         }
     }
 
@@ -1104,6 +1109,8 @@
 
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN:
+            updateIsBeingDraggedOnTouchDown();
+
             /*
              * If being flinged and user touches, stop the fling. isFinished
              * will be false if being flinged.
@@ -1543,7 +1550,7 @@
             snapToPage(getNextPage() - 1);
             return true;
         }
-        return false;
+        return onOverscroll(-getMeasuredWidth());
     }
 
     public boolean scrollRight() {
@@ -1551,7 +1558,15 @@
             snapToPage(getNextPage() + 1);
             return true;
         }
-        return false;
+        return onOverscroll(getMeasuredWidth());
+    }
+
+    protected boolean onOverscroll(int amount) {
+        if (!mAllowOverScroll) return false;
+        onScrollInteractionBegin();
+        overScroll(amount);
+        onScrollInteractionEnd();
+        return true;
     }
 
     @Override
@@ -1571,8 +1586,9 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         final boolean pagesFlipped = isPageOrderFlipped();
-        info.setScrollable(getPageCount() > 1);
-        if (getCurrentPage() < getPageCount() - 1) {
+        int offset = (mAllowOverScroll ? 0 : 1);
+        info.setScrollable(getPageCount() > offset);
+        if (getCurrentPage() < getPageCount() - offset) {
             info.addAction(pagesFlipped ?
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
@@ -1580,7 +1596,7 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
         }
-        if (getCurrentPage() > 0) {
+        if (getCurrentPage() >= offset) {
             info.addAction(pagesFlipped ?
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
@@ -1588,7 +1604,6 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);
         }
-
         // Accessibility-wise, PagedView doesn't support long click, so disabling it.
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
@@ -1607,7 +1622,7 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        event.setScrollable(getPageCount() > 1);
+        event.setScrollable(mAllowOverScroll || getPageCount() > 1);
     }
 
     @Override
diff --git a/src/com/android/launcher3/PromiseAppInfo.java b/src/com/android/launcher3/PromiseAppInfo.java
index 4ad0b3d..e55e4bd 100644
--- a/src/com/android/launcher3/PromiseAppInfo.java
+++ b/src/com/android/launcher3/PromiseAppInfo.java
@@ -19,16 +19,16 @@
 import android.content.Context;
 import android.content.Intent;
 
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.util.PackageManagerHelper;
-
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.util.PackageManagerHelper;
+
 public class PromiseAppInfo extends AppInfo {
 
     public int level = 0;
 
-    public PromiseAppInfo(@NonNull PackageInstallerCompat.PackageInstallInfo installInfo) {
+    public PromiseAppInfo(@NonNull PackageInstallInfo installInfo) {
         componentName = installInfo.componentName;
         intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_LAUNCHER)
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 55cb6f2..3c2ed72 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -2,6 +2,7 @@
 
 import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE;
+
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
@@ -15,6 +16,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Bundle;
@@ -27,11 +29,12 @@
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 
 import java.net.URISyntaxException;
@@ -155,7 +158,7 @@
             user = item.user;
         }
         if (intent != null) {
-            LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
+            LauncherActivityInfo info = mLauncher.getSystemService(LauncherApps.class)
                     .resolveActivity(intent, user);
             if (info != null
                     && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -240,6 +243,7 @@
                     .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
                     .putExtra(Intent.EXTRA_USER, info.user);
             mLauncher.startActivity(i);
+            FileLog.d(TAG, "start uninstall activity " + cn.getPackageName());
             return cn;
         } catch (URISyntaxException e) {
             Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
@@ -284,9 +288,8 @@
         @Override
         public void onLauncherResume() {
             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
-            if (LauncherAppsCompat.getInstance(mContext)
-                    .getApplicationInfo(mPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            mDragObject.dragInfo.user) == null) {
+            if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
+                    mDragObject.dragInfo.user, PackageManager.MATCH_UNINSTALLED_PACKAGES) == null) {
                 mDragObject.dragSource = mOriginal;
                 mOriginal.onDropCompleted(SecondaryDropTarget.this, mDragObject, true);
             } else {
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 6853bf6..8dedc6c 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.pm.PackageInstallerCompat.getUserHandle;
+
 import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -23,6 +25,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
@@ -37,14 +40,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.Executors;
-import com.android.launcher3.compat.PackageInstallerCompat;
 
 import java.util.List;
 
-import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle;
-
 /**
  * BroadcastReceiver to handle session commit intent.
  */
@@ -85,9 +85,8 @@
 
     public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) {
         String packageName = sessionInfo.getAppPackageName();
-        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
-                .getActivityList(packageName, getUserHandle(sessionInfo));
-        if (activities == null || activities.isEmpty()) {
+        if (context.getSystemService(LauncherApps.class)
+                .getActivityList(packageName, getUserHandle(sessionInfo)).isEmpty()) {
             // Ensure application isn't already installed.
             queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
                     sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
@@ -95,9 +94,9 @@
     }
 
     public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
-        List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
+        List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
                 .getActivityList(packageName, user);
-        if (activities == null || activities.isEmpty()) {
+        if (activities.isEmpty()) {
             // no activity found
             return;
         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 6ddebe7..92f8069 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
@@ -59,12 +60,11 @@
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntArray;
@@ -526,7 +526,7 @@
             boolean flattenDrawable, Object[] outObj) {
         LauncherAppState appState = LauncherAppState.getInstance(launcher);
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-            LauncherActivityInfo activityInfo = LauncherAppsCompat.getInstance(launcher)
+            LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
                     .resolveActivity(info.getIntent(), info.user);
             outObj[0] = activityInfo;
             return (activityInfo != null) ? appState.getIconCache()
@@ -584,7 +584,7 @@
             LauncherIcons li = LauncherIcons.obtain(appState.getContext());
             Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).iconBitmap;
             li.recycle();
-            float badgeSize = launcher.getResources().getDimension(R.dimen.profile_badge_size);
+            float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
                     insetFraction, insetFraction, 0, 0);
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index abf6cbd..c5e74ef 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -35,13 +35,13 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShadowGenerator;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.PackageUserKey;
@@ -467,7 +467,7 @@
 
     private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info,
             int maxWidth, int maxHeight, Bitmap preview) {
-        int iconSize = launcher.getDeviceProfile().iconSizePx;
+        int iconSize = launcher.getDeviceProfile().allAppsIconSizePx;
         int padding = launcher.getResources()
                 .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1b757d5..eca5d12 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -60,7 +60,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Toast;
 
-import com.android.launcher3.Launcher.LauncherOverlay;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
@@ -92,7 +91,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -102,6 +100,7 @@
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -134,9 +133,6 @@
 
     private static final int DEFAULT_PAGE = 0;
 
-    public static final boolean MAP_NO_RECURSE = false;
-    public static final boolean MAP_RECURSE = true;
-
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -969,6 +965,9 @@
         onOverlayScrollChanged(0);
     }
 
+    public boolean hasOverlay() {
+        return mLauncherOverlay != null;
+    }
 
     private boolean isScrollingOverlay() {
         return mLauncherOverlay != null &&
@@ -1039,6 +1038,13 @@
     }
 
     @Override
+    protected boolean onOverscroll(int amount) {
+        // Enforce overscroll on -1 direction
+        if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
+        return super.onOverscroll(amount);
+    }
+
+    @Override
     protected boolean shouldFlingForVelocity(int velocityX) {
         // When the overlay is moving, the fling or settle transition is controlled by the overlay.
         return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
@@ -1465,6 +1471,9 @@
 
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared");
+        }
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1490,7 +1499,7 @@
         Rect dragRect = null;
         if (child instanceof BubbleTextView) {
             dragRect = new Rect();
-            ((BubbleTextView) child).getIconBounds(dragRect);
+            BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
             dragLayerY += dragRect.top;
             // Note: The dragRect is used to calculate drag layer offsets, but the
             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
@@ -2806,10 +2815,27 @@
     }
 
     /**
+     * Removed widget from workspace by appWidgetId
+     * @param appWidgetId
+     */
+    public void removeWidget(int appWidgetId) {
+        mapOverItems((info, view) -> {
+            if (info instanceof LauncherAppWidgetInfo) {
+                LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info;
+                if (appWidgetInfo.appWidgetId == appWidgetId) {
+                    mLauncher.removeItem(view, appWidgetInfo, true);
+                    return true;
+                }
+            }
+            return false;
+        });
+    }
+
+    /**
      * Removes all folder listeners
      */
     public void removeFolderListeners() {
-        mapOverItems(false, new ItemOperator() {
+        mapOverItems(new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View view) {
                 if (view instanceof FolderIcon) {
@@ -2961,7 +2987,7 @@
 
     public View getFirstMatch(final ItemOperator operator) {
         final View[] value = new View[1];
-        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+        mapOverItems(new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v) {
                 if (operator.evaluate(info, v)) {
@@ -2984,7 +3010,7 @@
         final View[] matches = new View[operators.length];
         // For efficiency, the outer loop should be CellLayout.
         for (CellLayout cellLayout : cellLayouts) {
-            mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> {
+            mapOverCellLayout(cellLayout, (info, v) -> {
                 for (int i = 0; i < operators.length; ++i) {
                     if (matches[i] == null && operators[i].evaluate(info, v)) {
                         matches[i] = v;
@@ -3009,7 +3035,7 @@
     }
 
     void clearDropTargets() {
-        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+        mapOverItems(new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v) {
                 if (v instanceof DropTarget) {
@@ -3054,10 +3080,12 @@
                 } else if (itemToRemove.container >= 0) {
                     // The item may belong to a folder.
                     View parent = idToViewMap.get(itemToRemove.container);
-                    if (parent != null) {
+                    if (parent instanceof FolderIcon) {
                         FolderInfo folderInfo = (FolderInfo) parent.getTag();
-                        folderInfo.prepareAutoUpdate();
                         folderInfo.remove((WorkspaceItemInfo) itemToRemove, false);
+                        if (((FolderIcon) parent).getFolder().isOpen()) {
+                            ((FolderIcon) parent).getFolder().close(false /* animate */);
+                        }
                     }
                 }
             }
@@ -3081,18 +3109,17 @@
     /**
      * Map the operator over the shortcuts and widgets, return the first-non-null value.
      *
-     * @param recurse true: iterate over folder children. false: op get the folders themselves.
      * @param op the operator to map over the shortcuts
      */
-    public void mapOverItems(boolean recurse, ItemOperator op) {
+    public void mapOverItems(ItemOperator op) {
         for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
-            if (mapOverCellLayout(recurse, layout, op)) {
+            if (mapOverCellLayout(layout, op)) {
                 return;
             }
         }
     }
 
-    private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) {
+    private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) {
         // TODO(b/128460496) Potential race condition where layout is not yet loaded
         if (layout == null) {
             return false;
@@ -3102,103 +3129,68 @@
         final int itemCount = container.getChildCount();
         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
             View item = container.getChildAt(itemIdx);
-            ItemInfo info = (ItemInfo) item.getTag();
-            if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
-                FolderIcon folder = (FolderIcon) item;
-                ArrayList<View> folderChildren = folder.getFolder().getIconsInReadingOrder();
-                // map over all the children in the folder
-                final int childCount = folderChildren.size();
-                for (int childIdx = 0; childIdx < childCount; childIdx++) {
-                    View child = folderChildren.get(childIdx);
-                    info = (ItemInfo) child.getTag();
-                    if (op.evaluate(info, child)) {
-                        return true;
-                    }
-                }
-            } else {
-                if (op.evaluate(info, item)) {
-                    return true;
-                }
+            if (op.evaluate((ItemInfo) item.getTag(), item)) {
+                return true;
             }
         }
         return false;
     }
 
     void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
-        int total  = shortcuts.size();
-        final HashSet<WorkspaceItemInfo> updates = new HashSet<>(total);
-        final IntSet folderIds = new IntSet();
+        final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+        ItemOperator op = (info, v) -> {
+            if (v instanceof BubbleTextView && updates.contains(info)) {
+                WorkspaceItemInfo si = (WorkspaceItemInfo) info;
+                BubbleTextView shortcut = (BubbleTextView) v;
+                Drawable oldIcon = shortcut.getIcon();
+                boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+                        && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
+                shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
+            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+                ((FolderIcon) v).updatePreviewItems(updates::contains);
+            }
 
-        for (int i = 0; i < total; i++) {
-            WorkspaceItemInfo s = shortcuts.get(i);
-            updates.add(s);
-            folderIds.add(s.container);
+            // Iterate all items
+            return false;
+        };
+
+        mapOverItems(op);
+        Folder openFolder = Folder.getOpen(mLauncher);
+        if (openFolder != null) {
+            openFolder.iterateOverItems(op);
         }
-
-        mapOverItems(MAP_RECURSE, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView &&
-                        updates.contains(info)) {
-                    WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                    BubbleTextView shortcut = (BubbleTextView) v;
-                    Drawable oldIcon = shortcut.getIcon();
-                    boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
-                            && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
-                    shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
-                }
-                // process all the shortcuts
-                return false;
-            }
-        });
-
-        // Update folder icons
-        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof FolderInfo && folderIds.contains(info.id)) {
-                    ((FolderInfo) info).itemsChanged(false);
-                }
-                // process all the shortcuts
-                return false;
-            }
-        });
     }
 
     public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
         final PackageUserKey packageUserKey = new PackageUserKey(null, null);
-        final IntSet folderIds = new IntSet();
-        mapOverItems(MAP_RECURSE, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
-                    if (!packageUserKey.updateFromItemInfo(info)
-                            || updatedDots.test(packageUserKey)) {
-                        ((BubbleTextView) v).applyDotState(info, true /* animate */);
-                        folderIds.add(info.container);
-                    }
-                }
-                // process all the shortcuts
-                return false;
-            }
-        });
+        Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+                || updatedDots.test(packageUserKey);
 
-        // Update folder icons
-        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof FolderInfo && folderIds.contains(info.id)
-                        && v instanceof FolderIcon) {
+        ItemOperator op = (info, v) -> {
+            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
+                if (matcher.test(info)) {
+                    ((BubbleTextView) v).applyDotState(info, true /* animate */);
+                }
+            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+                FolderInfo fi = (FolderInfo) info;
+                if (fi.contents.stream().anyMatch(matcher)) {
                     FolderDotInfo folderDotInfo = new FolderDotInfo();
-                    for (WorkspaceItemInfo si : ((FolderInfo) info).contents) {
+                    for (WorkspaceItemInfo si : fi.contents) {
                         folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
                     }
                     ((FolderIcon) v).setDotInfo(folderDotInfo);
                 }
-                // process all the shortcuts
-                return false;
             }
-        });
+
+            // process all the shortcuts
+            return false;
+        };
+
+        mapOverItems(op);
+        Folder folder = Folder.getOpen(mLauncher);
+        if (folder != null) {
+            folder.iterateOverItems(op);
+        }
     }
 
     public void removeAbandonedPromise(String packageName, UserHandle user) {
@@ -3210,21 +3202,25 @@
     }
 
     public void updateRestoreItems(final HashSet<ItemInfo> updates) {
-        mapOverItems(MAP_RECURSE, new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
-                        && updates.contains(info)) {
-                    ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
-                } else if (v instanceof PendingAppWidgetHostView
-                        && info instanceof LauncherAppWidgetInfo
-                        && updates.contains(info)) {
-                    ((PendingAppWidgetHostView) v).applyState();
-                }
-                // process all the shortcuts
-                return false;
+        ItemOperator op = (info, v) -> {
+            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
+                    && updates.contains(info)) {
+                ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
+            } else if (v instanceof PendingAppWidgetHostView
+                    && info instanceof LauncherAppWidgetInfo
+                    && updates.contains(info)) {
+                ((PendingAppWidgetHostView) v).applyState();
+            } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
+                ((FolderIcon) v).updatePreviewItems(updates::contains);
             }
-        });
+            // process all the shortcuts
+            return false;
+        };
+        mapOverItems(op);
+        Folder folder = Folder.getOpen(mLauncher);
+        if (folder != null) {
+            folder.iterateOverItems(op);
+        }
     }
 
     public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
@@ -3248,7 +3244,7 @@
             } else {
                 // widgetRefresh will automatically run when the packages are updated.
                 // For now just update the progress bars
-                mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+                mapOverItems(new ItemOperator() {
                     @Override
                     public boolean evaluate(ItemInfo info, View view) {
                         if (view instanceof PendingAppWidgetHostView
@@ -3372,7 +3368,7 @@
             mRefreshPending = false;
 
             ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
-            mapOverItems(MAP_NO_RECURSE, (info, view) -> {
+            mapOverItems((info, view) -> {
                 if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
                     views.add((PendingAppWidgetHostView) view);
                 }
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 1323588..23795c5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -212,7 +212,7 @@
     public ComponentName getTargetComponent() {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
-                || hasStatusFlag(FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON))) {
+                || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) {
             // Legacy shortcuts and promise icons with web UI may not have a componentName but just
             // a packageName. In that case create a dummy componentName instead of adding additional
             // check everywhere.
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0c1303b..0c12c60 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.accessibility;
 
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+
 import static com.android.launcher3.LauncherState.NORMAL;
 
 import android.app.AlertDialog;
@@ -30,14 +32,14 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.ShortcutUtil;
@@ -164,6 +166,13 @@
     }
 
     public boolean performAction(final View host, final ItemInfo item, int action) {
+        if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) {
+            CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+            if (popup.canShow()) {
+                popup.show();
+                return true;
+            }
+        }
         if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 293b867..13b7b54 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -21,12 +21,10 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.os.Process;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -34,6 +32,13 @@
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -45,10 +50,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -60,13 +62,6 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
 /**
  * The all apps view container.
  */
@@ -163,16 +158,14 @@
     }
 
     private void onAppsUpdated() {
-        if (FeatureFlags.ALL_APPS_TABS_ENABLED) {
-            boolean hasWorkApps = false;
-            for (AppInfo app : mAllAppsStore.getApps()) {
-                if (mWorkMatcher.matches(app, null)) {
-                    hasWorkApps = true;
-                    break;
-                }
+        boolean hasWorkApps = false;
+        for (AppInfo app : mAllAppsStore.getApps()) {
+            if (mWorkMatcher.matches(app, null)) {
+                hasWorkApps = true;
+                break;
             }
-            rebindAdapters(hasWorkApps);
         }
+        rebindAdapters(hasWorkApps);
     }
 
     /**
@@ -312,6 +305,7 @@
                 + grid.cellLayoutPaddingLeftRightPx;
 
         for (int i = 0; i < mAH.length; i++) {
+            mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
             mAH[i].padding.bottom = insets.bottom;
             mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
             mAH[i].applyPadding();
@@ -615,17 +609,4 @@
                     && verticalFadingEdge);
         }
     }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (AccessibilityManagerCompat.processTestRequest(
-                mLauncher, TestProtocol.GET_SCROLL_MESSAGE, action, arguments,
-                response ->
-                        response.putInt(TestProtocol.SCROLL_Y_FIELD,
-                                getActiveRecyclerView().getCurrentScrollY()))) {
-            return true;
-        }
-
-        return super.performAccessibilityAction(action, arguments);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 3cfa0b1..bb21268 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -180,7 +180,7 @@
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
 
-    private final int mAppsPerRow;
+    private int mAppsPerRow;
 
     private BindViewCallback mBindViewCallback;
     private OnFocusChangeListener mIconFocusListener;
@@ -200,7 +200,11 @@
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mLayoutInflater = LayoutInflater.from(launcher);
 
-        mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
+        setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
+    }
+
+    public void setAppsPerRow(int appsPerRow) {
+        mAppsPerRow = appsPerRow;
         mGridLayoutMgr.setSpanCount(mAppsPerRow);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 1399302..f82e380 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -34,6 +34,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -113,6 +114,13 @@
         if (mScrollbar != null) {
             mScrollbar.reattachThumbToScroll();
         }
+        if (getLayoutManager() instanceof AppsGridLayoutManager) {
+            AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager();
+            if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
+                // We are at the top, so don't scrollToPosition (would cause unnecessary relayout).
+                return;
+            }
+        }
         scrollToPosition(0);
     }
 
@@ -419,13 +427,4 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
-
-    @Override
-    public void onScrollStateChanged(int state) {
-        super.onScrollStateChanged(state);
-
-        if (state == SCROLL_STATE_IDLE) {
-            AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
-        }
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index dfddc61..3836c9f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,7 +1,5 @@
 package com.android.launcher3.allapps;
 
-import static android.view.View.ALPHA;
-
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -12,7 +10,6 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_UPDATE_ALL_APPS_VISIBILITY;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -21,7 +18,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.util.FloatProperty;
-import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -215,12 +211,7 @@
 
         Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
         Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
-        View allAppsContent = mAppsView.getContentView();
-        if (!hasAllAppsContent && builder.hasFlag(FLAG_DONT_UPDATE_ALL_APPS_VISIBILITY)) {
-            setter.setFloat(allAppsContent, ALPHA, 0, allAppsFade);
-        } else {
-            setter.setViewAlpha(allAppsContent, hasAllAppsContent ? 1 : 0, allAppsFade);
-        }
+        setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
         setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
         mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent,
                 setter, headerFade, allAppsFade);
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/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 07e7553..cd30dea 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -43,7 +43,6 @@
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
 
     public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
-    public static final int FLAG_DONT_UPDATE_ALL_APPS_VISIBILITY = 1 << 1;
 
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
 
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
new file mode 100644
index 0000000..0f34c1e
--- /dev/null
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -0,0 +1,229 @@
+/*
+ * 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.anim;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.FloatProperty;
+
+import com.android.launcher3.util.DefaultDisplay;
+
+import androidx.annotation.FloatRange;
+import androidx.dynamicanimation.animation.SpringForce;
+
+/**
+ * Utility class to build an object animator which follows the same path as a spring animation for
+ * an underdamped spring.
+ */
+public class SpringAnimationBuilder<T> extends FloatProperty<T> {
+
+    private final T mTarget;
+    private final FloatProperty<T> mProperty;
+
+    private float mStartValue;
+    private float mEndValue;
+    private float mVelocity = 0;
+
+    private float mStiffness = SpringForce.STIFFNESS_MEDIUM;
+    private float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+    private float mMinVisibleChange = 1;
+
+    // Multiplier to the min visible change value for value threshold
+    private static final float THRESHOLD_MULTIPLIER = 0.65f;
+
+    /**
+     * The spring equation is given as
+     *   x = e^(-beta*t/2) * (a cos(gamma * t) + b sin(gamma * t)
+     *   v = e^(-beta*t/2) * ((2 * a * gamma + beta * b) * sin(gamma * t)
+     *                  + (a * beta - 2 * b * gamma) * cos(gamma * t)) / 2
+     *
+     *   a = x(0)
+     *   b = beta * x(0) / (2 * gamma) + v(0) / gamma
+     */
+    private double beta;
+    private double gamma;
+
+    private double a, b;
+    private double va, vb;
+
+    // Threshold for velocity and value to determine when it's reasonable to assume that the spring
+    // is approximately at rest.
+    private double mValueThreshold;
+    private double mVelocityThreshold;
+
+    private float mCurrentTime = 0;
+
+    public SpringAnimationBuilder(T target, FloatProperty<T> property) {
+        super("dynamic-spring-property");
+        mTarget = target;
+        mProperty = property;
+
+        mStartValue = mProperty.get(target);
+    }
+
+    public SpringAnimationBuilder<T> setEndValue(float value) {
+        mEndValue = value;
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setStartValue(float value) {
+        mStartValue = value;
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setValues(float... values) {
+        if (values.length > 1) {
+            mStartValue = values[0];
+            mEndValue = values[values.length - 1];
+        } else {
+            mEndValue = values[0];
+        }
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setStiffness(
+            @FloatRange(from = 0.0, fromInclusive = false) float stiffness) {
+        if (stiffness <= 0) {
+            throw new IllegalArgumentException("Spring stiffness constant must be positive.");
+        }
+        mStiffness = stiffness;
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setDampingRatio(
+            @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false)
+                    float dampingRatio) {
+        if (dampingRatio <= 0 || dampingRatio >= 1) {
+            throw new IllegalArgumentException("Damping ratio must be between 0 and 1");
+        }
+        mDampingRatio = dampingRatio;
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setMinimumVisibleChange(
+            @FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) {
+        if (minimumVisibleChange <= 0) {
+            throw new IllegalArgumentException("Minimum visible change must be positive.");
+        }
+        mMinVisibleChange = minimumVisibleChange;
+        return this;
+    }
+
+    public SpringAnimationBuilder<T> setStartVelocity(float startVelocity) {
+        mVelocity = startVelocity;
+        return this;
+    }
+
+    @Override
+    public void setValue(T object, float time) {
+        mCurrentTime = time;
+        mProperty.setValue(
+                object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue);
+    }
+
+    @Override
+    public Float get(T t) {
+        return mCurrentTime;
+    }
+
+    public ObjectAnimator build(Context context) {
+        int singleFrameMs = DefaultDisplay.getSingleFrameMs(context);
+        double naturalFreq = Math.sqrt(mStiffness);
+        double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
+
+        // All the calculations assume the stable position to be 0, shift the values accordingly.
+        beta = 2 * mDampingRatio * naturalFreq;
+        gamma = dampedFreq;
+        a =  mStartValue - mEndValue;
+        b = beta * a / (2 * gamma) + mVelocity / gamma;
+
+        va = a * beta / 2 - b * gamma;
+        vb = a * gamma + beta * b / 2;
+
+        mValueThreshold = mMinVisibleChange * THRESHOLD_MULTIPLIER;
+
+        // This multiplier is used to calculate the velocity threshold given a certain value
+        // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
+        // then the velocity is a reasonable threshold.
+        mVelocityThreshold = mValueThreshold * 1000.0 / singleFrameMs;
+
+        // Find the duration (in seconds) for the spring to reach equilibrium.
+        // equilibrium is reached when x = 0
+        double duration = Math.atan2(-a, b) / gamma;
+
+        // Keep moving ahead until the velocity reaches equilibrium.
+        double piByG = Math.PI / gamma;
+        while (duration < 0 || Math.abs(exponentialComponent(duration) * cosSinV(duration))
+                >= mVelocityThreshold) {
+            duration += piByG;
+        }
+
+        // Find the shortest time
+        double edgeTime = Math.max(0, duration - piByG / 2);
+        double minDiff = singleFrameMs / 2000.0;    // Half frame time in seconds
+
+        do {
+            if ((duration - edgeTime) < minDiff) {
+                break;
+            }
+            double mid = (edgeTime + duration) / 2;
+            if (isAtEquilibrium(mid)) {
+                duration = mid;
+            } else {
+                edgeTime = mid;
+            }
+        } while (true);
+
+
+        long durationMs = (long) (1000.0 * duration);
+        ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration);
+        animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR);
+        animator.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mProperty.setValue(mTarget, mEndValue);
+            }
+        });
+        return animator;
+    }
+
+    private boolean isAtEquilibrium(double t) {
+        double ec = exponentialComponent(t);
+
+        if (Math.abs(ec * cosSinX(t)) >= mValueThreshold) {
+            return false;
+        }
+        return Math.abs(ec * cosSinV(t)) < mVelocityThreshold;
+    }
+
+    private double exponentialComponent(double t) {
+        return Math.pow(Math.E, - beta * t / 2);
+    }
+
+    private double cosSinX(double t) {
+        return cosSin(t, a, b);
+    }
+
+    private double cosSinV(double t) {
+        return cosSin(t, va, vb);
+    }
+
+    private double cosSin(double t, double cosFactor, double sinFactor) {
+        double angle = t * gamma;
+        return cosFactor * Math.cos(angle) + sinFactor * Math.sin(angle);
+    }
+}
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 81c95cb..d47a40e 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -21,12 +21,9 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
 
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.Utilities;
-
-import java.util.function.Consumer;
+import com.android.launcher3.testing.TestProtocol;
 
 public class AccessibilityManagerCompat {
 
@@ -103,24 +100,6 @@
         return accessibilityManager;
     }
 
-    public static boolean processTestRequest(Context context, String eventTag, int action,
-            Bundle request, Consumer<Bundle> responseFiller) {
-        final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
-        if (accessibilityManager == null) return false;
-
-        // The test sends a request via a ACTION_SET_TEXT.
-        if (action == AccessibilityNodeInfo.ACTION_SET_TEXT &&
-                eventTag.equals(request.getCharSequence(
-                        AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE))) {
-            final Bundle response = new Bundle();
-            responseFiller.accept(response);
-            AccessibilityManagerCompat.sendEventToTest(
-                    accessibilityManager, eventTag + TestProtocol.RESPONSE_MESSAGE_POSTFIX, response);
-            return true;
-        }
-        return false;
-    }
-
     public static int getRecommendedTimeoutMillis(Context context, int originalTimeout, int flags) {
         if (Utilities.ATLEAST_Q) {
             return getManager(context).getRecommendedTimeoutMillis(originalTimeout, flags);
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index c8b1f67..8f6500b 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -28,7 +28,7 @@
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo;
@@ -51,7 +51,7 @@
 
     @Override
     public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return Collections.emptyList();
         }
         if (packageUser == null) {
@@ -82,7 +82,7 @@
     @Override
     public boolean bindAppWidgetIdIfAllowed(int appWidgetId, AppWidgetProviderInfo info,
             Bundle options) {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return false;
         }
         if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
@@ -94,7 +94,7 @@
 
     @Override
     public LauncherAppWidgetProviderInfo findProvider(ComponentName provider, UserHandle user) {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return null;
         }
         for (AppWidgetProviderInfo info :
@@ -117,7 +117,7 @@
     @Override
     public HashMap<ComponentKey, AppWidgetProviderInfo> getAllProvidersMap() {
         HashMap<ComponentKey, AppWidgetProviderInfo> result = new HashMap<>();
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return result;
         }
         for (UserHandle user : mUserManager.getUserProfiles()) {
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
index 11ec333..2814afc 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -21,7 +21,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.Collections;
@@ -35,7 +35,7 @@
 
     @Override
     public List<AppWidgetProviderInfo> getAllProviders(@Nullable PackageUserKey packageUser) {
-        if (FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
             return Collections.emptyList();
         }
         if (packageUser == null) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
deleted file mode 100644
index cdb5c4d..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.List;
-
-public abstract class LauncherAppsCompat {
-
-    public interface OnAppsChangedCallbackCompat {
-        default void onPackageRemoved(String packageName, UserHandle user) { }
-        default void onPackageAdded(String packageName, UserHandle user) { }
-        default void onPackageChanged(String packageName, UserHandle user) { }
-        default void onPackagesAvailable(String[] packageNames, UserHandle user,
-                boolean replacing) { }
-        default void onPackagesUnavailable(String[] packageNames, UserHandle user,
-                boolean replacing) { }
-        default void onPackagesSuspended(String[] packageNames, UserHandle user) { }
-        default void onPackagesUnsuspended(String[] packageNames, UserHandle user) { }
-        default void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
-                UserHandle user) { }
-    }
-
-    protected LauncherAppsCompat() {
-    }
-
-    private static LauncherAppsCompat sInstance;
-    private static final Object sInstanceLock = new Object();
-
-    public static LauncherAppsCompat getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                if (Utilities.ATLEAST_Q) {
-                    sInstance = new LauncherAppsCompatVQ(context.getApplicationContext());
-                } else if (Utilities.ATLEAST_OREO) {
-                    sInstance = new LauncherAppsCompatVO(context.getApplicationContext());
-                } else {
-                    sInstance = new LauncherAppsCompatVL(context.getApplicationContext());
-                }
-            }
-            return sInstance;
-        }
-    }
-
-    public abstract List<LauncherActivityInfo> getActivityList(String packageName,
-            UserHandle user);
-    public abstract LauncherActivityInfo resolveActivity(Intent intent,
-            UserHandle user);
-    public abstract void startActivityForProfile(ComponentName component, UserHandle user,
-            Rect sourceBounds, Bundle opts);
-    public abstract ApplicationInfo getApplicationInfo(
-            String packageName, int flags, UserHandle user);
-    public abstract void showAppDetailsForProfile(ComponentName component, UserHandle user,
-            Rect sourceBounds, Bundle opts);
-    public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
-    public abstract void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
-    public abstract boolean isPackageEnabledForProfile(String packageName, UserHandle user);
-    public abstract boolean isActivityEnabledForProfile(ComponentName component,
-            UserHandle user);
-    public abstract List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
-            @Nullable PackageUserKey packageUser);
-
-    public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
deleted file mode 100644
index 1885d8f..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class LauncherAppsCompatVL extends LauncherAppsCompat {
-
-    protected final LauncherApps mLauncherApps;
-    protected final Context mContext;
-
-    private final ArrayMap<OnAppsChangedCallbackCompat, WrappedCallback> mCallbacks =
-        new ArrayMap<>();
-
-    LauncherAppsCompatVL(Context context) {
-        mContext = context;
-        mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
-    }
-
-    @Override
-    public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
-        return mLauncherApps.getActivityList(packageName, user);
-    }
-
-    @Override
-    public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
-        return mLauncherApps.resolveActivity(intent, user);
-    }
-
-    @Override
-    public void startActivityForProfile(ComponentName component, UserHandle user,
-            Rect sourceBounds, Bundle opts) {
-        mLauncherApps.startMainActivity(component, user, sourceBounds, opts);
-    }
-
-    @Override
-    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
-        final boolean isPrimaryUser = Process.myUserHandle().equals(user);
-        if (!isPrimaryUser && (flags == 0)) {
-            // We are looking for an installed app on a secondary profile. Prior to O, the only
-            // entry point for work profiles is through the LauncherActivity.
-            List<LauncherActivityInfo> activityList =
-                    mLauncherApps.getActivityList(packageName, user);
-            return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
-        }
-        try {
-            ApplicationInfo info =
-                    mContext.getPackageManager().getApplicationInfo(packageName, flags);
-            // There is no way to check if the app is installed for managed profile. But for
-            // primary profile, we can still have this check.
-            if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
-                    || !info.enabled) {
-                return null;
-            }
-            return info;
-        } catch (PackageManager.NameNotFoundException e) {
-            // Package not found
-            return null;
-        }
-    }
-
-    @Override
-    public void showAppDetailsForProfile(ComponentName component, UserHandle user,
-            Rect sourceBounds, Bundle opts) {
-        mLauncherApps.startAppDetailsActivity(component, user, sourceBounds, opts);
-    }
-
-    @Override
-    public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
-        WrappedCallback wrappedCallback = new WrappedCallback(callback);
-        synchronized (mCallbacks) {
-            mCallbacks.put(callback, wrappedCallback);
-        }
-        mLauncherApps.registerCallback(wrappedCallback);
-    }
-
-    @Override
-    public void removeOnAppsChangedCallback(OnAppsChangedCallbackCompat callback) {
-        final WrappedCallback wrappedCallback;
-        synchronized (mCallbacks) {
-            wrappedCallback = mCallbacks.remove(callback);
-        }
-        if (wrappedCallback != null) {
-            mLauncherApps.unregisterCallback(wrappedCallback);
-        }
-    }
-
-    @Override
-    public boolean isPackageEnabledForProfile(String packageName, UserHandle user) {
-        return mLauncherApps.isPackageEnabled(packageName, user);
-    }
-
-    @Override
-    public boolean isActivityEnabledForProfile(ComponentName component, UserHandle user) {
-        return mLauncherApps.isActivityEnabled(component, user);
-    }
-
-    private static class WrappedCallback extends LauncherApps.Callback {
-        private final LauncherAppsCompat.OnAppsChangedCallbackCompat mCallback;
-
-        public WrappedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onPackageRemoved(String packageName, UserHandle user) {
-            mCallback.onPackageRemoved(packageName, user);
-        }
-
-        @Override
-        public void onPackageAdded(String packageName, UserHandle user) {
-            mCallback.onPackageAdded(packageName, user);
-        }
-
-        @Override
-        public void onPackageChanged(String packageName, UserHandle user) {
-            mCallback.onPackageChanged(packageName, user);
-        }
-
-        @Override
-        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
-            mCallback.onPackagesAvailable(packageNames, user, replacing);
-        }
-
-        @Override
-        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-                boolean replacing) {
-            mCallback.onPackagesUnavailable(packageNames, user, replacing);
-        }
-
-        @Override
-        public void onPackagesSuspended(String[] packageNames, UserHandle user) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.APP_NOT_DISABLED, "onPackagesSuspended: " +
-                        Arrays.toString(packageNames));
-            }
-            mCallback.onPackagesSuspended(packageNames, user);
-        }
-
-        @Override
-        public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
-            mCallback.onPackagesUnsuspended(packageNames, user);
-        }
-
-        @Override
-        public void onShortcutsChanged(@NonNull String packageName,
-            @NonNull List<ShortcutInfo> shortcuts,
-            @NonNull UserHandle user) {
-            mCallback.onShortcutsChanged(packageName, shortcuts, user);
-        }
-    }
-
-    @Override
-    public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
-            @Nullable PackageUserKey packageUser) {
-        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
-        if (packageUser != null && !packageUser.mUser.equals(Process.myUserHandle())) {
-            return result;
-        }
-        PackageManager pm = mContext.getPackageManager();
-        for (ResolveInfo info :
-                pm.queryIntentActivities(new Intent(Intent.ACTION_CREATE_SHORTCUT), 0)) {
-            if (packageUser == null || packageUser.mPackageName
-                    .equals(info.activityInfo.packageName)) {
-                result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
-        return mContext.getPackageManager().getPackageInstaller().getAllSessions();
-    }
-}
-
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
deleted file mode 100644
index 5e13d00..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ /dev/null
@@ -1,156 +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.compat;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.PinItemRequest;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.os.Build;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.util.PackageUserKey;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@TargetApi(26)
-public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
-
-    LauncherAppsCompatVO(Context context) {
-        super(context);
-    }
-
-    @Override
-    public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) {
-        try {
-            ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
-            return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
-                    ? null : info;
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    @Override
-    public List<ShortcutConfigActivityInfo> getCustomShortcutActivityList(
-            @Nullable PackageUserKey packageUser) {
-        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
-        UserHandle myUser = Process.myUserHandle();
-
-        final List<UserHandle> users;
-        final String packageName;
-        if (packageUser == null) {
-            users = UserManagerCompat.getInstance(mContext).getUserProfiles();
-            packageName = null;
-        } else {
-            users = new ArrayList<>(1);
-            users.add(packageUser.mUser);
-            packageName = packageUser.mPackageName;
-        }
-        for (UserHandle user : users) {
-            boolean ignoreTargetSdk = myUser.equals(user);
-            List<LauncherActivityInfo> activities =
-                    mLauncherApps.getShortcutConfigActivityList(packageName, user);
-            for (LauncherActivityInfo activityInfo : activities) {
-                if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion >=
-                        Build.VERSION_CODES.O) {
-                    result.add(new ShortcutConfigActivityInfoVO(activityInfo));
-                }
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * request.accept() will initiate the following flow:
-     *      -> go-to-system-process for actual processing (a)
-     *      -> callback-to-launcher on UI thread (b)
-     *      -> post callback on the worker thread (c)
-     *      -> Update model and unpin (in system) any shortcut not in out model. (d)
-     *
-     * Note that (b) will take at-least one frame as it involves posting callback from binder
-     * thread to UI thread.
-     * If (d) happens before we add this shortcut to our model, we will end up unpinning
-     * the shortcut in the system.
-     * Here its the caller's responsibility to add the newly created WorkspaceItemInfo immediately
-     * to the model (which may involves a single post-to-worker-thread). That will guarantee
-     * that (d) happens after model is updated.
-     */
-    @Nullable
-    public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
-            Context context, final PinItemRequest request, final long acceptDelay) {
-        if (request != null &&
-                request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT &&
-                request.isValid()) {
-
-            if (acceptDelay <= 0) {
-                if (!request.accept()) {
-                    return null;
-                }
-            } else {
-                // Block the worker thread until the accept() is called.
-                MODEL_EXECUTOR.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            Thread.sleep(acceptDelay);
-                        } catch (InterruptedException e) {
-                            // Ignore
-                        }
-                        if (request.isValid()) {
-                            request.accept();
-                        }
-                    }
-                });
-            }
-
-            ShortcutInfo si = request.getShortcutInfo();
-            WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
-            // Apply the unbadged icon and fetch the actual icon asynchronously.
-            LauncherIcons li = LauncherIcons.obtain(context);
-            info.applyFrom(li.createShortcutIcon(si, false /* badged */));
-            li.recycle();
-            LauncherAppState.getInstance(context).getModel()
-                    .updateAndBindWorkspaceItem(info, si);
-            return info;
-        } else {
-            return null;
-        }
-    }
-
-    public static PinItemRequest getPinItemRequest(Intent intent) {
-        Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
-        return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
-    }
-}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
deleted file mode 100644
index 0a1811e..0000000
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
+++ /dev/null
@@ -1,36 +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.compat;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-
-import java.util.List;
-
-@TargetApi(29)
-public class LauncherAppsCompatVQ extends LauncherAppsCompatVO {
-
-    LauncherAppsCompatVQ(Context context) {
-        super(context);
-    }
-
-    public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
-        return mLauncherApps.getAllPackageInstallerSessions();
-    }
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
deleted file mode 100644
index 11cb1f8..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.os.Process;
-import android.os.UserHandle;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Utilities;
-
-public abstract class PackageInstallerCompat {
-
-    // Set<String> of session ids of promise icons that have been added to the home screen
-    // as FLAG_PROMISE_NEW_INSTALLS.
-    protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
-
-    public static final int STATUS_INSTALLED = 0;
-    public static final int STATUS_INSTALLING = 1;
-    public static final int STATUS_FAILED = 2;
-
-    private static final Object sInstanceLock = new Object();
-    private static PackageInstallerCompat sInstance;
-
-    public static PackageInstallerCompat getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                sInstance = new PackageInstallerCompatVL(context);
-            }
-            return sInstance;
-        }
-    }
-
-    public static UserHandle getUserHandle(PackageInstaller.SessionInfo info) {
-        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
-    }
-
-    /**
-     * @return a map of active installs to their progress
-     */
-    public abstract HashMap<String, PackageInstaller.SessionInfo> updateAndGetActiveSessionCache();
-
-    /**
-     * @return an active SessionInfo for {@param pkg} or null if none exists.
-     */
-    public abstract PackageInstaller.SessionInfo getActiveSessionInfo(UserHandle user, String pkg);
-
-    public abstract void onStop();
-
-    public static final class PackageInstallInfo {
-        public final ComponentName componentName;
-        public final String packageName;
-        public final int state;
-        public final int progress;
-        public final UserHandle user;
-
-        private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) {
-            this.state = STATUS_INSTALLING;
-            this.packageName = info.getAppPackageName();
-            this.componentName = new ComponentName(packageName, "");
-            this.progress = (int) (info.getProgress() * 100f);
-            this.user = getUserHandle(info);
-        }
-
-        public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) {
-            this.state = state;
-            this.packageName = packageName;
-            this.componentName = new ComponentName(packageName, "");
-            this.progress = progress;
-            this.user = user;
-        }
-
-        public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) {
-            return new PackageInstallInfo(info);
-        }
-
-        public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
-            return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
-        }
-
-    }
-
-    public abstract List<PackageInstaller.SessionInfo> getAllVerifiedSessions();
-
-    /**
-     * Returns true if a promise icon was already added to the home screen for {@param sessionId}.
-     * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
-     */
-    public abstract boolean promiseIconAddedForId(int sessionId);
-
-    /**
-     * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS.
-     */
-    public abstract void removePromiseIconId(int sessionId);
-}
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
deleted file mode 100644
index c5a1bcc..0000000
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2014 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.compat;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionCallback;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.SessionCommitReceiver;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-import static com.android.launcher3.Utilities.getPrefs;
-
-public class PackageInstallerCompatVL extends PackageInstallerCompat {
-
-    private static final boolean DEBUG = false;
-
-    @Thunk final SparseArray<PackageUserKey> mActiveSessions = new SparseArray<>();
-
-    @Thunk final PackageInstaller mInstaller;
-    private final IconCache mCache;
-    private final Context mAppContext;
-    private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>();
-    private final LauncherAppsCompat mLauncherApps;
-    private final IntSet mPromiseIconIds;
-
-    PackageInstallerCompatVL(Context context) {
-        mAppContext = context.getApplicationContext();
-        mInstaller = context.getPackageManager().getPackageInstaller();
-        mCache = LauncherAppState.getInstance(context).getIconCache();
-        mInstaller.registerSessionCallback(mCallback, MODEL_EXECUTOR.getHandler());
-        mLauncherApps = LauncherAppsCompat.getInstance(context);
-        mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
-                getPrefs(context).getString(PROMISE_ICON_IDS, "")));
-
-        cleanUpPromiseIconIds();
-    }
-
-    private void cleanUpPromiseIconIds() {
-        IntArray existingIds = new IntArray();
-        for (SessionInfo info : updateAndGetActiveSessionCache().values()) {
-            existingIds.add(info.getSessionId());
-        }
-        IntArray idsToRemove = new IntArray();
-
-        for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
-            if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
-                idsToRemove.add(mPromiseIconIds.getArray().get(i));
-            }
-        }
-        for (int i = idsToRemove.size() - 1; i >= 0; --i) {
-            mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
-        }
-    }
-
-    @Override
-    public HashMap<String, SessionInfo> updateAndGetActiveSessionCache() {
-        HashMap<String, SessionInfo> activePackages = new HashMap<>();
-        for (SessionInfo info : getAllVerifiedSessions()) {
-            addSessionInfoToCache(info, getUserHandle(info));
-            if (info.getAppPackageName() != null) {
-                activePackages.put(info.getAppPackageName(), info);
-                mActiveSessions.put(info.getSessionId(),
-                        new PackageUserKey(info.getAppPackageName(), getUserHandle(info)));
-            }
-        }
-        return activePackages;
-    }
-
-    public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
-        for (SessionInfo info : getAllVerifiedSessions()) {
-            boolean match = pkg.equals(info.getAppPackageName());
-            if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
-                match = false;
-            }
-            if (match) {
-                return info;
-            }
-        }
-        return null;
-    }
-
-    @Thunk void addSessionInfoToCache(SessionInfo info, UserHandle user) {
-        String packageName = info.getAppPackageName();
-        if (packageName != null) {
-            mCache.cachePackageInstallInfo(packageName, user, info.getAppIcon(),
-                    info.getAppLabel());
-        }
-    }
-
-    @Override
-    public void onStop() {
-        mInstaller.unregisterSessionCallback(mCallback);
-    }
-
-    @Thunk void sendUpdate(PackageInstallInfo info) {
-        LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-        if (app != null) {
-            app.getModel().setPackageState(info);
-        }
-    }
-
-    /**
-     * Add a promise app icon to the workspace iff:
-     * - The settings for it are enabled
-     * - The user installed the app
-     * - There is an app icon and label (For apps with no launching activity, no icon is provided).
-     */
-    private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) {
-        if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
-                && SessionCommitReceiver.isEnabled(mAppContext)
-                && verify(sessionInfo) != null
-                && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
-                && sessionInfo.getAppIcon() != null
-                && !TextUtils.isEmpty(sessionInfo.getAppLabel())
-                && !mPromiseIconIds.contains(sessionInfo.getSessionId())) {
-            SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
-            mPromiseIconIds.add(sessionInfo.getSessionId());
-            updatePromiseIconPrefs();
-        }
-    }
-
-    private final SessionCallback mCallback = new SessionCallback() {
-
-        @Override
-        public void onCreated(int sessionId) {
-            SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
-            if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS && sessionInfo != null) {
-                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-                if (app != null) {
-                    app.getModel().onInstallSessionCreated(
-                            PackageInstallInfo.fromInstallingState(sessionInfo));
-                }
-            }
-
-            tryQueuePromiseAppIcon(sessionInfo);
-        }
-
-        @Override
-        public void onFinished(int sessionId, boolean success) {
-            // For a finished session, we can't get the session info. So use the
-            // packageName from our local cache.
-            PackageUserKey key = mActiveSessions.get(sessionId);
-            mActiveSessions.remove(sessionId);
-
-            if (key != null && key.mPackageName != null) {
-                String packageName = key.mPackageName;
-                sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED,
-                        packageName, key.mUser));
-
-                if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()) {
-                    LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
-                    if (appState != null) {
-                        LauncherModel model = appState.getModel();
-                        model.onPackageRemoved(packageName, key.mUser);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onProgressChanged(int sessionId, float progress) {
-            SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
-            if (session != null && session.getAppPackageName() != null) {
-                sendUpdate(PackageInstallInfo.fromInstallingState(session));
-            }
-        }
-
-        @Override
-        public void onActiveChanged(int sessionId, boolean active) { }
-
-        @Override
-        public void onBadgingChanged(int sessionId) {
-            SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
-            if (sessionInfo != null) {
-                tryQueuePromiseAppIcon(sessionInfo);
-            }
-        }
-
-        private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
-            SessionInfo session = verify(mInstaller.getSessionInfo(sessionId));
-            if (session != null && session.getAppPackageName() != null) {
-                mActiveSessions.put(session.getSessionId(),
-                        new PackageUserKey(session.getAppPackageName(), getUserHandle(session)));
-                addSessionInfoToCache(session, getUserHandle(session));
-                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
-                if (app != null) {
-                    app.getModel().updateSessionDisplayInfo(session.getAppPackageName());
-                }
-                return session;
-            }
-            return null;
-        }
-    };
-
-    private PackageInstaller.SessionInfo verify(PackageInstaller.SessionInfo sessionInfo) {
-        if (sessionInfo == null
-                || sessionInfo.getInstallerPackageName() == null
-                || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
-            return null;
-        }
-        String pkg = sessionInfo.getInstallerPackageName();
-        synchronized (mSessionVerifiedMap) {
-            if (!mSessionVerifiedMap.containsKey(pkg)) {
-                LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext);
-                boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg,
-                        ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null;
-                mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
-            }
-        }
-        return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
-    }
-
-    @Override
-    public List<SessionInfo> getAllVerifiedSessions() {
-        List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
-                ? mLauncherApps.getAllPackageInstallerSessions()
-                : mInstaller.getAllSessions());
-        Iterator<SessionInfo> it = list.iterator();
-        while (it.hasNext()) {
-            if (verify(it.next()) == null) {
-                it.remove();
-            }
-        }
-        return list;
-    }
-
-    @Override
-    public boolean promiseIconAddedForId(int sessionId) {
-        return mPromiseIconIds.contains(sessionId);
-    }
-
-    @Override
-    public void removePromiseIconId(int sessionId) {
-        if (mPromiseIconIds.contains(sessionId)) {
-            mPromiseIconIds.getArray().removeValue(sessionId);
-            updatePromiseIconPrefs();
-        }
-    }
-
-    private void updatePromiseIconPrefs() {
-        getPrefs(mAppContext).edit()
-                .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
-                .apply();
-    }
-}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
similarity index 74%
rename from src/com/android/launcher3/config/BaseFlags.java
rename to src/com/android/launcher3/config/FeatureFlags.java
index 46243f7..4abdbef 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -25,6 +25,7 @@
 import androidx.annotation.Keep;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.TogglableFlag;
 
@@ -39,7 +40,7 @@
  * <p>All the flags should be defined here with appropriate default values.
  */
 @Keep
-public abstract class BaseFlags {
+public final class FeatureFlags {
 
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
@@ -47,46 +48,47 @@
 
     static final String FLAGS_PREF_NAME = "featureFlags";
 
-    BaseFlags() {
-        throw new UnsupportedOperationException("Don't instantiate BaseFlags");
-    }
+    private FeatureFlags() { }
 
     public static boolean showFlagTogglerUi(Context context) {
         return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
     }
 
-    public static final boolean IS_DOGFOOD_BUILD = false;
+    public static final boolean IS_DOGFOOD_BUILD = BuildConfig.DEBUG;
 
+    /**
+     * Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature
+     * and should be modified at a project level.
+     */
+    public static final boolean QSB_ON_FIRST_SCREEN = true;
+
+
+    /**
+     * Feature flag to handle define config changes dynamically instead of killing the process.
+     *
+     *
+     * To add a new flag that can be toggled through the flags UI:
+     *
+     * 1. Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
+     *    and set a default value for the flag. This will be the default value on Debug builds.
+     *
+     * 2. Add your flag to mTogglableFlags.
+     *
+     * 3. Create a getter method (an 'is' method) for the flag by copying an existing one.
+     *
+     * 4. Create a getter method with the same name in the release flags copy of FeatureFlags.java.
+     *    This should returns a constant (true/false). This will be the value of the flag used on
+     *    release builds.
+     */
     // When enabled the promise icon is visible in all apps while installation an app.
-    public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
+    public static final TogglableFlag PROMISE_APPS_IN_ALL_APPS = new TogglableFlag(
+            "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");
 
     // When enabled a promise icon is added to the home screen when install session is active.
     public static final TogglableFlag PROMISE_APPS_NEW_INSTALLS =
             new TogglableFlag("PROMISE_APPS_NEW_INSTALLS", true,
                     "Adds a promise icon to the home screen for new install sessions.");
 
-    // Enable moving the QSB on the 0th screen of the workspace
-    public static final boolean QSB_ON_FIRST_SCREEN = true;
-
-    public static final TogglableFlag EXAMPLE_FLAG = new TogglableFlag("EXAMPLE_FLAG", true,
-            "An example flag that doesn't do anything. Useful for testing");
-
-    //Feature flag to enable pulling down navigation shade from workspace.
-    public static final boolean PULL_DOWN_STATUS_BAR = true;
-
-    // Features to control Launcher3Go behavior
-    public static final boolean GO_DISABLE_WIDGETS = false;
-
-    // When enabled shows a work profile tab in all apps
-    public static final boolean ALL_APPS_TABS_ENABLED = true;
-
-    // When true, overview shows screenshots in the orientation they were taken rather than
-    // trying to make them fit the orientation the device is in.
-    public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
-
-    /**
-     * Feature flag to handle define config changes dynamically instead of killing the process.
-     */
     public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
             "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
 
@@ -115,6 +117,8 @@
     public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag(
             "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
+    public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag(
+            "ENABLE_QUICK_CAPTURE_GESTURE", false, "Swipe from right to left to quick capture");
 
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
@@ -130,13 +134,13 @@
     static List<TogglableFlag> getTogglableFlags() {
         // By Java Language Spec 12.4.2
         // https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2, the
-        // TogglableFlag instances on BaseFlags will be created before those on the FeatureFlags
+        // TogglableFlag instances on FeatureFlags will be created before those on the FeatureFlags
         // subclass. This code handles flags that are redeclared in FeatureFlags, ensuring the
         // FeatureFlags one takes priority.
         SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
         synchronized (sLock) {
             for (TogglableFlag flag : sFlags) {
-                flagsByKey.put(((BaseTogglableFlag) flag).getKey(), flag);
+                flagsByKey.put(flag.getKey(), flag);
             }
         }
         return new ArrayList<>(flagsByKey.values());
@@ -144,6 +148,8 @@
 
     public static abstract class BaseTogglableFlag {
         private final String key;
+        // should be value that is hardcoded in client side.
+        // Comparatively, getDefaultValue() can be overridden.
         private final boolean defaultValue;
         private final String description;
         private boolean currentValue;
@@ -153,8 +159,9 @@
                 boolean defaultValue,
                 String description) {
             this.key = checkNotNull(key);
-            this.currentValue = this.defaultValue = getInitialValue(defaultValue);
+            this.currentValue = this.defaultValue = defaultValue;
             this.description = checkNotNull(description);
+
             synchronized (sLock) {
                 sFlags.add((TogglableFlag)this);
             }
@@ -170,16 +177,18 @@
             return key;
         }
 
-        void initialize(Context context) {
-            currentValue = getFromStorage(context, defaultValue);
+        protected void initialize(Context context) {
+            currentValue = getFromStorage(context, getDefaultValue());
         }
 
-        protected abstract boolean getInitialValue(boolean value);
+        protected abstract boolean getOverridenDefaultValue(boolean value);
+
+        protected abstract void addChangeListener(Context context, Runnable r);
 
         public void updateStorage(Context context, boolean value) {
             SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME,
                     Context.MODE_PRIVATE).edit();
-            if (value == defaultValue) {
+            if (value == getDefaultValue()) {
                 editor.remove(key).apply();
             } else {
                 editor.putBoolean(key, value).apply();
@@ -188,11 +197,11 @@
 
         boolean getFromStorage(Context context, boolean defaultValue) {
             return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
-                    .getBoolean(key, defaultValue);
+                    .getBoolean(key, getDefaultValue());
         }
 
         boolean getDefaultValue() {
-            return defaultValue;
+            return getOverridenDefaultValue(defaultValue);
         }
 
         /** Returns the value of the flag at process start, including any overrides present. */
@@ -209,6 +218,8 @@
             return "TogglableFlag{"
                     + "key=" + key + ", "
                     + "defaultValue=" + defaultValue + ", "
+                    + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", "
+                    + "currentValue=" + currentValue + ", "
                     + "description=" + description
                     + "}";
         }
@@ -221,7 +232,7 @@
             if (o instanceof TogglableFlag) {
                 BaseTogglableFlag that = (BaseTogglableFlag) o;
                 return (this.key.equals(that.getKey()))
-                        && (this.defaultValue == that.getDefaultValue())
+                        && (this.getDefaultValue() == that.getDefaultValue())
                         && (this.description.equals(that.getDescription()));
             }
             return false;
@@ -229,14 +240,7 @@
 
         @Override
         public int hashCode() {
-            int h$ = 1;
-            h$ *= 1000003;
-            h$ ^= key.hashCode();
-            h$ *= 1000003;
-            h$ ^= defaultValue ? 1231 : 1237;
-            h$ *= 1000003;
-            h$ ^= description.hashCode();
-            return h$;
+            return key.hashCode();
         }
     }
 }
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
index 54e5322..200938d 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -26,13 +26,13 @@
 import android.widget.Toast;
 
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag;
+import com.android.launcher3.uioverrides.TogglableFlag;
 
 import androidx.preference.PreferenceDataStore;
 import androidx.preference.PreferenceFragment;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.SwitchPreference;
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
-import com.android.launcher3.uioverrides.TogglableFlag;
 
 /**
  * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}.
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 9fb1090..1b0567f 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -45,13 +45,14 @@
 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;
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
@@ -92,7 +93,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mRequest = LauncherAppsCompatVO.getPinItemRequest(getIntent());
+        mRequest = PinRequestHelper.getPinItemRequest(getIntent());
         if (mRequest == null) {
             finish();
             return;
@@ -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/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index bd2a03b..01e0f92 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -61,6 +61,9 @@
                 mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_CANCEL:
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL");
+                }
                 mEventListener.onDriverDragCancel();
                 break;
         }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index b59164a..cdc7061 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -284,7 +284,8 @@
             // The child may be scaled (always about the center of the view) so to account for it,
             // we have to offset the position by the scaled size.  Once we do that, we can center
             // the drag view about the scaled child view.
-            toY += Math.round(toScale * tv.getPaddingTop());
+            // padding will remain constant (does not scale with size)
+            toY += tv.getPaddingTop();
             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
             if (dragView.getDragVisualizeOffset() != null) {
                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 589ad25..06b5c40 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -106,6 +106,7 @@
      * @return the vector at which the item was flung, or null if no fling was detected.
      */
     private PointF isFlingingToDelete() {
+        if (mVelocityTracker == null) return null;
         if (mDropTarget == null) {
             mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
         }
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 91a31aa..09062a4 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -29,14 +29,14 @@
 import android.os.Process;
 
 import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.compat.LauncherAppsCompatVO;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 
 /**
  * Extension of ShortcutConfigActivityInfo to be used in the confirmation prompt for pin item
@@ -88,7 +88,7 @@
                 LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY +
                 LauncherAnimUtils.SPRING_LOADED_TRANSITION_MS;
         // Delay the actual accept() call until the drop animation is complete.
-        return LauncherAppsCompatVO.createWorkspaceItemFromPinItemRequest(
+        return PinRequestHelper.createWorkspaceItemFromPinItemRequest(
                 mContext, mRequest, duration);
     }
 
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f9f8683..65d593c 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -170,8 +170,6 @@
     private boolean mDeleteFolderOnDropCompleted = false;
     private boolean mSuppressFolderDeletion = false;
     private boolean mItemAddedBackToSelfViaIcon = false;
-    @Thunk float mFolderIconPivotX;
-    @Thunk float mFolderIconPivotY;
     private boolean mIsEditingName = false;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -310,7 +308,8 @@
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
         String newTitle = mFolderName.getText().toString();
-        mInfo.setTitle(newTitle);
+        mInfo.title = newTitle;
+        mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
         if (TextUtils.isEmpty(mInfo.title)) {
@@ -385,7 +384,6 @@
         ArrayList<WorkspaceItemInfo> children = info.contents;
         Collections.sort(children, ITEM_POS_COMPARATOR);
         updateItemLocationsInDatabaseBatch();
-        mContent.bindItems(children);
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
         if (lp == null) {
@@ -393,13 +391,10 @@
             lp.customPosition = true;
             setLayoutParams(lp);
         }
-        centerAboutIcon();
-
         mItemsInvalidated = true;
-        updateTextViewFocus();
         mInfo.addListener(this);
 
-        if (TextUtils.isEmpty(mInfo.title)) {
+        if (!TextUtils.isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
             mFolderName.setHint(null);
         } else {
@@ -408,11 +403,9 @@
         }
 
         // In case any children didn't come across during loading, clean up the folder accordingly
-        mFolderIcon.post(new Runnable() {
-            public void run() {
-                if (getItemCount() <= 1) {
-                    replaceFolderWithFinalItem();
-                }
+        mFolderIcon.post(() -> {
+            if (getItemCount() <= 1) {
+                replaceFolderWithFinalItem();
             }
         });
     }
@@ -473,17 +466,49 @@
     }
 
     /**
+     * Opens the folder as part of a drag operation
+     */
+    public void beginExternalDrag() {
+        mIsExternalDrag = true;
+        mDragInProgress = true;
+
+        // Since this folder opened by another controller, it might not get onDrop or
+        // onDropComplete. Perform cleanup once drag-n-drop ends.
+        mDragController.addDragListener(this);
+
+        ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
+        mEmptyCellRank = items.size();
+        items.add(null);    // Add an empty spot at the end
+
+        animateOpen(items, mEmptyCellRank / mContent.itemsPerPage());
+    }
+
+    /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
      * is played.
      */
     public void animateOpen() {
+        animateOpen(mInfo.contents, 0);
+    }
+
+    /**
+     * Opens the user folder described by the specified tag. The opening of the folder
+     * is animated relative to the specified View. If the View is null, no animation
+     * is played.
+     */
+    private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
         Folder openFolder = getOpen(mLauncher);
         if (openFolder != null && openFolder != this) {
             // Close any open folder before opening a folder.
             openFolder.close(true);
         }
 
+        mContent.bindItems(items);
+        centerAboutIcon();
+        mItemsInvalidated = true;
+        updateTextViewFocus();
+
         mIsOpen = true;
 
         DragLayer dragLayer = mLauncher.getDragLayer();
@@ -500,10 +525,7 @@
         }
 
         mContent.completePendingPageChanges();
-        if (!mDragInProgress) {
-            // Open on the first page.
-            mContent.snapToPageImmediately(0);
-        }
+        mContent.snapToPageImmediately(pageNo);
 
         // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
         // leads to an inconsistent state if you drag out of the folder and drag back in without
@@ -574,16 +596,6 @@
         mContent.verifyVisibleHighResIcons(mContent.getNextPage());
     }
 
-    public void beginExternalDrag() {
-        mEmptyCellRank = mContent.allocateRankForNewItem();
-        mIsExternalDrag = true;
-        mDragInProgress = true;
-
-        // Since this folder opened by another controller, it might not get onDrop or
-        // onDropComplete. Perform cleanup once drag-n-drop ends.
-        mDragController.addDragListener(this);
-    }
-
     @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_FOLDER) != 0;
@@ -668,6 +680,8 @@
             } else if (mDragInProgress) {
                 mDeleteFolderOnDropCompleted = true;
             }
+        } else if (!mDragInProgress) {
+            mContent.unbindItems();
         }
         mSuppressFolderDeletion = false;
         clearDragInfo();
@@ -953,24 +967,12 @@
         setPivotX(folderPivotX);
         setPivotY(folderPivotY);
 
-        mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
-                (1.0f * folderPivotX / width));
-        mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
-                (1.0f * folderPivotY / height));
-
         lp.width = width;
         lp.height = height;
         lp.x = left;
         lp.y = top;
     }
 
-    public float getPivotXForIconAnimation() {
-        return mFolderIconPivotX;
-    }
-    public float getPivotYForIconAnimation() {
-        return mFolderIconPivotY;
-    }
-
     private int getContentAreaHeight() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
         int maxContentAreaHeight = grid.availableHeightPx
@@ -1031,7 +1033,7 @@
     }
 
     public int getItemCount() {
-        return mContent.getItemCount();
+        return mInfo.contents.size();
     }
 
     @Thunk void replaceFolderWithFinalItem() {
@@ -1039,7 +1041,7 @@
         Runnable onCompleteRunnable = new Runnable() {
             @Override
             public void run() {
-                int itemCount = mInfo.contents.size();
+                int itemCount = getItemCount();
                 if (itemCount <= 1) {
                     View newIcon = null;
 
@@ -1116,6 +1118,8 @@
                     return false;
                 }
             });
+        } else {
+            setOnKeyListener(null);
         }
     }
 
@@ -1242,9 +1246,9 @@
                 item.cellY);
         updateItemLocationsInDatabaseBatch();
 
-        ArrayList<View> items = new ArrayList<>(getIconsInReadingOrder());
-        items.add(rank, mContent.createAndAddViewForRank(item, rank));
-        mContent.arrangeChildren(items);
+        if (mContent.areViewsBound()) {
+            mContent.createAndAddViewForRank(item, rank);
+        }
         mItemsInvalidated = true;
     }
 
@@ -1275,12 +1279,11 @@
         updateTextViewFocus();
     }
 
-    @Override
-    public void prepareAutoUpdate() {
-        close(false);
-    }
-
-    public void onTitleChanged(CharSequence title) {
+    /**
+     * Utility methods to iterate over items of the view
+     */
+    public void iterateOverItems(ItemOperator op) {
+        mContent.iterateOverItems(op);
     }
 
     /**
@@ -1289,14 +1292,7 @@
     public ArrayList<View> getIconsInReadingOrder() {
         if (mItemsInvalidated) {
             mItemsInReadingOrder.clear();
-            mContent.iterateOverItems(new ItemOperator() {
-
-                @Override
-                public boolean evaluate(ItemInfo info, View view) {
-                    mItemsInReadingOrder.add(view);
-                    return false;
-                }
-            });
+            mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v));
             mItemsInvalidated = false;
         }
         return mItemsInReadingOrder;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d17b21f..3840639 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -70,6 +70,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * An icon that can appear on in the workspace representing an {@link Folder}.
@@ -100,7 +101,7 @@
     ClippedFolderIconLayoutRule mPreviewLayoutRule;
     private PreviewItemManager mPreviewItemManager;
     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
-    private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>();
+    private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
 
     boolean mAnimating = false;
 
@@ -175,7 +176,7 @@
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.mDotRenderer = grid.mDotRenderer;
+        icon.mDotRenderer = grid.mDotRendererWorkSpace;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -257,7 +258,6 @@
     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
         public void onAlarm(Alarm alarm) {
             mFolder.beginExternalDrag();
-            mFolder.animateOpen();
         }
     };
 
@@ -323,18 +323,17 @@
             int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
             boolean itemAdded = false;
             if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
-                List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
+                List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
                 mInfo.add(item, index, false);
                 mCurrentPreviewItems.clear();
-                mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0));
+                mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
 
                 if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
-                    for (int i = 0; i < mCurrentPreviewItems.size(); ++i) {
-                        if (mCurrentPreviewItems.get(i).getTag().equals(item)) {
-                            // If the item dropped is going to be in the preview, we update the
-                            // index here to reflect its position in the preview.
-                            index = i;
-                        }
+                    int newIndex = mCurrentPreviewItems.indexOf(item);
+                    if (newIndex >= 0) {
+                        // If the item dropped is going to be in the preview, we update the
+                        // index here to reflect its position in the preview.
+                        index = newIndex;
                     }
 
                     mPreviewItemManager.hidePreviewItem(index, true);
@@ -548,11 +547,10 @@
     }
 
     /**
-     * Returns the list of "preview items" on {@param page}.
+     * Returns the list of items which should be visible in the preview
      */
-    public List<BubbleTextView> getPreviewIconsOnPage(int page) {
-        return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
-                .previewItemsForPage(page, mFolder.getIconsInReadingOrder());
+    public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
+        return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
     }
 
     @Override
@@ -570,11 +568,14 @@
     private void updatePreviewItems(boolean animate) {
         mPreviewItemManager.updatePreviewItems(animate);
         mCurrentPreviewItems.clear();
-        mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0));
+        mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
     }
 
-    @Override
-    public void prepareAutoUpdate() {
+    /**
+     * Updates the preview items which match the provided condition
+     */
+    public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+        mPreviewItemManager.updatePreviewItems(itemCheck);
     }
 
     @Override
@@ -597,7 +598,6 @@
         requestLayout();
     }
 
-    @Override
     public void onTitleChanged(CharSequence title) {
         mFolderName.setText(title);
         setContentDescription(getContext().getString(R.string.folder_name_format, title));
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3e00cae..54b363e 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -24,10 +24,10 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewDebug;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
@@ -46,11 +46,14 @@
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.ViewCache;
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.function.ToIntFunction;
+import java.util.stream.Collectors;
 
 public class FolderPagedView extends PagedView<PageIndicatorDots> {
 
@@ -69,12 +72,12 @@
 
     public final boolean mIsRtl;
 
-    private final LayoutInflater mInflater;
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
 
     @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>();
 
     private final FolderGridOrganizer mOrganizer;
+    private final ViewCache mViewCache;
 
     private int mAllocatedContentSize;
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -84,17 +87,20 @@
 
     private Folder mFolder;
 
+    // If the views are attached to the folder or not. A folder should be bound when its
+    // animating or is open.
+    private boolean mViewsBound = false;
+
     public FolderPagedView(Context context, AttributeSet attrs) {
         super(context, attrs);
         InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
         mOrganizer = new FolderGridOrganizer(profile);
 
-        mInflater = LayoutInflater.from(context);
-
         mIsRtl = Utilities.isRtl(getResources());
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
+        mViewCache = BaseActivity.fromContext(context).getViewCache();
     }
 
     public void setFolder(Folder folder) {
@@ -127,35 +133,50 @@
     /**
      * Binds items to the layout.
      */
-    public void bindItems(ArrayList<WorkspaceItemInfo> items) {
-        ArrayList<View> icons = new ArrayList<>();
-        for (WorkspaceItemInfo item : items) {
-            icons.add(createNewView(item));
+    public void bindItems(List<WorkspaceItemInfo> items) {
+        if (mViewsBound) {
+            unbindItems();
         }
-        arrangeChildren(icons);
-    }
-
-    public void allocateSpaceForRank(int rank) {
-        ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder());
-        views.add(rank, null);
-        arrangeChildren(views);
+        arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList()));
+        mViewsBound = true;
     }
 
     /**
-     * Create space for a new item at the end, and returns the rank for that item.
-     * Also sets the current page to the last page.
+     * Removes all the icons from the folder
      */
-    public int allocateRankForNewItem() {
-        int rank = getItemCount();
-        allocateSpaceForRank(rank);
-        setCurrentPage(rank / mOrganizer.getMaxItemsPerPage());
-        return rank;
+    public void unbindItems() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            CellLayout page = (CellLayout) getChildAt(i);
+            ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
+            for (int j = container.getChildCount() - 1; j >= 0; j--) {
+                mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
+            }
+            page.removeAllViews();
+            mViewCache.recycleView(R.layout.folder_page, page);
+        }
+        removeAllViews();
+        mViewsBound = false;
     }
 
+    /**
+     * Returns true if the icons are bound to the folder
+     */
+    public boolean areViewsBound() {
+        return mViewsBound;
+    }
+
+    /**
+     * Creates and adds an icon corresponding to the provided rank
+     * @return the created icon
+     */
     public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
         View icon = createNewView(item);
-        allocateSpaceForRank(rank);
-        addViewForRank(icon, item, rank);
+        if (!mViewsBound) {
+            return icon;
+        }
+        ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder());
+        views.add(rank, icon);
+        arrangeChildren(views);
         return icon;
     }
 
@@ -173,16 +194,24 @@
 
     @SuppressLint("InflateParams")
     public View createNewView(WorkspaceItemInfo item) {
-        final BubbleTextView textView = (BubbleTextView) mInflater.inflate(
-                R.layout.folder_application, null, false);
+        if (item == null) {
+            return null;
+        }
+        final BubbleTextView textView = mViewCache.getView(
+                R.layout.folder_application, getContext(), null);
         textView.applyFromWorkspaceItem(item);
-        textView.setHapticFeedbackEnabled(false);
         textView.setOnClickListener(ItemClickHandler.INSTANCE);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
-
-        textView.setLayoutParams(new CellLayout.LayoutParams(
-                item.cellX, item.cellY, item.spanX, item.spanY));
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams();
+        if (lp == null) {
+            textView.setLayoutParams(new CellLayout.LayoutParams(
+                    item.cellX, item.cellY, item.spanX, item.spanY));
+        } else {
+            lp.cellX = item.cellX;
+            lp.cellY = item.cellY;
+            lp.cellHSpan = lp.cellVSpan = 1;
+        }
         return textView;
     }
 
@@ -197,7 +226,7 @@
 
     private CellLayout createAndAddNewPage() {
         DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
-        CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false);
+        CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
         page.setInvertIfRtl(true);
@@ -240,7 +269,7 @@
      * @param list the ordered list of children.
      */
     @SuppressLint("RtlHardcoded")
-    public void arrangeChildren(ArrayList<View> list) {
+    public void arrangeChildren(List<View> list) {
         int itemCount = list.size();
         ArrayList<CellLayout> pages = new ArrayList<>();
         for (int i = 0; i < getChildCount(); i++) {
@@ -313,16 +342,6 @@
                 (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
     }
 
-    public int getItemCount() {
-        int lastPageIndex = getChildCount() - 1;
-        if (lastPageIndex < 0) {
-            // If there are no pages, nothing has yet been added to the folder.
-            return 0;
-        }
-        return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
-                + lastPageIndex * mOrganizer.getMaxItemsPerPage();
-    }
-
     /**
      * @return the rank of the cell nearest to the provided pixel position.
      */
diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
index c818462..caf6e55 100644
--- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
+++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java
@@ -17,6 +17,8 @@
 
 import android.graphics.drawable.Drawable;
 
+import com.android.launcher3.WorkspaceItemInfo;
+
 /**
  * Manages the parameters used to draw a Folder preview item.
  */
@@ -25,9 +27,10 @@
     float transY;
     float scale;
     float overlayAlpha;
-    FolderPreviewItemAnim anim;
+    public FolderPreviewItemAnim anim;
     public boolean hidden;
-    Drawable drawable;
+    public Drawable drawable;
+    public WorkspaceItemInfo item;
 
     PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
         this.transX = transX;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2ac6bf4..2d817e6 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -23,28 +23,51 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
 import android.view.View;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.graphics.PreloadIconDrawable;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
  */
 public class PreviewItemManager {
 
-    private FolderIcon mIcon;
+    private static final FloatProperty<PreviewItemManager> CURRENT_PAGE_ITEMS_TRANS_X =
+            new FloatProperty<PreviewItemManager>("currentPageItemsTransX") {
+                @Override
+                public void setValue(PreviewItemManager manager, float v) {
+                    manager.mCurrentPageItemsTransX = v;
+                    manager.onParamsChanged();
+                }
+
+                @Override
+                public Float get(PreviewItemManager manager) {
+                    return manager.mCurrentPageItemsTransX;
+                }
+            };
+
+    private final Context mContext;
+    private final FolderIcon mIcon;
+    private final DrawableFactory mDrawableFactory;
+    private final int mIconSize;
 
     // These variables are all associated with the drawing of the preview; they are stored
     // as member variables for shared usage and to avoid computation on each frame
@@ -69,7 +92,10 @@
     private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
 
     public PreviewItemManager(FolderIcon icon) {
+        mContext = icon.getContext();
         mIcon = icon;
+        mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
+        mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
     }
 
     /**
@@ -200,7 +226,7 @@
     }
 
     void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
-        List<BubbleTextView> items = mIcon.getPreviewIconsOnPage(page);
+        List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
         int prevNumItems = params.size();
 
         // We adjust the size of the list to match the number of items in the preview.
@@ -214,13 +240,7 @@
         int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
         for (int i = 0; i < params.size(); i++) {
             PreviewItemDrawingParams p = params.get(i);
-            p.drawable = items.get(i).getCompoundDrawables()[1];
-
-            if (p.drawable != null && !mIcon.mFolder.isOpen()) {
-                // Set the callback to FolderIcon as it is responsible to drawing the icon. The
-                // callback will be released when the folder is opened.
-                p.drawable.setCallback(mIcon);
-            }
+            setDrawable(p, items.get(i));
 
             if (!animate) {
                 computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
@@ -253,14 +273,8 @@
             buildParamsForPage(currentPage, mCurrentPageParams, false);
             onParamsChanged();
 
-            ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
-            slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                    mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue();
-                    onParamsChanged();
-                }
-            });
+            ValueAnimator slideAnimator = ObjectAnimator
+                    .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
             slideAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -277,6 +291,25 @@
         buildParamsForPage(0, mFirstPageParams, animate);
     }
 
+    void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
+        boolean modified = false;
+        for (PreviewItemDrawingParams param : mFirstPageParams) {
+            if (itemCheck.test(param.item)) {
+                setDrawable(param, param.item);
+                modified = true;
+            }
+        }
+        for (PreviewItemDrawingParams param : mCurrentPageParams) {
+            if (itemCheck.test(param.item)) {
+                setDrawable(param, param.item);
+                modified = true;
+            }
+        }
+        if (modified) {
+            mIcon.invalidate();
+        }
+    }
+
     boolean verifyDrawable(@NonNull Drawable who) {
         for (int i = 0; i < mFirstPageParams.size(); i++) {
             if (mFirstPageParams.get(i).drawable == who) {
@@ -296,46 +329,46 @@
      *  - Moving into a new position
      *  - Moving out of the preview
      *
-     * @param oldParams The list of items in the old preview.
-     * @param newParams The list of items in the new preview.
+     * @param oldItems The list of items in the old preview.
+     * @param newItems The list of items in the new preview.
      * @param dropped The item that was dropped onto the FolderIcon.
      */
-    public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams,
+    public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
             WorkspaceItemInfo dropped) {
-        int numItems = newParams.size();
+        int numItems = newItems.size();
         final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
         buildParamsForPage(0, params, false);
 
         // New preview items for items that are moving in (except for the dropped item).
-        List<BubbleTextView> moveIn = new ArrayList<>();
-        for (BubbleTextView btv : newParams) {
-            if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) {
-                moveIn.add(btv);
+        List<WorkspaceItemInfo> moveIn = new ArrayList<>();
+        for (WorkspaceItemInfo newItem : newItems) {
+            if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
+                moveIn.add(newItem);
             }
         }
         for (int i = 0; i < moveIn.size(); ++i) {
-            int prevIndex = newParams.indexOf(moveIn.get(i));
+            int prevIndex = newItems.indexOf(moveIn.get(i));
             PreviewItemDrawingParams p = params.get(prevIndex);
             computePreviewItemDrawingParams(prevIndex, numItems, p);
-            updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)),
+            updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)),
                     numItems);
         }
 
         // Items that are moving into new positions within the preview.
-        for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) {
-            int oldIndex = oldParams.indexOf(newParams.get(newIndex));
+        for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) {
+            int oldIndex = oldItems.indexOf(newItems.get(newIndex));
             if (oldIndex >= 0 && newIndex != oldIndex) {
                 PreviewItemDrawingParams p = params.get(newIndex);
-                updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems);
+                updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems);
             }
         }
 
         // Old preview items that need to be moved out.
-        List<BubbleTextView> moveOut = new ArrayList<>(oldParams);
-        moveOut.removeAll(newParams);
+        List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
+        moveOut.removeAll(newItems);
         for (int i = 0; i < moveOut.size(); ++i) {
-            BubbleTextView item = moveOut.get(i);
-            int oldIndex = oldParams.indexOf(item);
+            WorkspaceItemInfo item = moveOut.get(i);
+            int oldIndex = oldItems.indexOf(item);
             PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
             updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
             params.add(0, p); // We want these items first so that they are on drawn last.
@@ -348,14 +381,9 @@
         }
     }
 
-    private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv,
+    private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
             int prevIndex, int newIndex, int numItems) {
-        p.drawable = btv.getCompoundDrawables()[1];
-        if (!mIcon.mFolder.isOpen()) {
-            // Set the callback to FolderIcon as it is responsible to drawing the icon. The
-            // callback will be released when the folder is opened.
-            p.drawable.setCallback(mIcon);
-        }
+        setDrawable(p, item);
 
         FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems,
                 newIndex, numItems, DROP_IN_ANIMATION_DURATION, null);
@@ -364,4 +392,20 @@
         }
         p.anim = anim;
     }
+
+    private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
+        if (item.hasPromiseIconUi()) {
+            PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
+            drawable.setLevel(item.getInstallProgress());
+            p.drawable = drawable;
+        } else {
+            p.drawable = mDrawableFactory.newIcon(mContext, item);
+        }
+        p.drawable.setBounds(0, 0, mIconSize, mIconSize);
+        p.item = item;
+
+        // Set the callback to FolderIcon as it is responsible to drawing the icon. The
+        // callback will be released when the folder is opened.
+        p.drawable.setCallback(mIcon);
+    }
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 747efe3..f579451 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -195,15 +195,22 @@
 
         private final Bitmap mPreviewSnapshot;
         private final Context mContext;
+        private final boolean mIsIcon;
 
         OutlineGeneratorCallback(Bitmap preview) {
             mPreviewSnapshot = preview;
             mContext = mView.getContext();
+            mIsIcon = mView instanceof BubbleTextView;
         }
 
         @Override
         public void run() {
             Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
+            if (mIsIcon) {
+                int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx;
+                preview = Bitmap.createScaledBitmap(preview, size, size, false);
+            }
+            //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
 
             // We start by removing most of the alpha channel so as to ignore shadows, and
             // other types of partial transparency when defining the shape of the object
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 288749f..837301f 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -80,26 +80,25 @@
      * Returns a drawable that can be used as a badge for the user or null.
      */
     @UiThread
-    public Drawable getBadgeForUser(UserHandle user, Context context) {
+    public Drawable getBadgeForUser(UserHandle user, Context context, int badgeSize) {
         if (mMyUser.equals(user)) {
             return null;
         }
 
-        Bitmap badgeBitmap = getUserBadge(user, context);
+        Bitmap badgeBitmap = getUserBadge(user, context, badgeSize);
         FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap);
         d.setFilterBitmap(true);
         d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight());
         return d;
     }
 
-    protected synchronized Bitmap getUserBadge(UserHandle user, Context context) {
+    protected synchronized Bitmap getUserBadge(UserHandle user, Context context, int badgeSize) {
         Bitmap badgeBitmap = mUserBadges.get(user);
         if (badgeBitmap != null) {
             return badgeBitmap;
         }
 
         final Resources res = context.getApplicationContext().getResources();
-        int badgeSize = res.getDimensionPixelSize(R.dimen.profile_badge_size);
         badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888);
 
         Drawable drawable = context.getPackageManager().getUserBadgedDrawableForDensity(
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index 46b5002..832956d 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -34,9 +34,11 @@
     class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> {
 
         private final PackageManager mPackageManager;
+        private final boolean mAddToMemCache;
 
-        public ComponentCachingLogic(Context context) {
+        public ComponentCachingLogic(Context context, boolean addToMemCache) {
             mPackageManager = context.getPackageManager();
+            mAddToMemCache = addToMemCache;
         }
 
         @Override
@@ -60,5 +62,10 @@
             // Do not load icon.
             target.icon = BitmapInfo.LOW_RES_ICON;
         }
+
+        @Override
+        public boolean addToMemCache() {
+            return mAddToMemCache;
+        }
     }
 }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 5fb833c..9886f53 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -23,7 +23,9 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
@@ -41,14 +43,15 @@
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
 import com.android.launcher3.icons.cache.BaseIconCache;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 
 import java.util.function.Supplier;
@@ -63,7 +66,7 @@
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
 
-    private final LauncherAppsCompat mLauncherApps;
+    private final LauncherApps mLauncherApps;
     private final UserManagerCompat mUserManager;
     private final InstantAppResolver mInstantAppResolver;
     private final IconProvider mIconProvider;
@@ -73,9 +76,9 @@
     public IconCache(Context context, InvariantDeviceProfile inv) {
         super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
-        mComponentWithLabelCachingLogic = new ComponentCachingLogic(context);
+        mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
-        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+        mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
         mIconProvider = IconProvider.INSTANCE.get(context);
@@ -193,7 +196,7 @@
     public synchronized String getTitleNoCache(ComponentWithLabel info) {
         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
                 mComponentWithLabelCachingLogic, false /* usePackageIcon */,
-                true /* useLowResIcon */, false /* addToMemCache */);
+                true /* useLowResIcon */);
         return Utilities.trim(entry.title);
     }
 
@@ -234,9 +237,14 @@
         return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
     }
 
+    public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
+        cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
+    }
+
     @Override
     protected String getIconSystemState(String packageName) {
-        return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
+        return mIconProvider.getSystemStateForPackage(mSystemState, packageName)
+                + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
     }
 
     public static abstract class IconLoadRequest extends HandlerRunnable {
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 7632408..adc92c4 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -24,6 +24,8 @@
 import android.graphics.drawable.Drawable;
 import android.os.Process;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -37,8 +39,6 @@
 
 import java.util.function.Supplier;
 
-import androidx.annotation.Nullable;
-
 /**
  * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
  * that are threadsafe.
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 47a0f53..04cf20a 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -8,8 +8,8 @@
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.util.IOUtils;
 
 import java.io.BufferedReader;
@@ -32,8 +32,7 @@
  */
 public final class FileLog {
 
-    protected static final boolean ENABLED =
-            FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE;
+    protected static final boolean ENABLED = true;
     private static final String FILE_NAME_PREFIX = "log-";
     private static final DateFormat DATE_FORMAT =
             DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
@@ -91,7 +90,8 @@
         Message.obtain(getHandler(), LogWriterCallback.MSG_WRITE, out).sendToTarget();
     }
 
-    private static Handler getHandler() {
+    @VisibleForTesting
+    static Handler getHandler() {
         synchronized (DATE_FORMAT) {
             if (sHandler == null) {
                 sHandler = new Handler(createAndStartNewLooper("file-logger"),
@@ -105,15 +105,16 @@
      * Blocks until all the pending logs are written to the disk
      * @param out if not null, all the persisted logs are copied to the writer.
      */
-    public static void flushAll(PrintWriter out) throws InterruptedException {
+    public static boolean flushAll(PrintWriter out) throws InterruptedException {
         if (!ENABLED) {
-            return;
+            return false;
         }
         CountDownLatch latch = new CountDownLatch(1);
         Message.obtain(getHandler(), LogWriterCallback.MSG_FLUSH,
                 Pair.create(out, latch)).sendToTarget();
 
         latch.await(2, TimeUnit.SECONDS);
+        return latch.getCount() == 0;
     }
 
     /**
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index c72b07a..21ca74e 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -427,10 +427,16 @@
         mAppOrTaskLaunch = false;
         ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
         ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
-
         if (!IS_VERBOSE) {
             return;
         }
+        Log.d(TAG, generateLog(ev));
+    }
+
+    /**
+     * Returns a human-readable log for given user event.
+     */
+    public static String generateLog(LauncherEvent ev) {
         String log = "\n-----------------------------------------------------"
                 + "\naction:" + LoggerUtils.getActionStr(ev.action);
         if (ev.srcTarget != null && ev.srcTarget.length > 0) {
@@ -445,8 +451,7 @@
                 ev.elapsedSessionMillis,
                 ev.actionDurationMillis);
         log += "\n\n";
-        Log.d(TAG, log);
-        return;
+        return log;
     }
 
     private static String getTargetsStr(Target[] targets) {
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index dfd5a70..227bb22 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -17,6 +17,7 @@
 
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.UserHandle;
 import android.util.LongSparseArray;
@@ -29,11 +30,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -90,7 +90,7 @@
 
             PackageInstallerCompat packageInstaller =
                     PackageInstallerCompat.getInstance(app.getContext());
-            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(app.getContext());
+            LauncherApps launcherApps = app.getContext().getSystemService(LauncherApps.class);
 
             for (ItemInfo item : filteredItems) {
                 // Find appropriate space for the item.
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 3873a17..9f1843f 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -23,21 +23,24 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.os.LocaleList;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppFilter;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SafeCloseable;
 
 import java.util.ArrayList;
@@ -46,9 +49,6 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 
 /**
  * Stores the list of all applications for the all apps view.
@@ -110,10 +110,9 @@
         mDataChanged = true;
     }
 
-    public void addPromiseApp(Context context,
-                              PackageInstallerCompat.PackageInstallInfo installInfo) {
-        ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context)
-                .getApplicationInfo(installInfo.packageName, 0, installInfo.user);
+    public void addPromiseApp(Context context, PackageInstallInfo installInfo) {
+        ApplicationInfo applicationInfo = new PackageManagerHelper(context)
+                .getApplicationInfo(installInfo.packageName, installInfo.user, 0);
         // only if not yet installed
         if (applicationInfo == null) {
             PromiseAppInfo info = new PromiseAppInfo(installInfo);
@@ -134,10 +133,10 @@
                     && appInfo.user.equals(user)
                     && appInfo instanceof PromiseAppInfo) {
                 final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
-                if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
+                if (installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
                     promiseAppInfo.level = installInfo.progress;
                     return promiseAppInfo;
-                } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                } else if (installInfo.state == PackageInstallInfo.STATUS_FAILED) {
                     removeApp(i);
                 }
             }
@@ -164,11 +163,8 @@
      * Add the icons for the supplied apk called packageName.
      */
     public void addPackage(Context context, String packageName, UserHandle user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
-                user);
-
-        for (LauncherActivityInfo info : matches) {
+        for (LauncherActivityInfo info : context.getSystemService(LauncherApps.class)
+                .getActivityList(packageName, user)) {
             add(new AppInfo(context, info, user), info);
         }
     }
@@ -214,9 +210,8 @@
      * Add and remove icons for this package which has been updated.
      */
     public void updatePackage(Context context, String packageName, UserHandle user) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-        final List<LauncherActivityInfo> matches = launcherApps.getActivityList(packageName,
-                user);
+        final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
+                .getActivityList(packageName, user);
         if (matches.size() > 0) {
             // Find disabled/removed activities and remove them from data and add them
             // to the removed list.
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 1149b55..a0b7177 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.util.MultiHashMap;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -34,6 +35,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import static android.os.Process.myUserHandle;
+
 /**
  * Helper class to send broadcasts to package installers that have:
  * - Items on the first screen
@@ -60,7 +63,7 @@
 
     private final MultiHashMap<String, String> mPackagesForInstaller;
 
-    public FirstScreenBroadcast(HashMap<String, SessionInfo> sessionInfoForPackage) {
+    public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
         mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
     }
 
@@ -69,11 +72,13 @@
      *         of packages with active sessions for that installer.
      */
     private MultiHashMap<String, String> getPackagesForInstaller(
-            HashMap<String, SessionInfo> sessionInfoForPackage) {
+            HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
         MultiHashMap<String, String> packagesForInstaller = new MultiHashMap<>();
-        for (Map.Entry<String, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
-            packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
-                    entry.getKey());
+        for (Map.Entry<PackageUserKey, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
+            if (myUserHandle().equals(entry.getKey().mUser)) {
+                packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
+                        entry.getKey().mPackageName);
+            }
         }
         return packagesForInstaller;
     }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index faecc06..ac44b0e 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -17,6 +17,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
@@ -27,8 +29,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.util.GridOccupancy;
@@ -39,8 +41,6 @@
 import java.util.Collections;
 import java.util.HashSet;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
  * result of restoring from a larger device or device density change.
@@ -970,8 +970,9 @@
                 .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
             validPackages.add(info.packageName);
         }
-        validPackages.addAll(PackageInstallerCompat.getInstance(context)
-                .updateAndGetActiveSessionCache().keySet());
+        PackageInstallerCompat.getInstance(context)
+                .getActiveSessions().keySet()
+                .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
         return validPackages;
     }
 
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 1c39d1f..6154e7e 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.CursorWrapper;
@@ -33,17 +34,16 @@
 import android.util.LongSparseArray;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ContentWriter;
@@ -260,7 +260,7 @@
         Intent newIntent = new Intent(Intent.ACTION_MAIN, null);
         newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         newIntent.setComponent(componentName);
-        LauncherActivityInfo lai = LauncherAppsCompat.getInstance(mContext)
+        LauncherActivityInfo lai = mContext.getSystemService(LauncherApps.class)
                 .resolveActivity(newIntent, user);
         if ((lai == null) && !allowMissingTarget) {
             Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 60e917d..a29b7e1 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
@@ -52,8 +53,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
@@ -65,6 +64,8 @@
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.pm.PackageInstallInfo;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -74,6 +75,7 @@
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TraceHelper;
 
 import java.util.ArrayList;
@@ -103,7 +105,7 @@
 
     private final LoaderResults mResults;
 
-    private final LauncherAppsCompat mLauncherApps;
+    private final LauncherApps mLauncherApps;
     private final UserManagerCompat mUserManager;
     private final DeepShortcutManager mShortcutManager;
     private final PackageInstallerCompat mPackageInstaller;
@@ -119,7 +121,7 @@
         mBgDataModel = dataModel;
         mResults = results;
 
-        mLauncherApps = LauncherAppsCompat.getInstance(mApp.getContext());
+        mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
         mUserManager = UserManagerCompat.getInstance(mApp.getContext());
         mShortcutManager = DeepShortcutManager.getInstance(mApp.getContext());
         mPackageInstaller = PackageInstallerCompat.getInstance(mApp.getContext());
@@ -228,9 +230,10 @@
             mResults.bindWidgets();
 
             verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache");
-            updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(mApp.getContext()),
-                    mApp.getModel()::onWidgetLabelsUpdated);
+
+            TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache");
+            updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
+                    mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update");
@@ -283,8 +286,11 @@
         synchronized (mBgDataModel) {
             mBgDataModel.clear();
 
-            final HashMap<String, SessionInfo> installingPkgs =
-                    mPackageInstaller.updateAndGetActiveSessionCache();
+            final HashMap<PackageUserKey, SessionInfo> installingPkgs =
+                    mPackageInstaller.getActiveSessions();
+            installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
+
+            final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
@@ -384,14 +390,16 @@
                             // If there is no target package, its an implicit intent
                             // (legacy shortcut) which is always valid
                             boolean validTarget = TextUtils.isEmpty(targetPkg) ||
-                                    mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user);
+                                    mLauncherApps.isPackageEnabled(targetPkg, c.user);
 
-                            if (cn != null && validTarget) {
+                            // If it's a deep shortcut, we'll use pinned shortcuts to restore it
+                            if (cn != null && validTarget && c.itemType
+                                    != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                                 // If the apk is present and the shortcut points to a specific
                                 // component.
 
                                 // If the component is already present
-                                if (mLauncherApps.isActivityEnabledForProfile(cn, c.user)) {
+                                if (mLauncherApps.isActivityEnabled(cn, c.user)) {
                                     // no special handling necessary for this item
                                     c.markRestored();
                                 } else {
@@ -421,9 +429,10 @@
                                     // installed later.
                                     FileLog.d(TAG, "package not yet restored: " + targetPkg);
 
+                                    tempPackageKey.update(targetPkg, c.user);
                                     if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) {
                                         // Restore has started once.
-                                    } else if (installingPkgs.containsKey(targetPkg)) {
+                                    } else if (installingPkgs.containsKey(tempPackageKey)) {
                                         // App restore has started. Update the flag
                                         c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED;
                                         c.updater().put(LauncherSettings.Favorites.RESTORED,
@@ -538,7 +547,8 @@
                                 }
 
                                 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
-                                    SessionInfo si = installingPkgs.get(targetPkg);
+                                    tempPackageKey.update(targetPkg, c.user);
+                                    SessionInfo si = installingPkgs.get(tempPackageKey);
                                     if (si == null) {
                                         info.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
                                     } else {
@@ -569,7 +579,7 @@
                             break;
 
                         case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                            if (FeatureFlags.GO_DISABLE_WIDGETS) {
+                            if (WidgetsModel.GO_DISABLE_WIDGETS) {
                                 c.markDeleted("Only legacy shortcuts can have null package");
                                 continue;
                             }
@@ -639,8 +649,10 @@
                                     appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
                                             component);
                                     appWidgetInfo.restoreStatus = c.restoreFlag;
+
+                                    tempPackageKey.update(component.getPackageName(), c.user);
                                     SessionInfo si =
-                                            installingPkgs.get(component.getPackageName());
+                                            installingPkgs.get(tempPackageKey);
                                     Integer installProgress = si == null
                                             ? null
                                             : (int) (si.getProgress() * 100);
@@ -830,12 +842,12 @@
             allActivityList.addAll(apps);
         }
 
-        if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
+        if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
             // get all active sessions and add them to the all apps list
             for (PackageInstaller.SessionInfo info :
                     mPackageInstaller.getAllVerifiedSessions()) {
                 mBgAllAppsList.addPromiseApp(mApp.getContext(),
-                        PackageInstallerCompat.PackageInstallInfo.fromInstallingState(info));
+                        PackageInstallInfo.fromInstallingState(info));
             }
         }
 
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index b7a19d3..c69ace9 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -40,15 +40,18 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ItemInfoMatcher;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Class for handling model updates.
@@ -262,9 +265,12 @@
     /**
      * Removes the specified items from the database
      */
-    public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
+    public void deleteItemsFromDatabase(final Collection<? extends ItemInfo> items) {
         ModelVerifier verifier = new ModelVerifier();
-
+        FileLog.d(TAG, "removing items from db " + items.stream().map(
+                (item) -> item.getTargetComponent() == null ? ""
+                        : item.getTargetComponent().getPackageName()).collect(
+                Collectors.joining(",")), new Exception());
         enqueueDeleteRunnable(() -> {
             for (ItemInfo item : items) {
                 final Uri uri = Favorites.getContentUri(item.id);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 802cbc7..2832150 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -19,16 +19,14 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
-import com.android.launcher3.AppInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.PackageInstallerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.util.HashSet;
@@ -46,7 +44,7 @@
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-        if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
+        if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
             try {
                 // For instant apps we do not get package-add. Use setting events to update
                 // any pinned icons.
@@ -79,7 +77,7 @@
                     if (si.hasPromiseIconUi() && (cn != null)
                             && mInstallInfo.packageName.equals(cn.getPackageName())) {
                         si.setInstallProgress(mInstallInfo.progress);
-                        if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                        if (mInstallInfo.state == PackageInstallInfo.STATUS_FAILED) {
                             // Mark this info as broken.
                             si.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE;
                         }
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 741be66..3ef48cd 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.model;
 
 import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherSettings;
 
 /**
  * Represents a {@link Package} in the widget tray section.
@@ -30,10 +31,12 @@
 
     public PackageItemInfo(String packageName) {
         this.packageName = packageName;
+        this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
     }
 
     public PackageItemInfo(PackageItemInfo copy) {
         this.packageName = copy.packageName;
+        this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index e6a1a0b..db63b7c 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,9 +15,13 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.Process;
 import android.os.UserHandle;
@@ -31,7 +35,6 @@
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
@@ -53,8 +56,6 @@
 import java.util.HashSet;
 import java.util.List;
 
-import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-
 /**
  * Handles updates due to changes in package manager (app installed/updated/removed)
  * or when a user availability changes.
@@ -104,7 +105,7 @@
                 for (int i = 0; i < N; i++) {
                     if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
                     iconCache.updateIconsForPkg(packages[i], mUser);
-                    if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) {
+                    if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
                         appsList.removePackage(packages[i], mUser);
                     }
                     appsList.addPackage(context, packages[i], mUser);
@@ -132,6 +133,7 @@
                 break;
             case OP_REMOVE: {
                 for (int i = 0; i < N; i++) {
+                    FileLog.d(TAG, "Removing app icon" + packages[i]);
                     iconCache.removeIconsForPkg(packages[i], mUser);
                 }
                 // Fall through
@@ -217,10 +219,10 @@
                                         infoUpdated = true;
                                     }
                                 } else if (!cn.getClassName().equals(IconCache.EMPTY_CLASS_NAME)) {
-                                    isTargetValid = LauncherAppsCompat.getInstance(context)
-                                            .isActivityEnabledForProfile(cn, mUser);
+                                    isTargetValid = context.getSystemService(LauncherApps.class)
+                                            .isActivityEnabled(cn, mUser);
                                 }
-                                if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) {
+                                if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                     if (updateWorkspaceItemIntent(context, si, packageName)) {
                                         infoUpdated = true;
                                     } else if (si.hasPromiseIconUi()) {
@@ -301,9 +303,9 @@
             // removedPackages is a super-set of removedComponents
         } else if (mOp == OP_UPDATE) {
             // Mark disabled packages in the broadcast to be removed
-            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+            final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
             for (int i=0; i<N; i++) {
-                if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
+                if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
                     removedPackages.add(packages[i]);
                 }
             }
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index 3aedae6..eb3cb52 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -19,11 +19,11 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -53,7 +53,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
         for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
             UserHandle user = entry.getKey();
@@ -62,7 +62,7 @@
             final ArrayList<String> packagesUnavailable = new ArrayList<>();
 
             for (String pkg : new HashSet<>(entry.getValue())) {
-                if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
+                if (!launcherApps.isPackageEnabled(pkg, user)) {
                     if (pmHelper.isAppOnSdcard(pkg, user)) {
                         packagesUnavailable.add(pkg);
                     } else {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index c3cd9d0..6c358b1 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -64,7 +64,7 @@
         for (ItemInfo itemInfo : dataModel.itemsIdMap) {
             if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo;
-                if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) {
+                if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) {
                     keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
                     allIds.add(si.getDeepShortcutId());
                 }
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index e38529b..b442c42 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -8,8 +8,8 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
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/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
new file mode 100644
index 0000000..f157603
--- /dev/null
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -0,0 +1,129 @@
+/*
+ * 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.pm;
+
+import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
+import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
+import static com.android.launcher3.pm.PackageInstallerCompat.getUserHandle;
+
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.launcher3.util.PackageUserKey;
+
+public class InstallSessionTracker extends PackageInstaller.SessionCallback {
+
+    // Lazily initialized
+    private SparseArray<PackageUserKey> mActiveSessions = null;
+
+    private final PackageInstallerCompat mInstallerCompat;
+    private final Callback mCallback;
+
+    InstallSessionTracker(PackageInstallerCompat installerCompat, Callback callback) {
+        mInstallerCompat = installerCompat;
+        mCallback = callback;
+    }
+
+    @Override
+    public void onCreated(int sessionId) {
+        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+        if (sessionInfo != null) {
+            mCallback.onInstallSessionCreated(PackageInstallInfo.fromInstallingState(sessionInfo));
+        }
+
+        mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+    }
+
+    @Override
+    public void onFinished(int sessionId, boolean success) {
+        // For a finished session, we can't get the session info. So use the
+        // packageName from our local cache.
+        SparseArray<PackageUserKey> activeSessions = getActiveSessionMap();
+        PackageUserKey key = activeSessions.get(sessionId);
+        activeSessions.remove(sessionId);
+
+        if (key != null && key.mPackageName != null) {
+            String packageName = key.mPackageName;
+            PackageInstallInfo info = PackageInstallInfo.fromState(
+                    success ? STATUS_INSTALLED : STATUS_FAILED,
+                    packageName, key.mUser);
+            mCallback.onPackageStateChanged(info);
+
+            if (!success && mInstallerCompat.promiseIconAddedForId(sessionId)) {
+                mCallback.onSessionFailure(packageName, key.mUser);
+                // If it is successful, the id is removed in the the package added flow.
+                mInstallerCompat.removePromiseIconId(sessionId);
+            }
+        }
+    }
+
+    @Override
+    public void onProgressChanged(int sessionId, float progress) {
+        SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+        if (session != null && session.getAppPackageName() != null) {
+            mCallback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(session));
+        }
+    }
+
+    @Override
+    public void onActiveChanged(int sessionId, boolean active) { }
+
+    @Override
+    public void onBadgingChanged(int sessionId) {
+        SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId);
+        if (sessionInfo != null) {
+            mInstallerCompat.tryQueuePromiseAppIcon(sessionInfo);
+        }
+    }
+
+    private SessionInfo pushSessionDisplayToLauncher(int sessionId) {
+        SessionInfo session = mInstallerCompat.getVerifiedSessionInfo(sessionId);
+        if (session != null && session.getAppPackageName() != null) {
+            PackageUserKey key =
+                    new PackageUserKey(session.getAppPackageName(), getUserHandle(session));
+            getActiveSessionMap().put(session.getSessionId(), key);
+            mCallback.onUpdateSessionDisplay(key, session);
+            return session;
+        }
+        return null;
+    }
+
+    private SparseArray<PackageUserKey> getActiveSessionMap() {
+        if (mActiveSessions == null) {
+            mActiveSessions = new SparseArray<>();
+            mInstallerCompat.getActiveSessions().forEach(
+                    (key, si) -> mActiveSessions.put(si.getSessionId(), key));
+        }
+        return mActiveSessions;
+    }
+
+    public void unregister() {
+        mInstallerCompat.unregister(this);
+    }
+
+    public interface Callback {
+
+        void onSessionFailure(String packageName, UserHandle user);
+
+        void onUpdateSessionDisplay(PackageUserKey key, SessionInfo info);
+
+        void onPackageStateChanged(PackageInstallInfo info);
+
+        void onInstallSessionCreated(PackageInstallInfo info);
+    }
+}
diff --git a/src/com/android/launcher3/pm/PackageInstallInfo.java b/src/com/android/launcher3/pm/PackageInstallInfo.java
new file mode 100644
index 0000000..6776ec4
--- /dev/null
+++ b/src/com/android/launcher3/pm/PackageInstallInfo.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.launcher3.pm;
+
+import android.content.ComponentName;
+import android.content.pm.PackageInstaller;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+public final class PackageInstallInfo {
+
+    public static final int STATUS_INSTALLED = 0;
+    public static final int STATUS_INSTALLING = 1;
+    public static final int STATUS_FAILED = 2;
+
+    public final ComponentName componentName;
+    public final String packageName;
+    public final int state;
+    public final int progress;
+    public final UserHandle user;
+
+    private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) {
+        this.state = STATUS_INSTALLING;
+        this.packageName = info.getAppPackageName();
+        this.componentName = new ComponentName(packageName, "");
+        this.progress = (int) (info.getProgress() * 100f);
+        this.user = PackageInstallerCompat.getUserHandle(info);
+    }
+
+    public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) {
+        this.state = state;
+        this.packageName = packageName;
+        this.componentName = new ComponentName(packageName, "");
+        this.progress = progress;
+        this.user = user;
+    }
+
+    public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) {
+        return new PackageInstallInfo(info);
+    }
+
+    public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
+        return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
+    }
+
+}
diff --git a/src/com/android/launcher3/pm/PackageInstallerCompat.java b/src/com/android/launcher3/pm/PackageInstallerCompat.java
new file mode 100644
index 0000000..c7b27d9
--- /dev/null
+++ b/src/com/android/launcher3/pm/PackageInstallerCompat.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 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.pm;
+
+import static com.android.launcher3.Utilities.getPrefs;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import com.android.launcher3.SessionCommitReceiver;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+public class PackageInstallerCompat {
+
+    // Set<String> of session ids of promise icons that have been added to the home screen
+    // as FLAG_PROMISE_NEW_INSTALLS.
+    protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
+
+    private static final Object sInstanceLock = new Object();
+    private static final boolean DEBUG = false;
+    private static PackageInstallerCompat sInstance;
+    private final LauncherApps mLauncherApps;
+    private final Context mAppContext;
+    private final IntSet mPromiseIconIds;
+
+    private final PackageInstaller mInstaller;
+    private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
+
+    public PackageInstallerCompat(Context context) {
+        mInstaller = context.getPackageManager().getPackageInstaller();
+        mAppContext = context.getApplicationContext();
+        mLauncherApps = context.getSystemService(LauncherApps.class);
+
+        mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
+                getPrefs(context).getString(PROMISE_ICON_IDS, "")));
+
+        cleanUpPromiseIconIds();
+    }
+
+    public static PackageInstallerCompat getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new PackageInstallerCompat(context);
+            }
+            return sInstance;
+        }
+    }
+
+    public static UserHandle getUserHandle(SessionInfo info) {
+        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
+    }
+
+    protected void cleanUpPromiseIconIds() {
+        IntArray existingIds = new IntArray();
+        for (SessionInfo info : getActiveSessions().values()) {
+            existingIds.add(info.getSessionId());
+        }
+        IntArray idsToRemove = new IntArray();
+
+        for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) {
+            if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) {
+                idsToRemove.add(mPromiseIconIds.getArray().get(i));
+            }
+        }
+        for (int i = idsToRemove.size() - 1; i >= 0; --i) {
+            mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
+        }
+    }
+
+    public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
+        HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
+        for (SessionInfo info : getAllVerifiedSessions()) {
+            activePackages.put(new PackageUserKey(info.getAppPackageName(), getUserHandle(info)),
+                    info);
+        }
+        return activePackages;
+    }
+
+    public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) {
+        for (SessionInfo info : getAllVerifiedSessions()) {
+            boolean match = pkg.equals(info.getAppPackageName());
+            if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) {
+                match = false;
+            }
+            if (match) {
+                return info;
+            }
+        }
+        return null;
+    }
+
+    private void updatePromiseIconPrefs() {
+        getPrefs(mAppContext).edit()
+                .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
+                .apply();
+    }
+
+    SessionInfo getVerifiedSessionInfo(int sessionId) {
+        return verify(mInstaller.getSessionInfo(sessionId));
+    }
+
+    private SessionInfo verify(SessionInfo sessionInfo) {
+        if (sessionInfo == null
+                || sessionInfo.getInstallerPackageName() == null
+                || TextUtils.isEmpty(sessionInfo.getAppPackageName())) {
+            return null;
+        }
+        String pkg = sessionInfo.getInstallerPackageName();
+        synchronized (mSessionVerifiedMap) {
+            if (!mSessionVerifiedMap.containsKey(pkg)) {
+                boolean hasSystemFlag = new PackageManagerHelper(mAppContext).getApplicationInfo(
+                        pkg, getUserHandle(sessionInfo), ApplicationInfo.FLAG_SYSTEM) != null;
+                mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
+            }
+        }
+        return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
+    }
+
+    public List<SessionInfo> getAllVerifiedSessions() {
+        List<SessionInfo> list = new ArrayList<>(Utilities.ATLEAST_Q
+                ? mLauncherApps.getAllPackageInstallerSessions()
+                : mInstaller.getAllSessions());
+        Iterator<SessionInfo> it = list.iterator();
+        while (it.hasNext()) {
+            if (verify(it.next()) == null) {
+                it.remove();
+            }
+        }
+        return list;
+    }
+
+    public boolean promiseIconAddedForId(int sessionId) {
+        return mPromiseIconIds.contains(sessionId);
+    }
+
+    public void removePromiseIconId(int sessionId) {
+        if (mPromiseIconIds.contains(sessionId)) {
+            mPromiseIconIds.getArray().removeValue(sessionId);
+            updatePromiseIconPrefs();
+        }
+    }
+
+    /**
+     * Add a promise app icon to the workspace iff:
+     * - The settings for it are enabled
+     * - The user installed the app
+     * - There is an app icon and label (For apps with no launching activity, no icon is provided).
+     * - The app is not already installed
+     * - A promise icon for the session has not already been created
+     */
+    void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
+        if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+                && SessionCommitReceiver.isEnabled(mAppContext)
+                && verify(sessionInfo) != null
+                && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
+                && sessionInfo.getAppIcon() != null
+                && !TextUtils.isEmpty(sessionInfo.getAppLabel())
+                && !mPromiseIconIds.contains(sessionInfo.getSessionId())
+                && new PackageManagerHelper(mAppContext).getApplicationInfo(
+                        sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
+            SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+            mPromiseIconIds.add(sessionInfo.getSessionId());
+            updatePromiseIconPrefs();
+        }
+    }
+
+    public InstallSessionTracker registerInstallTracker(
+            InstallSessionTracker.Callback callback, LooperExecutor executor) {
+        InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            mInstaller.registerSessionCallback(tracker, executor.getHandler());
+        } else {
+            mLauncherApps.registerPackageInstallerSessionCallback(executor, tracker);
+        }
+        return tracker;
+    }
+
+    void unregister(InstallSessionTracker tracker) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            mInstaller.unregisterSessionCallback(tracker);
+        } else {
+            mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
new file mode 100644
index 0000000..68ea6c4
--- /dev/null
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 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.pm;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.PinItemRequest;
+import android.content.pm.ShortcutInfo;
+import android.os.Build;
+import android.os.Parcelable;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.LauncherIcons;
+
+public class PinRequestHelper {
+
+    /**
+     * request.accept() will initiate the following flow:
+     *      -> go-to-system-process for actual processing (a)
+     *      -> callback-to-launcher on UI thread (b)
+     *      -> post callback on the worker thread (c)
+     *      -> Update model and unpin (in system) any shortcut not in out model. (d)
+     *
+     * Note that (b) will take at-least one frame as it involves posting callback from binder
+     * thread to UI thread.
+     * If (d) happens before we add this shortcut to our model, we will end up unpinning
+     * the shortcut in the system.
+     * Here its the caller's responsibility to add the newly created WorkspaceItemInfo immediately
+     * to the model (which may involves a single post-to-worker-thread). That will guarantee
+     * that (d) happens after model is updated.
+     */
+    @Nullable
+    @TargetApi(Build.VERSION_CODES.O)
+    public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
+            Context context, final PinItemRequest request, final long acceptDelay) {
+        if (request != null && request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT
+                && request.isValid()) {
+
+            if (acceptDelay <= 0) {
+                if (!request.accept()) {
+                    return null;
+                }
+            } else {
+                // Block the worker thread until the accept() is called.
+                MODEL_EXECUTOR.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            Thread.sleep(acceptDelay);
+                        } catch (InterruptedException e) {
+                            // Ignore
+                        }
+                        if (request.isValid()) {
+                            request.accept();
+                        }
+                    }
+                });
+            }
+
+            ShortcutInfo si = request.getShortcutInfo();
+            WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
+            // Apply the unbadged icon and fetch the actual icon asynchronously.
+            LauncherIcons li = LauncherIcons.obtain(context);
+            info.applyFrom(li.createShortcutIcon(si, false /* badged */));
+            li.recycle();
+            LauncherAppState.getInstance(context).getModel()
+                    .updateAndBindWorkspaceItem(info, si);
+            return info;
+        } else {
+            return null;
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    public static PinItemRequest getPinItemRequest(Intent intent) {
+        Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
+        return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
+    }
+}
diff --git a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
similarity index 66%
rename from src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
rename to src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index ace5691..0922e41 100644
--- a/src/com/android/launcher3/compat/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -14,29 +14,40 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.compat;
+package com.android.launcher3.pm;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.widget.Toast;
 
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Wrapper class for representing a shortcut configure activity.
@@ -87,9 +98,9 @@
             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
         } catch (SecurityException e) {
             Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
-                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
-                    "or use the exported attribute for this activity.", e);
+            Log.e(TAG, "Launcher does not have the permission to launch " + intent
+                    + ". Make sure to create a MAIN intent-filter for the corresponding activity "
+                    + "or use the exported attribute for this activity.", e);
         }
         return false;
     }
@@ -106,7 +117,7 @@
 
         private final ActivityInfo mInfo;
 
-        public ShortcutConfigActivityInfoVL(ActivityInfo info) {
+        ShortcutConfigActivityInfoVL(ActivityInfo info) {
             super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
             mInfo = info;
         }
@@ -158,4 +169,46 @@
             }
         }
     }
+
+    public static List<ShortcutConfigActivityInfo> queryList(
+            Context context, @Nullable PackageUserKey packageUser) {
+        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
+        UserHandle myUser = Process.myUserHandle();
+
+        if (Utilities.ATLEAST_OREO) {
+            final List<UserHandle> users;
+            final String packageName;
+            if (packageUser == null) {
+                users = UserManagerCompat.getInstance(context).getUserProfiles();
+                packageName = null;
+            } else {
+                users = new ArrayList<>(1);
+                users.add(packageUser.mUser);
+                packageName = packageUser.mPackageName;
+            }
+            LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+            for (UserHandle user : users) {
+                boolean ignoreTargetSdk = myUser.equals(user);
+                for (LauncherActivityInfo activityInfo :
+                        launcherApps.getShortcutConfigActivityList(packageName, user)) {
+                    if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
+                            >= Build.VERSION_CODES.O) {
+                        result.add(new ShortcutConfigActivityInfoVO(activityInfo));
+                    }
+                }
+            }
+        } else {
+            if (packageUser == null || packageUser.mUser.equals(myUser)) {
+                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+                if (packageUser != null) {
+                    intent.setPackage(packageUser.mPackageName);
+                }
+                for (ResolveInfo info :
+                        context.getPackageManager().queryIntentActivities(intent, 0)) {
+                    result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
+                }
+            }
+        }
+        return result;
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index c2aabca..4833c26 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -65,6 +66,7 @@
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
@@ -194,6 +196,9 @@
      * @return the container if shown or null.
      */
     public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon");
+        }
         Launcher launcher = Launcher.getLauncher(icon.getContext());
         if (getOpen(launcher) != null) {
             // There is already an items container open, so don't open this one.
@@ -235,6 +240,9 @@
 
     protected void populateAndShow(
             BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow");
+        }
         PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
         populateAndShow(icon,
                 popupDataProvider.getShortcutCountForItem(item),
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 516c251..8296cf9 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,10 +15,16 @@
  */
 package com.android.launcher3.testing;
 
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.os.Bundle;
+import android.os.Debug;
+import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -29,7 +35,10 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 
 public class TestInformationHandler implements ResourceBasedOverride {
 
@@ -42,14 +51,14 @@
     protected DeviceProfile mDeviceProfile;
     protected LauncherAppState mLauncherAppState;
     protected Launcher mLauncher;
+    private static LinkedList mLeaks;
 
     public void init(Context context) {
         mContext = context;
         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) {
@@ -76,7 +85,7 @@
             }
 
             case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
                 break;
             }
 
@@ -106,21 +115,93 @@
                             mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                             deferUpdatesFlags);
-                } catch (ExecutionException e) {
-                    throw new RuntimeException(e);
-                } catch (InterruptedException e) {
+                } catch (ExecutionException | InterruptedException e) {
                     throw new RuntimeException(e);
                 }
                 break;
             }
 
-            case TestProtocol.REQUEST_ALLOCATED_MEMORY: {
-                final Runtime runtime = Runtime.getRuntime();
-                response.putLong(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        runtime.totalMemory() - runtime.freeMemory());
+            case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
+                try {
+                    final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
+                            mLauncher.getAppsView().getActiveRecyclerView().getCurrentScrollY())
+                            .get();
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                            deferUpdatesFlags);
+                } catch (ExecutionException | InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                break;
+            }
+
+            case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+                runGcAndFinalizersSync();
+                Debug.MemoryInfo mem = new Debug.MemoryInfo();
+                Debug.getMemoryInfo(mem);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss());
+                break;
+            }
+
+            case TestProtocol.REQUEST_JAVA_LEAK: {
+                if (mLeaks == null) mLeaks = new LinkedList();
+
+                // Allocate and dirty the memory.
+                final int leakSize = 1024 * 1024;
+                final byte[] bytes = new byte[leakSize];
+                for (int i = 0; i < leakSize; i += 239) {
+                    bytes[i] = (byte) (i % 256);
+                }
+                mLeaks.add(bytes);
+                break;
+            }
+
+            case TestProtocol.REQUEST_NATIVE_LEAK: {
+                if (mLeaks == null) mLeaks = new LinkedList();
+
+                // Allocate and dirty a bitmap.
+                final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888);
+                bitmap.eraseColor(Color.RED);
+                mLeaks.add(bitmap);
+                break;
+            }
+
+            case TestProtocol.REQUEST_VIEW_LEAK: {
+                if (mLeaks == null) mLeaks = new LinkedList();
+
+                mLeaks.add(new View(mContext));
                 break;
             }
         }
         return response;
     }
+
+    protected boolean isLauncherInitialized() {
+        return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
+                || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
+    }
+
+    private static void runGcAndFinalizersSync() {
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+
+        final CountDownLatch fence = new CountDownLatch(1);
+        new Object() {
+            @Override
+            protected void finalize() throws Throwable {
+                try {
+                    fence.countDown();
+                } finally {
+                    super.finalize();
+                }
+            }
+        };
+        try {
+            do {
+                Runtime.getRuntime().gc();
+                Runtime.getRuntime().runFinalization();
+            } while (!fence.await(100, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index d569d40..5110977 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -20,13 +20,10 @@
  * Protocol for custom accessibility events for communication with UI Automation tests.
  */
 public final class TestProtocol {
-    public static final String GET_SCROLL_MESSAGE = "TAPL_GET_SCROLL";
-    public static final String SCROLL_Y_FIELD = "scrollY";
     public static final String STATE_FIELD = "state";
     public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
     public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
     public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
-    public static final String RESPONSE_MESSAGE_POSTFIX = "_RESPONSE";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -71,9 +68,14 @@
     public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
+    public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
     public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
     public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
-    public static final String REQUEST_ALLOCATED_MEMORY = "allocated-memory";
+    public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
+    public static final String REQUEST_JAVA_LEAK = "java-leak";
+    public static final String REQUEST_NATIVE_LEAK = "native-leak";
+    public static final String REQUEST_VIEW_LEAK = "view-leak";
+    public static final String REQUEST_RECENT_TASKS_LIST = "recent-tasks-list";
 
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
@@ -82,6 +84,5 @@
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
     public static final String APP_NOT_DISABLED = "b/139891609";
-    public static final String ALL_APPS_UPON_RECENTS = "b/139941530";
-    public static final String STABLE_STATE_MISMATCH = "b/140311911";
+    public static final String NO_CONTEXT_MENU = "b/141770616";
 }
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 03493a5..455af5a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -25,8 +25,6 @@
 import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
 
 import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -52,9 +50,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 6cd2b2d..86d2b39 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnLongClickListener;
 
@@ -33,6 +34,9 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.Arrays;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -75,10 +79,19 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1");
+        }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2");
+        }
         // When we have exited all apps or are in transition, disregard long clicks
         if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3");
+        }
         if (launcher.getWorkspace().isSwitchingState()) return false;
 
         // Start the drag
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/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 4ae84d8..0f81520 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -17,7 +17,6 @@
  */
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -25,11 +24,7 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
 import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
 
 import java.util.function.Consumer;
 
@@ -37,7 +32,8 @@
  * {@link BroadcastReceiver} which watches configuration changes and
  * notifies the callback in case changes which affect the device profile occur.
  */
-public class ConfigMonitor extends BroadcastReceiver implements DisplayListener {
+public class ConfigMonitor extends BroadcastReceiver implements
+        DefaultDisplay.DisplayInfoChangeListener {
 
     private static final String TAG = "ConfigMonitor";
 
@@ -61,24 +57,19 @@
         mFontScale = config.fontScale;
         mDensity = config.densityDpi;
 
-        Display display = getDefaultDisplay(context);
-        mDisplayId = display.getDisplayId();
+        DefaultDisplay display = DefaultDisplay.INSTANCE.get(context);
+        display.addChangeListener(this);
+        DefaultDisplay.Info displayInfo = display.getInfo();
+        mDisplayId = displayInfo.id;
 
-        mRealSize = new Point();
-        display.getRealSize(mRealSize);
-
-        mSmallestSize = new Point();
-        mLargestSize = new Point();
-        display.getCurrentSizeRange(mSmallestSize, mLargestSize);
+        mRealSize = new Point(displayInfo.realSize);
+        mSmallestSize = new Point(displayInfo.smallestSize);
+        mLargestSize = new Point(displayInfo.largestSize);
 
         mCallback = callback;
 
         // Listen for configuration change
         mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
-
-        // Listen for display manager change
-        mContext.getSystemService(DisplayManager.class)
-                .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
     }
 
     @Override
@@ -91,26 +82,19 @@
     }
 
     @Override
-    public void onDisplayAdded(int displayId) { }
-
-    @Override
-    public void onDisplayRemoved(int displayId) { }
-
-    @Override
-    public void onDisplayChanged(int displayId) {
-        if (displayId != mDisplayId) {
+    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+        if (info.id != mDisplayId) {
             return;
         }
-        Display display = getDefaultDisplay(mContext);
-        display.getRealSize(mTmpPoint1);
-
+        mTmpPoint1.set(info.realSize.x, info.realSize.y);
         if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) {
             Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1));
             notifyChange();
             return;
         }
 
-        display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2);
+        mTmpPoint1.set(info.smallestSize.x, info.smallestSize.y);
+        mTmpPoint2.set(info.largestSize.x, info.largestSize.y);
         if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) {
             Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
                     mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2));
@@ -126,14 +110,11 @@
         }
     }
 
-    private Display getDefaultDisplay(Context context) {
-        return context.getSystemService(WindowManager.class).getDefaultDisplay();
-    }
-
     public void unregister() {
         try {
             mContext.unregisterReceiver(this);
-            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+            DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
+            display.removeChangeListener(this);
         } catch (Exception e) {
             Log.e(TAG, "Failed to unregister config monitor", e);
         }
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 4080e21..8529d50 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Handler;
 import android.os.Message;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
@@ -124,6 +125,8 @@
         public final Point smallestSize;
         public final Point largestSize;
 
+        public final DisplayMetrics metrics;
+
         private Info(Context context) {
             Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
 
@@ -138,6 +141,9 @@
             largestSize = new Point();
             display.getRealSize(realSize);
             display.getCurrentSizeRange(smallestSize, largestSize);
+
+            metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
         }
 
         private boolean hasDifferentSize(Info info) {
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index fe9c2c4..cf4e8c7 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.uioverrides.DejankBinderTracker;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.concurrent.ExecutionException;
@@ -41,7 +42,8 @@
     public T get(Context context) {
         if (mValue == null) {
             if (Looper.myLooper() == Looper.getMainLooper()) {
-                mValue = mProvider.get(context.getApplicationContext());
+                mValue = DejankBinderTracker.whitelistIpcs(() ->
+                        mProvider.get(context.getApplicationContext()));
             } else {
                 try {
                     return MAIN_EXECUTOR.submit(() -> get(context)).get();
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/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index ef4307e..7b4e0c6 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -24,6 +24,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -34,6 +35,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
+import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -46,8 +48,8 @@
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.compat.LauncherAppsCompat;
 
 import java.net.URISyntaxException;
 import java.util.List;
@@ -61,12 +63,12 @@
 
     private final Context mContext;
     private final PackageManager mPm;
-    private final LauncherAppsCompat mLauncherApps;
+    private final LauncherApps mLauncherApps;
 
     public PackageManagerHelper(Context context) {
         mContext = context;
         mPm = context.getPackageManager();
-        mLauncherApps = LauncherAppsCompat.getInstance(context);
+        mLauncherApps = context.getSystemService(LauncherApps.class);
     }
 
     /**
@@ -74,8 +76,8 @@
      * guarantee that the app is on SD card.
      */
     public boolean isAppOnSdcard(String packageName, UserHandle user) {
-        ApplicationInfo info = mLauncherApps.getApplicationInfo(
-                packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
+        ApplicationInfo info = getApplicationInfo(
+                packageName, user, PackageManager.MATCH_UNINSTALLED_PACKAGES);
         return info != null && (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
@@ -84,10 +86,47 @@
      * {@link android.app.admin.DevicePolicyManager#isPackageSuspended}.
      */
     public boolean isAppSuspended(String packageName, UserHandle user) {
-        ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, 0, user);
+        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
         return info != null && isAppSuspended(info);
     }
 
+    /**
+     * Returns the application info for the provided package or null
+     */
+    public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
+        if (Utilities.ATLEAST_OREO) {
+            try {
+                ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+                return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+                        ? null : info;
+            } catch (PackageManager.NameNotFoundException e) {
+                return null;
+            }
+        } else {
+            final boolean isPrimaryUser = Process.myUserHandle().equals(user);
+            if (!isPrimaryUser && (flags == 0)) {
+                // We are looking for an installed app on a secondary profile. Prior to O, the only
+                // entry point for work profiles is through the LauncherActivity.
+                List<LauncherActivityInfo> activityList =
+                        mLauncherApps.getActivityList(packageName, user);
+                return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
+            }
+            try {
+                ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
+                // There is no way to check if the app is installed for managed profile. But for
+                // primary profile, we can still have this check.
+                if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
+                        || !info.enabled) {
+                    return null;
+                }
+                return info;
+            } catch (PackageManager.NameNotFoundException e) {
+                // Package not found
+                return null;
+            }
+        }
+    }
+
     public boolean isSafeMode() {
         return mContext.getPackageManager().isSafeMode();
     }
@@ -202,8 +241,7 @@
         }
         if (componentName != null) {
             try {
-                mLauncherApps.showAppDetailsForProfile(
-                        componentName, info.user, sourceBounds, opts);
+                mLauncherApps.startAppDetailsActivity(componentName, info.user, sourceBounds, opts);
             } catch (SecurityException | ActivityNotFoundException e) {
                 Toast.makeText(mContext, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
                 Log.e(TAG, "Unable to launch settings", e);
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index 041c708..f243ca6 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -30,7 +30,7 @@
         update(packageName, user);
     }
 
-    private void update(String packageName, UserHandle user) {
+    public void update(String packageName, UserHandle user) {
         mPackageName = packageName;
         mUser = user;
         mHashCode = Arrays.hashCode(new Object[] {packageName, user});
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 792d69f..49c97da 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -19,41 +19,61 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.shortcuts.ShortcutKey;
 
 public class ShortcutUtil {
-  public static boolean supportsShortcuts(ItemInfo info) {
-    return isActive(info) && (isApp(info) || isPinnedShortcut(info));
-  }
+    /**
+     * Returns true when we should show shortcut menu for the item.
+     */
+    public static boolean supportsShortcuts(ItemInfo info) {
+        return isActive(info) && (isApp(info) || isPinnedShortcut(info));
+    }
 
-  public static boolean supportsDeepShortcuts(ItemInfo info) {
-    return isActive(info) && isApp(info);
-  }
+    /**
+     * Returns true when we should show depp shortcuts in shortcut menu for the item.
+     */
+    public static boolean supportsDeepShortcuts(ItemInfo info) {
+        return isActive(info) && isApp(info);
+    }
 
-  public static String getShortcutIdIfPinnedShortcut(ItemInfo info) {
-    return isActive(info) && isPinnedShortcut(info) ?
-        ShortcutKey.fromItemInfo(info).getId() : null;
-  }
+    /**
+     * Returns the shortcut id if the item is a pinned shortcut.
+     */
+    public static String getShortcutIdIfPinnedShortcut(ItemInfo info) {
+        return isActive(info) && isPinnedShortcut(info)
+                ? ShortcutKey.fromItemInfo(info).getId() : null;
+    }
 
-  public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) {
-    return isActive(info) && isPinnedShortcut(info) ?
-        ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY;
-  }
+    /**
+     * Returns the person keys associated with the item. (Has no function right now.)
+     */
+    public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) {
+        return isActive(info) && isPinnedShortcut(info)
+                ? ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY;
+    }
 
-  private static boolean isActive(ItemInfo info) {
-    boolean isLoading = info instanceof WorkspaceItemInfo
-        && ((WorkspaceItemInfo) info).hasPromiseIconUi();
-    return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
-  }
+    /**
+     * Returns true if the item is a deep shortcut.
+     */
+    public static boolean isDeepShortcut(ItemInfo info) {
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                && info instanceof WorkspaceItemInfo;
+    }
 
-  private static boolean isApp(ItemInfo info) {
-    return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-  }
+    private static boolean isActive(ItemInfo info) {
+        boolean isLoading = info instanceof WorkspaceItemInfo
+                && ((WorkspaceItemInfo) info).hasPromiseIconUi();
+        return !isLoading && !info.isDisabled() && !WidgetsModel.GO_DISABLE_WIDGETS;
+    }
 
-  private static boolean isPinnedShortcut(ItemInfo info) {
-    return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-        && info.container != ItemInfo.NO_ID
-        && info instanceof WorkspaceItemInfo;
-  }
-}
\ No newline at end of file
+    private static boolean isApp(ItemInfo info) {
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+    }
+
+    private static boolean isPinnedShortcut(ItemInfo info) {
+        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                && info.container != ItemInfo.NO_ID
+                && info instanceof WorkspaceItemInfo;
+    }
+}
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
new file mode 100644
index 0000000..465a0e8
--- /dev/null
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import java.util.function.Consumer;
+
+public class SimpleBroadcastReceiver extends BroadcastReceiver {
+
+    private final Consumer<Intent> mIntentConsumer;
+
+    public SimpleBroadcastReceiver(Consumer<Intent> intentConsumer) {
+        mIntentConsumer = intentConsumer;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mIntentConsumer.accept(intent);
+    }
+
+    /**
+     * Helper method to register multiple actions
+     */
+    public void register(Context context, String... actions) {
+        IntentFilter filter = new IntentFilter();
+        for (String action : actions) {
+            filter.addAction(action);
+        }
+        context.registerReceiver(this, filter);
+    }
+}
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index f8d1632..a133f01 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.view.inputmethod.InputMethodManager;
+import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Utility class for offloading some class from UI thread
@@ -52,15 +53,22 @@
                 .sendToTarget();
     }
 
+    public static void setBackButtonAlphaAsync(Context context, AsyncCommand command, float alpha,
+            boolean animate) {
+        runAsyncCommand(context, command, Float.floatToIntBits(alpha), animate ? 1 : 0);
+    }
+
     public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
         Message.obtain(getHandler(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
     }
 
     private static class UiCallbacks implements Handler.Callback {
 
+        private final Context mContext;
         private final InputMethodManager mIMM;
 
         UiCallbacks(Context context) {
+            mContext = context;
             mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
         }
 
@@ -74,7 +82,7 @@
                     ((Activity) message.obj).setRequestedOrientation(message.arg1);
                     return true;
                 case MSG_RUN_COMMAND:
-                    ((AsyncCommand) message.obj).execute(message.arg1, message.arg2);
+                    ((AsyncCommand) message.obj).execute(mContext, message.arg1, message.arg2);
                     return true;
             }
             return false;
@@ -82,7 +90,6 @@
     }
 
     public interface AsyncCommand {
-
-        void execute(int arg1, int arg2);
+        void execute(Context proxy, int arg1, int arg2);
     }
 }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 799762d..2a4c5a7 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -29,6 +29,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
@@ -41,6 +42,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.TouchController;
@@ -261,6 +263,10 @@
             }
             case ACTION_CANCEL:
             case ACTION_UP:
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE,
+                            "BaseDragLayer.ACTION_UP/CANCEL " + ev);
+                }
                 mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
                 mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
                 break;
@@ -459,7 +465,7 @@
     }
 
     public void dump(String prefix, PrintWriter writer) {
-        writer.println(prefix + "DragLayer");
+        writer.println(prefix + "DragLayer:");
         if (mActiveController != null) {
             writer.println(prefix + "\tactiveController: " + mActiveController);
             mActiveController.dump(prefix + "\t", writer);
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 3145893..45c0d90 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -720,7 +720,7 @@
      */
     @UiThread
     public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
-        IconLoadResult result = new IconLoadResult();
+        IconLoadResult result = new IconLoadResult(info);
         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
             RectF position = new RectF();
             getLocationBoundsForView(l, v, isOpening, position);
@@ -749,10 +749,13 @@
 
         // Get the drawable on the background thread
         boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal;
-        view.mIconLoadResult = sIconLoadResult;
-        if (shouldLoadIcon && view.mIconLoadResult == null) {
-            view.mIconLoadResult = fetchIcon(launcher, originalView,
-                    (ItemInfo) originalView.getTag(), isOpening);
+        if (shouldLoadIcon) {
+            if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) {
+                view.mIconLoadResult = sIconLoadResult;
+            } else {
+                view.mIconLoadResult = fetchIcon(launcher, originalView,
+                        (ItemInfo) originalView.getTag(), isOpening);
+            }
         }
         sIconLoadResult = null;
 
@@ -764,11 +767,6 @@
         // Match the position of the original view.
         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
 
-        // Must be called after matchPositionOf so that we know what size to load.
-        if (shouldLoadIcon) {
-            view.checkIconResult(originalView, isOpening);
-        }
-
         // We need to add it to the overlay, but keep it invisible until animation starts..
         view.setVisibility(INVISIBLE);
         parent.addView(view);
@@ -795,6 +793,14 @@
                 view.finish(dragLayer);
             }
         };
+
+        // Must be called after matchPositionOf so that we know what size to load.
+        // Must be called after the fastFinish listener and end runnable is created so that
+        // the icon is not left in a hidden state.
+        if (shouldLoadIcon) {
+            view.checkIconResult(originalView, isOpening);
+        }
+
         return view;
     }
 
@@ -894,10 +900,15 @@
     }
 
     private static class IconLoadResult {
+        final ItemInfo itemInfo;
         Drawable drawable;
         Drawable badge;
         int iconOffset;
         Runnable onIconLoaded;
         boolean isIconLoaded;
+
+        public IconLoadResult(ItemInfo itemInfo) {
+            this.itemInfo = itemInfo;
+        }
     }
 }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 465df44..88d34da 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -158,7 +158,7 @@
                 R.drawable.ic_palette : R.drawable.ic_wallpaper;
         options.add(new OptionItem(resString, resDrawable,
                 ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker));
-        if (!FeatureFlags.GO_DISABLE_WIDGETS) {
+        if (!WidgetsModel.GO_DISABLE_WIDGETS) {
             options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
                     ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked));
         }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index da1df3f..9f59d78 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -288,6 +288,7 @@
             anim.addUpdateListener((v) -> invalidate(invalidateRegion));
             getOverlay().add(drawable);
             anim.start();
+            return true;
         }
         return value;
     }
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 62b6903..6e21a41 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.widget;
 
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 
 /**
  * Meta data used for late binding of the short cuts.
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index dc4af8c..6944879 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.graphics.DrawableFactory;
+import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.model.WidgetItem;
 
 /**
@@ -80,6 +81,7 @@
     private Bitmap mDeferredBitmap;
 
     protected final BaseActivity mActivity;
+    protected DeviceProfile mDeviceProfile;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -93,6 +95,7 @@
         super(context, attrs, defStyle);
 
         mActivity = BaseActivity.fromContext(context);
+        mDeviceProfile = mActivity.getDeviceProfile();
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         setContainerWidth();
@@ -102,8 +105,7 @@
     }
 
     private void setContainerWidth() {
-        DeviceProfile profile = mActivity.getDeviceProfile();
-        mCellSize = (int) (profile.cellWidthPx * WIDTH_SCALE);
+        mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
         mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
     }
 
@@ -180,8 +182,10 @@
             return;
         }
         if (bitmap != null) {
-            mWidgetImage.setBitmap(bitmap, DrawableFactory.INSTANCE.get(getContext())
-                    .getBadgeForUser(mItem.user, getContext()));
+            mWidgetImage.setBitmap(bitmap,
+                    DrawableFactory.INSTANCE.get(getContext()).getBadgeForUser(mItem.user,
+                            getContext(), BaseIconFactory.getBadgeSizeForIconSize(
+                                    mDeviceProfile.allAppsIconSizePx)));
             if (mAnimatePreview) {
                 mWidgetImage.setAlpha(0f);
                 ViewPropertyAnimator anim = mWidgetImage.animate();
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
index cf3e26d..b3569f2 100644
--- a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -50,13 +50,18 @@
     public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
             new MainThreadInitializedObject<>(CustomWidgetManager::new);
 
-    private final List<CustomWidgetPlugin> mPlugins;
+    /**
+     * auto provider Id is an ever-increasing number that serves as the providerId whenever a new
+     * custom widget has been connected.
+     */
+    private int mAutoProviderId = 0;
+    private final SparseArray<CustomWidgetPlugin> mPlugins;
     private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
     private final SparseArray<ComponentName> mWidgetsIdMap;
     private Consumer<PackageUserKey> mWidgetRefreshCallback;
 
     private CustomWidgetManager(Context context) {
-        mPlugins = new ArrayList<>();
+        mPlugins = new SparseArray<>();
         mCustomWidgets = new ArrayList<>();
         mWidgetsIdMap = new SparseArray<>();
         PluginManagerWrapper.INSTANCE.get(context)
@@ -65,25 +70,28 @@
 
     @Override
     public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
-        mPlugins.add(plugin);
+        mPlugins.put(mAutoProviderId, plugin);
         List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
                 .getInstalledProvidersForProfile(Process.myUserHandle());
         if (providers.isEmpty()) return;
         Parcel parcel = Parcel.obtain();
         providers.get(0).writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
-        CustomAppWidgetProviderInfo info = newInfo(plugin, parcel, context);
+        CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context);
         parcel.recycle();
         mCustomWidgets.add(info);
-        mWidgetsIdMap.put(plugin.getProviderId(), info.provider);
+        mWidgetsIdMap.put(mAutoProviderId, info.provider);
         mWidgetRefreshCallback.accept(null);
+        mAutoProviderId++;
     }
 
     @Override
     public void onPluginDisconnected(CustomWidgetPlugin plugin) {
-        mPlugins.remove(plugin);
-        mCustomWidgets.remove(getWidgetProvider(plugin.getProviderId()));
-        mWidgetsIdMap.remove(plugin.getProviderId());
+        int providerId = findProviderId(plugin);
+        if (providerId == -1) return;
+        mPlugins.remove(providerId);
+        mCustomWidgets.remove(getWidgetProvider(providerId));
+        mWidgetsIdMap.remove(providerId);
     }
 
     /**
@@ -98,7 +106,7 @@
      */
     public void onViewCreated(LauncherAppWidgetHostView view) {
         CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
-        CustomWidgetPlugin plugin = findPlugin(info.providerId);
+        CustomWidgetPlugin plugin = mPlugins.get(info.providerId);
         if (plugin == null) return;
         plugin.onViewCreated(view);
     }
@@ -135,17 +143,14 @@
         return null;
     }
 
-    private static CustomAppWidgetProviderInfo newInfo(
-            CustomWidgetPlugin plugin, Parcel parcel, Context context) {
-        int providerId = plugin.getProviderId();
+    private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin,
+            Parcel parcel, Context context) {
         CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(
                 parcel, false, providerId);
         info.provider = new ComponentName(
                 context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
 
         info.label = plugin.getLabel();
-        info.icon = plugin.getIcon();
-        info.previewImage = plugin.getPreviewImage();
         info.resizeMode = plugin.getResizeMode();
 
         info.spanX = plugin.getSpanX();
@@ -155,9 +160,13 @@
         return info;
     }
 
-    @Nullable
-    private CustomWidgetPlugin findPlugin(int providerId) {
-        return mPlugins.stream().filter((p) -> p.getProviderId() == providerId).findFirst()
-                .orElse(null);
+    private int findProviderId(CustomWidgetPlugin plugin) {
+        for (int i = 0; i < mPlugins.size(); i++) {
+            int providerId = mPlugins.keyAt(i);
+            if (mPlugins.get(providerId) == plugin) {
+                return providerId;
+            }
+        }
+        return -1;
     }
 }
diff --git a/src_build_config/BuildConfig.java b/src_build_config/BuildConfig.java
index 36d7f4b..49aadf6 100644
--- a/src_build_config/BuildConfig.java
+++ b/src_build_config/BuildConfig.java
@@ -18,4 +18,5 @@
 
 public final class BuildConfig {
   public static final String APPLICATION_ID = "com.android.launcher3";
+  public static final boolean DEBUG = false;
 }
diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_flags/com/android/launcher3/config/FeatureFlags.java
deleted file mode 100644
index 73c6996..0000000
--- a/src_flags/com/android/launcher3/config/FeatureFlags.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.config;
-
-import android.content.Context;
-
-/**
- * Defines a set of flags used to control various launcher behaviors
- */
-public final class FeatureFlags extends BaseFlags {
-    private FeatureFlags() {
-        // Prevent instantiation
-    }
-}
diff --git a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
new file mode 100644
index 0000000..15a0ffa
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java
@@ -0,0 +1,54 @@
+/*
+ * 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.systemui.plugins;
+
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin interface which sends app launch events.
+ */
+@ProvidesInterface(action = AppLaunchEventsPlugin.ACTION, version = AppLaunchEventsPlugin.VERSION)
+public interface AppLaunchEventsPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_APP_EVENTS";
+    int VERSION = 1;
+
+    /**
+     * Receives onStartShortcut event from
+     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+     */
+    void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container);
+
+    /**
+     * Receives onStartApp event from
+     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+     */
+    void onStartApp(ComponentName componentName, UserHandle user, String container);
+
+    /**
+     * Receives onDismissApp event from
+     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+     */
+    void onDismissApp(ComponentName componentName, UserHandle user, String container);
+
+    /**
+     * Receives onReturnedToHome event from
+     * {@link com.android.launcher3.appprediction.PredictionAppTracker}.
+     */
+    void onReturnedToHome();
+}
diff --git a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
index 77ad7ea..56ebcc5 100644
--- a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -30,28 +30,11 @@
     int VERSION = 1;
 
     /**
-     * An unique identifier for this widget. Must be a non-negative integer.
-     */
-    int getProviderId();
-
-    /**
      * The label to display to the user in the AppWidget picker.
      */
     String getLabel();
 
     /**
-     * A preview of what the AppWidget will look like after it's configured.
-     * If not supplied, the AppWidget's icon will be used.
-     */
-    int getPreviewImage();
-
-    /**
-     * The icon to display for this AppWidget in the AppWidget picker. If not supplied in the
-     * xml, the application icon will be used.
-     */
-    int getIcon();
-
-    /**
      * The default width of the widget when added to a host, in dp. The widget will get
      * at least this width, and will often be given more, depending on the host.
      */
diff --git a/src_plugins/com/android/systemui/plugins/OverlayPlugin.java b/src_plugins/com/android/systemui/plugins/OverlayPlugin.java
new file mode 100644
index 0000000..1edb692
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverlayPlugin.java
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui.plugins;
+
+import android.app.Activity;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.shared.LauncherExterns;
+import com.android.systemui.plugins.shared.LauncherOverlayManager;
+
+/**
+ * Implement this interface to add a -1 content on the home screen.
+ */
+@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
+public interface OverlayPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERLAY";
+    int VERSION = 1;
+
+    LauncherOverlayManager createOverlayManager(Activity activity, LauncherExterns externs);
+
+}
diff --git a/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
new file mode 100644
index 0000000..8d9c0f4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.plugins;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to add action buttons for overview screenshots, e.g. share, edit etc.
+ */
+@ProvidesInterface(
+        action = OverviewScreenshotActions.ACTION, version = OverviewScreenshotActions.VERSION)
+public interface OverviewScreenshotActions extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_OVERVIEW_SCREENSHOT_ACTIONS";
+    int VERSION = 1;
+
+    /**
+     * Setup the actions for the screenshot, including edit, save, etc.
+     * @param parent The parent view to add buttons on.
+     * @param screenshot The screenshot we will do actions on.
+     * @param activity THe host activity.
+     */
+    void setupActions(ViewGroup parent, Bitmap screenshot, Activity activity);
+}
diff --git a/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
new file mode 100644
index 0000000..0ebea3d
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/RecentsExtraCard.java
@@ -0,0 +1,42 @@
+/*
+ * 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.systemui.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to allow extra card on recents overview.
+ */
+@ProvidesInterface(action = RecentsExtraCard.ACTION, version = RecentsExtraCard.VERSION)
+public interface RecentsExtraCard extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PLUGIN_RECENTS_EXTRA_CARD";
+    int VERSION = 1;
+
+    /**
+     * Sets up the recents overview extra card and fills in data.
+     *
+     * @param context Plugin context
+     * @param frameLayout PlaceholderView
+     * @param activity Recents activity to hold extra view
+     */
+    void setupView(Context context, FrameLayout frameLayout, Activity activity);
+}
diff --git a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
new file mode 100644
index 0000000..0e3664a
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui.plugins;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to access user event log on the device for prototype purpose.
+ * NOTE: plugin is for internal prototype only and is not visible in production environment.
+ */
+@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION)
+public interface UserEventPlugin extends Plugin {
+    String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG";
+    int VERSION = 1;
+
+    /**
+     * Callback to be triggered whenever an user event occurs.
+     */
+    void onUserEvent(Object event);
+}
diff --git a/src/com/android/launcher3/LauncherExterns.java b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
similarity index 63%
rename from src/com/android/launcher3/LauncherExterns.java
rename to src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
index 272bbf6..13e4999 100644
--- a/src/com/android/launcher3/LauncherExterns.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
@@ -14,19 +14,36 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.systemui.plugins.shared;
 
 import android.content.SharedPreferences;
 
+import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+
 /**
  * This interface defines the set of methods that the Launcher activity exposes. Methods
  * here should be safe to call from classes outside of com.android.launcher3.*
  */
 public interface LauncherExterns {
 
-    boolean setLauncherCallbacks(LauncherCallbacks callbacks);
-
+    /**
+     * Returns the shared main preference
+     */
     SharedPreferences getSharedPrefs();
 
-    void setLauncherOverlay(Launcher.LauncherOverlay overlay);
+    /**
+     * Returns the device specific preference
+     */
+    SharedPreferences getDevicePrefs();
+
+    /**
+     * Sets the overlay on the target activity
+     */
+    void setLauncherOverlay(LauncherOverlay overlay);
+
+    /**
+     * Executes the command, next time the overlay is hidden
+     */
+    void runOnOverlayHidden(Runnable runnable);
+
 }
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
new file mode 100644
index 0000000..ac02ba4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -0,0 +1,98 @@
+/*
+ * 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.systemui.plugins.shared;
+
+import android.app.Activity;
+import android.app.Application;
+import android.os.Bundle;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to control the overlay on Launcher
+ */
+public interface LauncherOverlayManager extends Application.ActivityLifecycleCallbacks {
+
+    default void onDeviceProvideChanged() { }
+
+    default void onAttachedToWindow() { }
+    default void onDetachedFromWindow() { }
+
+    default void dump(String prefix, PrintWriter w) { }
+
+    default void openOverlay() { }
+
+    default void hideOverlay(boolean animate) {
+        hideOverlay(animate ? 200 : 0);
+    }
+
+    default void hideOverlay(int duration) { }
+
+    default boolean startSearch(byte[] config, Bundle extras) {
+        return false;
+    }
+
+    @Override
+    default void onActivityCreated(Activity activity, Bundle bundle) { }
+
+    @Override
+    default void onActivityStarted(Activity activity) { }
+
+    @Override
+    default void onActivityResumed(Activity activity) { }
+
+    @Override
+    default void onActivityPaused(Activity activity) { }
+
+    @Override
+    default void onActivityStopped(Activity activity) { }
+
+    @Override
+    default void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+    @Override
+    default void onActivityDestroyed(Activity activity) { }
+
+    interface LauncherOverlay {
+
+        /**
+         * Touch interaction leading to overscroll has begun
+         */
+        void onScrollInteractionBegin();
+
+        /**
+         * Touch interaction related to overscroll has ended
+         */
+        void onScrollInteractionEnd();
+
+        /**
+         * Scroll progress, between 0 and 100, when the user scrolls beyond the leftmost
+         * screen (or in the case of RTL, the rightmost screen).
+         */
+        void onScrollChange(float progress, boolean rtl);
+
+        /**
+         * Called when the launcher is ready to use the overlay
+         * @param callbacks A set of callbacks provided by Launcher in relation to the overlay
+         */
+        void setOverlayCallbacks(LauncherOverlayCallbacks callbacks);
+    }
+
+    interface LauncherOverlayCallbacks {
+
+        void onScrollChanged(float progress);
+    }
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 7a7f828..b8af8ed 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -3,6 +3,8 @@
 
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
 
+import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -10,18 +12,19 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppFilter;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
@@ -37,8 +40,6 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
-import androidx.annotation.Nullable;
-
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
  *
@@ -46,6 +47,9 @@
  */
 public class WidgetsModel {
 
+    // True is the widget support is disabled.
+    public static final boolean GO_DISABLE_WIDGETS = false;
+
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;
 
@@ -103,8 +107,8 @@
             }
 
             // Shortcuts
-            for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context)
-                    .getCustomShortcutActivityList(packageUser)) {
+            for (ShortcutConfigActivityInfo info :
+                    queryList(context, packageUser)) {
                 widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
                 updatedItems.add(info);
             }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DejankBinderTracker.java b/src_ui_overrides/com/android/launcher3/uioverrides/DejankBinderTracker.java
new file mode 100644
index 0000000..47f6ac6
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/DejankBinderTracker.java
@@ -0,0 +1,57 @@
+/**
+ * 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.uioverrides;
+
+import android.os.IBinder;
+
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+/**
+ * A binder proxy transaction listener for tracking non-whitelisted binder calls.
+ */
+public class DejankBinderTracker {
+    public static void whitelistIpcs(Runnable runnable) {}
+
+    public static <T> T whitelistIpcs(Supplier<T> supplier) {
+        return  null;
+    }
+
+    public static void allowBinderTrackingInTests() {}
+
+    public static void disallowBinderTrackingInTests() {}
+
+    public DejankBinderTracker(BiConsumer<String, Integer> unexpectedTransactionCallback) {    }
+
+    public void startTracking() {}
+
+    public void stopTracking() {}
+
+    public Object onTransactStarted(IBinder binder, int transactionCode, int flags) {
+        return null;
+    }
+
+    public Object onTransactStarted(IBinder binder, int transactionCode) {
+        return null;
+    }
+
+    public void onTransactEnded(Object session) {}
+
+    public static boolean isMainThread() {
+        return true;
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
index e875a3c..d7bb293 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.uioverrides;
 
-import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+import android.content.Context;
+
+import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag;
 
 public class TogglableFlag extends BaseTogglableFlag {
 
@@ -25,7 +27,10 @@
     }
 
     @Override
-    public boolean getInitialValue(boolean value) {
+    public boolean getOverridenDefaultValue(boolean value) {
         return value;
     }
+
+    @Override
+    public void addChangeListener(Context context, Runnable r) { }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 467ae02..6d9ed88 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -96,7 +96,8 @@
 
     public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
 
-    public static void clearSwipeSharedState(boolean finishAnimation) {}
+    /** No-op. */
+    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
 
     public static Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;
diff --git a/tests/Android.mk b/tests/Android.mk
index 02ead4e..b5c1dae 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -30,13 +30,14 @@
     LOCAL_STATIC_JAVA_LIBRARIES += SystemUISharedLib
 
     LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
+        ../quickstep/src/com/android/launcher3/uioverrides/DejankBinderTracker.java \
         ../src/com/android/launcher3/ResourceUtils.java \
         ../src/com/android/launcher3/util/SecureSettingsObserver.java \
         ../src/com/android/launcher3/testing/TestProtocol.java
 endif
 
 LOCAL_MODULE := ub-launcher-aosp-tapl
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := system_current
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index c6f55a7..56eca6d 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -62,6 +62,12 @@
                 <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
             </intent-filter>
         </activity>
+        <activity android:name="com.android.launcher3.testcomponent.CustomShortcutConfigActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity
             android:name="com.android.launcher3.testcomponent.RequestPinItemActivity"
             android:icon="@drawable/test_drawable_pin_item"
@@ -73,8 +79,12 @@
             </intent-filter>
         </activity>
 
+        <service
+            android:name="com.android.launcher3.testcomponent.ListViewService"
+            android:permission="android.permission.BIND_REMOTEVIEWS" />
+
         <provider
-            android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
+            android:name="com.android.launcher3.testcomponent.TestCommandProvider"
             android:authorities="${packageName}.commands"
             android:exported="true"/>
 
diff --git a/tests/res/layout/test_layout_widget_list.xml b/tests/res/layout/test_layout_widget_list.xml
new file mode 100644
index 0000000..0152040
--- /dev/null
+++ b/tests/res/layout/test_layout_widget_list.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFFFFF">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="#FF0000FF"
+        android:id="@android:id/text1"
+        android:padding="10dp" />
+
+    <ListView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:id="@android:id/list" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 64df8e0..7029ad5 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -1,29 +1,5 @@
 package com.android.launcher3.model;
 
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.os.Process;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.compat.LauncherAppsCompat;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.PackageManagerHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
 import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
 import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
@@ -32,6 +8,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.ICON;
 import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE;
 import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE;
+import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
@@ -40,15 +17,41 @@
 import static com.android.launcher3.LauncherSettings.Favorites.SCREEN;
 import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
 import static com.android.launcher3.LauncherSettings.Favorites._ID;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.database.MatrixCursor;
+import android.graphics.Bitmap;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.util.PackageManagerHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Tests for {@link LoaderCursor}
  */
@@ -62,7 +65,7 @@
     private MatrixCursor mCursor;
     private InvariantDeviceProfile mIDP;
     private Context mContext;
-    private LauncherAppsCompat mLauncherApps;
+    private LauncherApps mLauncherApps;
 
     private LoaderCursor mLoaderCursor;
 
@@ -81,7 +84,7 @@
         when(mMockApp.getIconCache()).thenReturn(mMockIconCache);
         when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP);
         when(mMockApp.getContext()).thenReturn(mContext);
-        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
+        mLauncherApps = mContext.getSystemService(LauncherApps.class);
 
         mLoaderCursor = new LoaderCursor(mCursor, mMockApp);
         mLoaderCursor.allUsers.put(0, Process.myUserHandle());
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 6fa8d62..27990f4 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -1,8 +1,11 @@
 package com.android.launcher3.provider;
 
+import static org.junit.Assert.assertEquals;
+
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -13,8 +16,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertEquals;
-
 /**
  * Tests for {@link RestoreDbTask}
  */
@@ -82,7 +83,7 @@
         private final long mProfileId;
 
         MyDatabaseHelper(long profileId) {
-            super(InstrumentationRegistry.getContext(), null, null);
+            super(InstrumentationRegistry.getContext(), null);
             mProfileId = profileId;
         }
 
diff --git a/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java b/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java
new file mode 100644
index 0000000..b673faa
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/CustomShortcutConfigActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.testcomponent;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+import com.android.launcher3.R;
+
+import java.util.UUID;
+
+/**
+ * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+ * Custom shortcuts are replaced by deep shortcuts after api 25.
+ */
+public class CustomShortcutConfigActivity extends BaseTestingActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent launchIntent = new Intent(this, BaseTestingActivity.class)
+                .setAction("com.android.launcher3.intent.action.test_shortcut");
+        Intent shortcutIntent = createShortcutResultIntent(
+                this, UUID.randomUUID().toString(), "Shortcut",
+                R.drawable.ic_widget, launchIntent);
+        setResult(RESULT_OK, shortcutIntent);
+        finish();
+    }
+
+    private static Intent createShortcutResultIntent(
+            Context context, String uniqueId, String name, int iconId, Intent launchIntent) {
+        ShortcutInfo shortcutInfo =
+                createShortcutInfo(context, uniqueId, name, iconId, launchIntent);
+        ShortcutManager sm = context.getSystemService(ShortcutManager.class);
+        return sm.createShortcutResultIntent(shortcutInfo);
+    }
+
+    private static ShortcutInfo createShortcutInfo(
+            Context context, String uniqueId, String name, int iconId, Intent launchIntent) {
+        return new ShortcutInfo.Builder(context, uniqueId)
+                .setShortLabel(name)
+                .setLongLabel(name)
+                .setIcon(Icon.createWithResource(context, iconId))
+                .setIntent(launchIntent)
+                .build();
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/ListViewService.java b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
new file mode 100644
index 0000000..3da20e0
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
@@ -0,0 +1,95 @@
+/*
+ * 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.testcomponent;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+public class ListViewService extends RemoteViewsService {
+
+    public static IBinder sBinderForTest;
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        return new SimpleViewsFactory();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sBinderForTest != null ? sBinderForTest : super.onBind(intent);
+    }
+
+    public static class SimpleViewsFactory implements RemoteViewsFactory {
+
+        public int viewCount = 0;
+
+        @Override
+        public void onCreate() { }
+
+        @Override
+        public void onDataSetChanged() { }
+
+        @Override
+        public void onDestroy() { }
+
+        @Override
+        public int getCount() {
+            return viewCount;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int i) {
+            RemoteViews views = new RemoteViews("android", android.R.layout.simple_list_item_1);
+            views.setTextViewText(android.R.id.text1, getLabel(i));
+            return views;
+        }
+
+        public String getLabel(int i) {
+            return "Item " + i;
+        }
+
+        @Override
+        public RemoteViews getLoadingView() {
+            return null;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public long getItemId(int i) {
+            return i;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        public IBinder toBinder() {
+            return new RemoteViewsService() {
+                @Override
+                public RemoteViewsFactory onGetViewFactory(Intent intent) {
+                    return SimpleViewsFactory.this;
+                }
+            }.onBind(new Intent("dummy_intent"));
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.java b/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.java
new file mode 100644
index 0000000..f9981a9
--- /dev/null
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandProvider.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.testcomponent;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+
+import static com.android.launcher3.testcomponent.TestCommandReceiver.DISABLE_TEST_LAUNCHER;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.ENABLE_TEST_LAUNCHER;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.KILL_PROCESS;
+import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Base64;
+
+import com.android.launcher3.tapl.TestHelpers;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class TestCommandProvider extends ContentProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        switch (method) {
+            case ENABLE_TEST_LAUNCHER: {
+                getContext().getPackageManager().setComponentEnabledSetting(
+                        new ComponentName(getContext(), TestLauncherActivity.class),
+                        COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+                return null;
+            }
+            case DISABLE_TEST_LAUNCHER: {
+                getContext().getPackageManager().setComponentEnabledSetting(
+                        new ComponentName(getContext(), TestLauncherActivity.class),
+                        COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
+                return null;
+            }
+            case KILL_PROCESS: {
+                ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE))
+                        .killBackgroundProcesses(arg);
+                return null;
+            }
+
+            case GET_SYSTEM_HEALTH_MESSAGE: {
+                final Bundle response = new Bundle();
+                response.putString("result",
+                        TestHelpers.getSystemHealthMessage(getContext(), Long.parseLong(arg)));
+                return response;
+            }
+
+            case SET_LIST_VIEW_SERVICE_BINDER: {
+                ListViewService.sBinderForTest = extras.getBinder(EXTRA_VALUE);
+                return null;
+            }
+        }
+        return super.call(method, arg, extras);
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        String path = Base64.encodeToString(uri.getPath().getBytes(),
+                Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
+        File file = new File(getContext().getCacheDir(), path);
+        if (!file.exists()) {
+            // Create an empty file so that we can pass its descriptor
+            try {
+                file.createNewFile();
+            } catch (IOException e) {
+            }
+        }
+
+        return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+    }
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index 6a6916e..eb6c3ed 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -15,125 +15,36 @@
  */
 package com.android.launcher3.testcomponent;
 
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.DONT_KILL_APP;
-import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
-
-import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.util.Base64;
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.launcher3.tapl.TestHelpers;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
 /**
  * Content provider to receive commands from tests
  */
-public class TestCommandReceiver extends ContentProvider {
+public class TestCommandReceiver {
 
     public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
     public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
     public static final String KILL_PROCESS = "kill-process";
     public static final String GET_SYSTEM_HEALTH_MESSAGE = "get-system-health-message";
+    public static final String SET_LIST_VIEW_SERVICE_BINDER = "set-list-view-service-binder";
 
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        throw new UnsupportedOperationException("unimplemented mock method");
-    }
-
-    @Override
-    public Bundle call(String method, String arg, Bundle extras) {
-        switch (method) {
-            case ENABLE_TEST_LAUNCHER: {
-                getContext().getPackageManager().setComponentEnabledSetting(
-                        new ComponentName(getContext(), TestLauncherActivity.class),
-                        COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
-                return null;
-            }
-            case DISABLE_TEST_LAUNCHER: {
-                getContext().getPackageManager().setComponentEnabledSetting(
-                        new ComponentName(getContext(), TestLauncherActivity.class),
-                        COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
-                return null;
-            }
-            case KILL_PROCESS: {
-                ((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE)).
-                        killBackgroundProcesses(arg);
-                return null;
-            }
-
-            case GET_SYSTEM_HEALTH_MESSAGE: {
-                final Bundle response = new Bundle();
-                response.putString("result", TestHelpers.getSystemHealthMessage(getContext()));
-                return response;
-            }
-        }
-        return super.call(method, arg, extras);
-    }
+    public static final String EXTRA_VALUE = "value";
 
     public static Bundle callCommand(String command) {
         return callCommand(command, null);
     }
 
     public static Bundle callCommand(String command, String arg) {
-        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
-        Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
-        return inst.getTargetContext().getContentResolver().call(uri, command, arg, null);
+        return callCommand(command, arg, null);
     }
 
-    @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        String path = Base64.encodeToString(uri.getPath().getBytes(),
-                Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
-        File file = new File(getContext().getCacheDir(), path);
-        if (!file.exists()) {
-            // Create an empty file so that we can pass its descriptor
-            try {
-                file.createNewFile();
-            } catch (IOException e) {
-            }
-        }
-
-        return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+    public static Bundle callCommand(String command, String arg, Bundle extras) {
+        Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
+        return inst.getTargetContext().getContentResolver().call(uri, command, arg, extras);
     }
 }
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/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 761253d..62989a3 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,7 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
 import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -27,10 +28,13 @@
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -42,25 +46,26 @@
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.launcher3.util.rule.LauncherActivityRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.After;
 import org.junit.Before;
@@ -92,8 +97,7 @@
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
-    protected final LauncherInstrumentation mLauncher =
-            new LauncherInstrumentation(getInstrumentation());
+    protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
     protected Context mTargetContext;
     protected String mTargetPackage;
 
@@ -105,8 +109,9 @@
         }
         if (TestHelpers.isInLauncherProcess()) {
             Utilities.enableRunningInTestHarnessForTests();
-            mLauncher.setSystemHealthSupplier(() -> TestCommandReceiver.callCommand(
-                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE).getString("result"));
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -156,7 +161,8 @@
 
     @Rule
     public TestRule mOrderSensitiveRules = RuleChain.
-            outerRule(mActivityMonitor).
+            outerRule(new TestStabilityRule()).
+            around(mActivityMonitor).
             around(getRulesInsideActivityMonitor());
 
     public UiDevice getDevice() {
@@ -170,15 +176,13 @@
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
-        // Unlock the phone
-        mDevice.executeShellCommand("input keyevent 82");
     }
 
     @After
     public void verifyLauncherState() {
         try {
             // Limits UI tests affecting tests running after them.
-            waitForModelLoaded();
+            mLauncher.waitForLauncherInitialized();
         } catch (Throwable t) {
             Log.e(TAG,
                     "Couldn't deinit after a test, exiting tests, see logs for failures that "
@@ -188,14 +192,6 @@
         }
     }
 
-    protected void lockRotation(boolean naturalOrientation) throws RemoteException {
-        if (naturalOrientation) {
-            mDevice.setOrientationNatural();
-        } else {
-            mDevice.setOrientationRight();
-        }
-    }
-
     protected void clearLauncherData() throws IOException, InterruptedException {
         if (TestHelpers.isInLauncherProcess()) {
             LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
@@ -225,14 +221,36 @@
         } catch (Throwable t) {
             throw new IllegalArgumentException(t);
         }
-        waitForModelLoaded();
+        mLauncher.waitForLauncherInitialized();
     }
 
-    protected void waitForModelLoaded() {
-        waitForLauncherCondition("Launcher model didn't load", launcher -> {
-            final LauncherModel model = LauncherAppState.getInstance(mTargetContext).getModel();
-            return model.getCallback() == null || model.isModelLoaded();
-        });
+    /**
+     * Adds {@param item} on the homescreen on the 0th screen
+     */
+    protected void addItemToScreen(ItemInfo item) {
+        ContentResolver resolver = mTargetContext.getContentResolver();
+        int screenId = FIRST_SCREEN_ID;
+        // Update the screen id counter for the provider.
+        LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
+
+        if (screenId > FIRST_SCREEN_ID) {
+            screenId = FIRST_SCREEN_ID;
+        }
+
+        // Insert the item
+        ContentWriter writer = new ContentWriter(mTargetContext);
+        item.id = LauncherSettings.Settings.call(
+                resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        item.screenId = screenId;
+        item.onAddToDatabase(writer);
+        writer.put(LauncherSettings.Favorites._ID, item.id);
+        resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
+        resetLoaderState();
+
+        // Launch the home activity
+        mDevice.pressHome();
+        mLauncher.waitForLauncherInitialized();
     }
 
     /**
@@ -277,6 +295,12 @@
 
     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
     // flakiness.
+    protected <T> T getOnceNotNull(String message, Function<Launcher, T> f) {
+        return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT);
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
     protected void waitForLauncherCondition(
             String message, Function<Launcher, Boolean> condition, long timeout) {
         if (!TestHelpers.isInLauncherProcess()) return;
@@ -285,6 +309,20 @@
 
     // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
     // flakiness.
+    protected <T> T getOnceNotNull(String message, Function<Launcher, T> f, long timeout) {
+        if (!TestHelpers.isInLauncherProcess()) return null;
+
+        final Object[] output = new Object[1];
+        Wait.atMost(message, () -> {
+            final Object fromLauncher = getFromLauncher(f);
+            output[0] = fromLauncher;
+            return fromLauncher != null;
+        }, timeout);
+        return (T) output[0];
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
     protected void waitForLauncherCondition(
             String message,
             Runnable testThreadAction, Function<Launcher, Boolean> condition,
@@ -297,9 +335,8 @@
     }
 
     protected LauncherActivityInfo getSettingsApp() {
-        return LauncherAppsCompat.getInstance(mTargetContext)
-                .getActivityList("com.android.settings",
-                        Process.myUserHandle()).get(0);
+        return mTargetContext.getSystemService(LauncherApps.class)
+                .getActivityList("com.android.settings", Process.myUserHandle()).get(0);
     }
 
     /**
@@ -332,14 +369,14 @@
         }
     }
 
-    protected void startAppFast(String packageName) {
+    public static void startAppFast(String packageName) {
         startIntent(
                 getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
                         packageName),
                 By.pkg(packageName).depth(0));
     }
 
-    protected void startTestActivity(int activityNumber) {
+    public static void startTestActivity(int activityNumber) {
         final String packageName = getAppPackageName();
         final Intent intent = getInstrumentation().getContext().getPackageManager().
                 getLaunchIntentForPackage(packageName);
@@ -348,19 +385,25 @@
         startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber));
     }
 
-    private void startIntent(Intent intent, BySelector selector) {
+    private static void startIntent(Intent intent, BySelector selector) {
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
-                mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
+                UiDevice.getInstance(getInstrumentation())
+                        .wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
     }
 
-    public static String resolveSystemApp(String category) {
+    public static ActivityInfo resolveSystemAppInfo(String category) {
         return getInstrumentation().getContext().getPackageManager().resolveActivity(
                 new Intent(Intent.ACTION_MAIN).addCategory(category),
                 PackageManager.MATCH_SYSTEM_ONLY).
-                activityInfo.packageName;
+                activityInfo;
+    }
+
+
+    public static String resolveSystemApp(String category) {
+        return resolveSystemAppInfo(category).packageName;
     }
 
     protected void closeLauncherActivity() {
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index a76b4a4..1d89d6e 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -30,7 +30,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.testcomponent.TestCommandReceiver;
+import com.android.launcher3.testcomponent.TestCommandProvider;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
@@ -63,7 +63,7 @@
 
         PackageManager pm = mTargetContext.getPackageManager();
         ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext,
-                TestCommandReceiver.class), 0);
+                TestCommandProvider.class), 0);
         mAuthority = pi.authority;
     }
 
@@ -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");
     }
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index ddcb4da..80bb3ed 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -44,8 +44,11 @@
                 } finally {
                     mTest.mDevice.setOrientationNatural();
                     mTest.executeOnLauncher(launcher ->
-                            launcher.getRotationHelper().forceAllowRotationForTesting(
-                                    false));
+                    {
+                        if (launcher != null) {
+                            launcher.getRotationHelper().forceAllowRotationForTesting(false);
+                        }
+                    });
                     mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0);
                 }
             }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c2a3c1c..709822b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -24,6 +24,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.util.Log;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -34,9 +36,9 @@
 import com.android.launcher3.tapl.AppIcon;
 import com.android.launcher3.tapl.AppIconMenu;
 import com.android.launcher3.tapl.AppIconMenuItem;
-import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.WidgetsRecyclerView;
@@ -340,7 +342,29 @@
         }
     }
 
+    /**
+     * Test dragging a custom shortcut to the workspace and launch it.
+     *
+     * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
+     * Custom shortcuts are replaced by deep shortcuts after api 25.
+     */
+    @Test
+    @PortraitLandscape
+    public void testDragCustomShortcut() {
+        mLauncher.getWorkspace().openAllWidgets()
+                .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
+                .dragToWorkspace();
+        mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
+                .launch(getAppPackageName());
+    }
+
     public static String getAppPackageName() {
         return getInstrumentation().getContext().getPackageName();
     }
+
+    @Test
+    @Stability
+    public void testTestStabilityAttribute() {
+        Log.d("TestStabilityRule", "Hello world!");
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 3206a69..e1b3ede 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,7 +52,8 @@
 @RunWith(AndroidJUnit4.class)
 public class AddConfigWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     private LauncherAppWidgetProviderInfo mWidgetInfo;
     private AppWidgetManager mAppWidgetManager;
@@ -69,34 +69,22 @@
     }
 
     @Test
+    @PortraitLandscape
     public void testWidgetConfig() throws Throwable {
-        runTest(false, true);
+        runTest(true);
     }
 
     @Test
-    @Ignore // b/121280703
-    public void testWidgetConfig_rotate() throws Throwable {
-        runTest(true, true);
-    }
-
-    @Test
+    @PortraitLandscape
     public void testConfigCancelled() throws Throwable {
-        runTest(false, false);
+        runTest(false);
     }
 
-    @Test
-    @Ignore // b/121280703
-    public void testConfigCancelled_rotate() throws Throwable {
-        runTest(true, false);
-    }
 
     /**
-     * @param rotateConfig should the config screen be rotated
      * @param acceptConfig accept the config activity
      */
-    private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable {
-        lockRotation(true);
-
+    private void runTest(boolean acceptConfig) throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
 
@@ -110,13 +98,6 @@
         // Widget id for which the config activity was opened
         mWidgetId = monitor.getWidgetId();
 
-        if (rotateConfig) {
-            // Rotate the screen and verify that the config activity is recreated
-            monitor = new WidgetConfigStartupMonitor();
-            lockRotation(false);
-            assertEquals(mWidgetId, monitor.getWidgetId());
-        }
-
         // Verify that the widget id is valid and bound
         assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
 
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 276c614..b8ca5de 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.LargeTest;
@@ -22,11 +25,11 @@
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,22 +41,12 @@
 @RunWith(AndroidJUnit4.class)
 public class AddWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     @Test
-    public void testDragIcon_portrait() throws Throwable {
-        lockRotation(true);
-        performTest();
-    }
-
-    @Test
-    @Ignore // b/121280703
-    public void testDragIcon_landscape() throws Throwable {
-        lockRotation(false);
-        performTest();
-    }
-
-    private void performTest() throws Throwable {
+    @PortraitLandscape
+    public void testDragIcon() throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
 
@@ -70,5 +63,10 @@
                 (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
                                 widgetInfo.provider.getClassName())).call());
+
+        final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label,
+                DEFAULT_UI_TIMEOUT);
+        assertNotNull("Widget not found on the workspace", widget);
+        widget.launch(getAppPackageName());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 3a7df64..3d691da 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -25,6 +27,7 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
@@ -39,14 +42,12 @@
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
-import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.pm.PackageInstallerCompat;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.WidgetHostViewLoader;
 
 import org.junit.After;
 import org.junit.Before;
@@ -54,6 +55,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -69,7 +71,6 @@
     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     private ContentResolver mResolver;
-    private AppWidgetManagerCompat mWidgetManager;
 
     // Objects created during test, which should be cleaned up in the end.
     private Cursor mCursor;
@@ -82,7 +83,6 @@
         super.setUp();
 
         mResolver = mTargetContext.getContentResolver();
-        mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
 
         // Clear all existing data
         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
@@ -105,7 +105,7 @@
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
         LauncherAppWidgetInfo item = createWidgetInfo(info, true);
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyWidgetPresent(info);
     }
 
@@ -114,7 +114,7 @@
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
         LauncherAppWidgetInfo item = createWidgetInfo(info, true);
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyWidgetPresent(info);
     }
 
@@ -124,7 +124,7 @@
         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
         item.appWidgetId = -33;
 
-        setupContents(item);
+        addItemToScreen(item);
 
         final Workspace workspace = mLauncher.getWorkspace();
         // Item deleted from db
@@ -145,7 +145,7 @@
         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyWidgetPresent(info);
     }
 
@@ -158,7 +158,7 @@
         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyPendingWidgetPresent();
 
         // Item deleted from db
@@ -180,7 +180,7 @@
         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
                 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 
-        setupContents(item);
+        addItemToScreen(item);
 
         assertTrue("Pending widget exists",
                 mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
@@ -199,7 +199,7 @@
                 | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
                 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyPendingWidgetPresent();
 
         // Verify item still exists in db
@@ -227,7 +227,7 @@
         PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
         mSessionId = installer.createSession(params);
 
-        setupContents(item);
+        addItemToScreen(item);
         verifyPendingWidgetPresent();
 
         // Verify item still exists in db
@@ -242,35 +242,6 @@
                         & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
     }
 
-    /**
-     * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
-     * widget class is displayed on the homescreen.
-     */
-    private void setupContents(LauncherAppWidgetInfo item) {
-        int screenId = FIRST_SCREEN_ID;
-        // Update the screen id counter for the provider.
-        LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
-
-        if (screenId > FIRST_SCREEN_ID) {
-            screenId = FIRST_SCREEN_ID;
-        }
-
-        // Insert the item
-        ContentWriter writer = new ContentWriter(mTargetContext);
-        item.id = LauncherSettings.Settings.call(
-                mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
-                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-        item.screenId = screenId;
-        item.onAddToDatabase(writer);
-        writer.put(LauncherSettings.Favorites._ID, item.id);
-        mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
-        resetLoaderState();
-
-        // Launch the home activity
-        mDevice.pressHome();
-        waitForModelLoaded();
-    }
-
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
         assertTrue("Widget is not present",
                 mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
@@ -286,8 +257,10 @@
      * @param bindWidget if true the info is bound and a valid widgetId is assigned to
      *                   the LauncherAppWidgetInfo
      */
-    private LauncherAppWidgetInfo createWidgetInfo(
+    public static LauncherAppWidgetInfo createWidgetInfo(
             LauncherAppWidgetProviderInfo info, boolean bindWidget) {
+        Context targetContext = getTargetContext();
+
         LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
                 LauncherAppWidgetInfo.NO_ID, info.provider);
         item.spanX = info.minSpanX;
@@ -305,11 +278,12 @@
             pendingInfo.spanY = item.spanY;
             pendingInfo.minSpanX = item.minSpanX;
             pendingInfo.minSpanY = item.minSpanY;
-            Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
+            Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
 
-            AppWidgetHost host = new LauncherAppWidgetHost(mTargetContext);
+            AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
             int widgetId = host.allocateAppWidgetId();
-            if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
+            if (!AppWidgetManagerCompat.getInstance(targetContext)
+                    .bindAppWidgetIdIfAllowed(widgetId, info, options)) {
                 host.deleteAppWidgetId(widgetId);
                 throw new IllegalArgumentException("Unable to bind widget id");
             }
@@ -326,9 +300,12 @@
         int count = 0;
         String pkg = invalidPackage;
 
-        Set<String> activePackage = getOnUiThread(() ->
-                PackageInstallerCompat.getInstance(mTargetContext)
-                        .updateAndGetActiveSessionCache().keySet());
+        Set<String> activePackage = getOnUiThread(() -> {
+            Set<String> packages = new HashSet<>();
+            PackageInstallerCompat.getInstance(mTargetContext).getActiveSessions()
+                    .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
+            return packages;
+        });
         while(true) {
             try {
                 mTargetContext.getPackageManager().getPackageInfo(
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index a9a5090..07129dd 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -128,8 +128,6 @@
         if (!Utilities.ATLEAST_OREO) {
             return;
         }
-        lockRotation(true);
-
         clearHomescreen();
         mDevice.pressHome();
 
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 62fe26d..6a6ec3e 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -16,16 +16,10 @@
 package com.android.launcher3.util.rule;
 
 import android.app.Activity;
-import android.app.Application;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-
-import androidx.test.InstrumentationRegistry;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace.ItemOperator;
 
-import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
@@ -34,17 +28,23 @@
 /**
  * Test rule to get the current Launcher activity.
  */
-public class LauncherActivityRule implements TestRule {
+public class LauncherActivityRule extends SimpleActivityRule<Launcher> {
 
-    private Launcher mActivity;
+    public LauncherActivityRule() {
+        super(Launcher.class);
+    }
 
     @Override
     public Statement apply(Statement base, Description description) {
-        return new MyStatement(base);
-    }
 
-    public Launcher getActivity() {
-        return mActivity;
+        return new MyStatement(base) {
+            @Override
+            public void onActivityStarted(Activity activity) {
+                if (activity instanceof Launcher) {
+                    ((Launcher) activity).getRotationHelper().forceAllowRotationForTesting(true);
+                }
+            }
+        };
     }
 
     public Callable<Boolean> itemExists(final ItemOperator op) {
@@ -56,62 +56,4 @@
             return launcher.getWorkspace().getFirstMatch(op) != null;
         };
     }
-
-    private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
-
-        private final Statement mBase;
-
-        public MyStatement(Statement base) {
-            mBase = base;
-        }
-
-        @Override
-        public void evaluate() throws Throwable {
-            Application app = (Application)
-                    InstrumentationRegistry.getTargetContext().getApplicationContext();
-            app.registerActivityLifecycleCallbacks(this);
-            try {
-                mBase.evaluate();
-            } finally {
-                app.unregisterActivityLifecycleCallbacks(this);
-            }
-        }
-
-        @Override
-        public void onActivityCreated(Activity activity, Bundle bundle) {
-            if (activity instanceof Launcher) {
-                mActivity = (Launcher) activity;
-            }
-        }
-
-        @Override
-        public void onActivityStarted(Activity activity) {
-            if (activity instanceof Launcher) {
-                mActivity.getRotationHelper().forceAllowRotationForTesting(true);
-            }
-        }
-
-        @Override
-        public void onActivityResumed(Activity activity) {
-        }
-
-        @Override
-        public void onActivityPaused(Activity activity) {
-        }
-
-        @Override
-        public void onActivityStopped(Activity activity) {
-        }
-
-        @Override
-        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
-        }
-
-        @Override
-        public void onActivityDestroyed(Activity activity) {
-            if (activity == mActivity) {
-                mActivity = null;
-            }
-        }
-    }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
new file mode 100644
index 0000000..33a6cf9
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -0,0 +1,104 @@
+/*
+ * 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.rule;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Test rule to get the current activity.
+ */
+public class SimpleActivityRule<T extends Activity> implements TestRule {
+
+    private final Class<T> mClass;
+    private T mActivity;
+
+    public SimpleActivityRule(Class<T> clazz) {
+        mClass = clazz;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new MyStatement(base);
+    }
+
+    public T getActivity() {
+        return mActivity;
+    }
+
+    protected class MyStatement extends Statement implements ActivityLifecycleCallbacks {
+
+        private final Statement mBase;
+
+        public MyStatement(Statement base) {
+            mBase = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            Application app = (Application)
+                    InstrumentationRegistry.getTargetContext().getApplicationContext();
+            app.registerActivityLifecycleCallbacks(this);
+            try {
+                mBase.evaluate();
+            } finally {
+                app.unregisterActivityLifecycleCallbacks(this);
+            }
+        }
+
+        @Override
+        public void onActivityCreated(Activity activity, Bundle bundle) {
+            if (activity != null && mClass.isInstance(activity)) {
+                mActivity = (T) activity;
+            }
+        }
+
+        @Override
+        public void onActivityStarted(Activity activity) {
+        }
+
+        @Override
+        public void onActivityResumed(Activity activity) {
+        }
+
+        @Override
+        public void onActivityPaused(Activity activity) {
+        }
+
+        @Override
+        public void onActivityStopped(Activity activity) {
+        }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+        }
+
+        @Override
+        public void onActivityDestroyed(Activity activity) {
+            if (activity == mActivity) {
+                mActivity = null;
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
new file mode 100644
index 0000000..d7f41bf
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -0,0 +1,119 @@
+/*
+ * 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.rule;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TestStabilityRule implements TestRule {
+    private static final String TAG = "TestStabilityRule";
+    private static final Pattern LAUNCHER_BUILD =
+            Pattern.compile("^("
+                    + "(?<local>(BuildFromAndroidStudio|"
+                    + "([0-9]+|[A-Z])-eng\\.[a-z]+\\.[0-9]+\\.[0-9]+))|"
+                    + "(?<presubmit>([0-9]+|[A-Z])-P[0-9]+)|"
+                    + "(?<postsubmit>([0-9]+|[A-Z])-[0-9]+)|"
+                    + "(?<platform>[0-9]+|[A-Z])"
+                    + ")$");
+    private static final Pattern PLATFORM_BUILD =
+            Pattern.compile("^("
+                    + "(?<commandLine>eng\\.[a-z]+\\.[0-9]+\\.[0-9]+)|"
+                    + "(?<presubmit>P[0-9]+)|"
+                    + "(?<postsubmit>[0-9]+)"
+                    + ")$");
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface Stability {
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        if (description.getAnnotation(Stability.class) != null) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    getRunFlavor();
+
+                    base.evaluate();
+                }
+            };
+        } else {
+            return base;
+        }
+    }
+
+    private static void getRunFlavor() throws Exception {
+        final String launcherVersion = getInstrumentation().
+                getContext().
+                getPackageManager().
+                getPackageInfo(
+                        UiDevice.getInstance(getInstrumentation()).
+                                getLauncherPackageName(),
+                        0).
+                versionName;
+
+        final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
+
+        if (!launcherBuildMatcher.find()) {
+            Log.e(TAG, "Match not found");
+        }
+
+        final String platformVersion = Build.VERSION.INCREMENTAL;
+        final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
+
+        if (!platformBuildMatcher.find()) {
+            Log.e(TAG, "Match not found");
+        }
+
+        Log.d(TAG, "Launcher: " + launcherVersion + ", platform: " + platformVersion);
+
+        if (launcherBuildMatcher.group("local") != null && (
+                platformBuildMatcher.group("commandLine") != null ||
+                        platformBuildMatcher.group("postsubmit") != null)) {
+            Log.d(TAG, "LOCAL RUN");
+        } else if (launcherBuildMatcher.group("presubmit") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "UNBUNDLED PRESUBMIT");
+        } else if (launcherBuildMatcher.group("postsubmit") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "UNBUNDLED POSTSUBMIT");
+        } else if (launcherBuildMatcher.group("platform") != null
+                && platformBuildMatcher.group("presubmit") != null) {
+            Log.d(TAG, "PLATFORM PRESUBMIT");
+        } else if (launcherBuildMatcher.group("platform") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "PLATFORM POSTSUBMIT");
+        } else {
+            Log.e(TAG, "ERROR3");
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index f070280..e1e9b8d 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -53,8 +53,8 @@
         return LauncherInstrumentation.ContainerType.ALL_APPS;
     }
 
-    private boolean hasClickableIcon(
-            UiObject2 allAppsContainer, UiObject2 appListRecycler, BySelector appIconSelector) {
+    private boolean hasClickableIcon(UiObject2 allAppsContainer, UiObject2 appListRecycler,
+            BySelector appIconSelector, int displayBottom) {
         final UiObject2 icon = appListRecycler.findObject(appIconSelector);
         if (icon == null) {
             LauncherInstrumentation.log("hasClickableIcon: icon not visible");
@@ -66,6 +66,10 @@
             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
             return false;
         }
+        if (iconBounds.bottom > displayBottom) {
+            LauncherInstrumentation.log("hasClickableIcon: icon center bellow bottom offset");
+            return false;
+        }
         LauncherInstrumentation.log("hasClickableIcon: icon is clickable");
         return true;
     }
@@ -90,21 +94,32 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                     "apps_list_view");
+            final UiObject2 searchBox = getSearchBox(allAppsContainer);
+
+            int bottomGestureMargin = ResourceUtils.getNavbarSize(
+                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
+            int deviceHeight = mLauncher.getDevice().getDisplayHeight();
+            int displayBottom = deviceHeight - bottomGestureMargin;
             allAppsContainer.setGestureMargins(
                     0,
                     getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
                     0,
-                    ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
-                            mLauncher.getResources()) + 1);
+                    bottomGestureMargin);
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
-            if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
+            if (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+                    displayBottom)) {
                 scrollBackToBeginning();
                 int attempts = 0;
-                int scroll = getScroll(allAppsContainer);
+                int scroll = getAllAppsScroll();
                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
-                    while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
-                        mLauncher.scroll(allAppsContainer, Direction.DOWN, 0.8f, null, 50);
-                        final int newScroll = getScroll(allAppsContainer);
+                    while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+                            displayBottom)) {
+                        mLauncher.scrollToLastVisibleRow(
+                                allAppsContainer,
+                                mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
+                                searchBox.getVisibleBounds().bottom
+                                        - allAppsContainer.getVisibleBounds().top);
+                        final int newScroll = getAllAppsScroll();
                         if (newScroll == scroll) break;
 
                         mLauncher.assertTrue(
@@ -117,8 +132,11 @@
                 verifyActiveContainer();
             }
 
+            // Ignore bottom offset selection here as there might not be any scroll more scroll
+            // region available.
             mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName,
-                    hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector));
+                    hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector,
+                            deviceHeight));
 
             final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
                     appIconSelector);
@@ -136,16 +154,16 @@
             int attempts = 0;
             final Rect margins = new Rect(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
 
-            for (int scroll = getScroll(allAppsContainer);
+            for (int scroll = getAllAppsScroll();
                     scroll != 0;
-                    scroll = getScroll(allAppsContainer)) {
+                    scroll = getAllAppsScroll()) {
                 mLauncher.assertTrue("Negative scroll position", scroll > 0);
 
                 mLauncher.assertTrue(
                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
 
-                mLauncher.scroll(allAppsContainer, Direction.UP, 1, margins, 50);
+                mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50);
             }
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -154,9 +172,10 @@
         }
     }
 
-    private int getScroll(UiObject2 allAppsContainer) {
-        return mLauncher.getAnswerFromLauncher(allAppsContainer, TestProtocol.GET_SCROLL_MESSAGE).
-                getInt(TestProtocol.SCROLL_Y_FIELD, -1);
+    private int getAllAppsScroll() {
+        return mLauncher.getTestInfo(
+                TestProtocol.REQUEST_APPS_LIST_SCROLL_Y)
+                .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     private UiObject2 getSearchBox(UiObject2 allAppsContainer) {
@@ -172,7 +191,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center to avoid starting at elements near the top.
             mLauncher.scroll(
-                    allAppsContainer, Direction.DOWN, 1, new Rect(0, 0, 0, mHeight / 2), 10);
+                    allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10);
             verifyActiveContainer();
         }
     }
@@ -186,7 +205,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
             mLauncher.scroll(
-                    allAppsContainer, Direction.UP, 1, new Rect(0, mHeight / 2, 0, 0), 10);
+                    allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index bcce8ef..9e66740 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -104,7 +104,12 @@
                     startY = endY = mLauncher.getDevice().getDisplayHeight() / 2;
                 }
 
-                mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
+                if (mLauncher.isFallbackOverview()) {
+                    mLauncher.linearGesture(startX, startY, endX, endY, 10);
+                    new BaseOverview(mLauncher);
+                } else {
+                    mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState);
+                }
                 break;
             }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 25e6e8c..8ccfc05 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -55,7 +55,7 @@
             final int leftMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.LEFT, 1, new Rect(leftMargin, 0, 0, 0), 20);
+            mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20);
             verifyActiveContainer();
         }
     }
@@ -63,10 +63,10 @@
     /**
      * Dismissed all tasks by scrolling to Clear-all button and pressing it.
      */
-    public Workspace dismissAllTasks() {
+    public void dismissAllTasks() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "dismissing all tasks")) {
-            final BySelector clearAllSelector = mLauncher.getLauncherObjectSelector("clear_all");
+            final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
             for (int i = 0;
                     i < FLINGS_FOR_DISMISS_LIMIT
                             && !verifyActiveContainer().hasObject(clearAllSelector);
@@ -75,10 +75,6 @@
             }
 
             mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
-            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
-                    "dismissed all tasks")) {
-                return new Workspace(mLauncher);
-            }
         }
     }
 
@@ -93,7 +89,7 @@
             final int rightMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.RIGHT, 1, new Rect(0, 0, rightMargin, 0), 20);
+            mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20);
             verifyActiveContainer();
         }
     }
@@ -109,7 +105,7 @@
                 "want to get current task")) {
             verifyActiveContainer();
             final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
-                    mLauncher.getLauncherObjectSelector("snapshot"));
+                    mLauncher.getOverviewObjectSelector("snapshot"));
             mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
 
             // taskViews contains up to 3 task views: the 'main' (having the widest visible
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0bd6362..484cbb6 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -37,7 +37,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -52,6 +51,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Configurator;
@@ -60,20 +60,24 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.system.QuickStepContract;
 
-import org.junit.Assert;
-
+import java.util.ArrayList;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
+import java.util.function.Function;
+
+import org.junit.Assert;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -84,6 +88,7 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
+    private static long START_TIME = System.currentTimeMillis();
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -134,13 +139,22 @@
     private int mExpectedRotation = Surface.ROTATION_0;
     private final Uri mTestProviderUri;
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
-    private Supplier<String> mSystemHealthSupplier;
+    private Function<Long, String> mSystemHealthSupplier;
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
      */
+    public LauncherInstrumentation() {
+        this(InstrumentationRegistry.getInstrumentation());
+    }
+
+    /**
+     * Constructs the root of TAPL hierarchy. You get all other objects from it.
+     * Deprecated: use the constructor without parameters instead.
+     */
+    @Deprecated
     public LauncherInstrumentation(Instrumentation instrumentation) {
         mInstrumentation = instrumentation;
         mDevice = UiDevice.getInstance(instrumentation);
@@ -238,10 +252,6 @@
         return null;
     }
 
-    public static boolean isAvd() {
-        return Build.MODEL.contains("Cuttlefish");
-    }
-
     static void log(String message) {
         Log.d(TAG, message);
     }
@@ -296,7 +306,7 @@
         return "Background";
     }
 
-    public void setSystemHealthSupplier(Supplier<String> supplier) {
+    public void setSystemHealthSupplier(Function<Long, String> supplier) {
         this.mSystemHealthSupplier = supplier;
     }
 
@@ -316,8 +326,8 @@
         }
 
         return mSystemHealthSupplier != null
-                ? mSystemHealthSupplier.get()
-                : TestHelpers.getSystemHealthMessage(getContext());
+                ? mSystemHealthSupplier.apply(START_TIME)
+                : TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
     }
 
     private void fail(String message) {
@@ -368,7 +378,7 @@
         }
     }
 
-    private void assertEquals(String message, String expected, String actual) {
+    void assertEquals(String message, String expected, String actual) {
         if (!TextUtils.equals(expected, actual)) {
             fail(message + " expected: '" + expected + "' but was: '" + actual + "'");
         }
@@ -486,7 +496,7 @@
         }
     }
 
-    private void waitForLauncherInitialized() {
+    public void waitForLauncherInitialized() {
         for (int i = 0; i < 100; ++i) {
             if (getTestInfo(
                     TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
@@ -512,15 +522,6 @@
         }
     }
 
-    Bundle getAnswerFromLauncher(UiObject2 view, String requestTag) {
-        // Send a fake set-text request to Launcher to initiate a response with requested data.
-        final String responseTag = requestTag + TestProtocol.RESPONSE_MESSAGE_POSTFIX;
-        return (Bundle) executeAndWaitForEvent(
-                () -> view.setText(requestTag),
-                event -> responseTag.equals(event.getClassName()),
-                "Launcher didn't respond to request: " + requestTag);
-    }
-
     /**
      * Presses nav bar home button.
      *
@@ -534,6 +535,9 @@
         // accessibility events prior to pressing Home.
         final String action;
         if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
+            final String anomaly = getAnomalyMessage();
+            if (anomaly != null) fail("Can't swipe up to Home: " + anomaly);
+
             final Point displaySize = getRealDisplaySize();
 
             if (hasLauncherObject("deep_shortcuts_container")) {
@@ -541,13 +545,17 @@
                         displaySize.x / 2, displaySize.y - 1,
                         displaySize.x / 2, 0,
                         ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
-                assertTrue("Context menu is still visible afterswiping up to home",
-                        !hasLauncherObject("deep_shortcuts_container"));
+                try (LauncherInstrumentation.Closable c = addContextLayer(
+                        "Swiped up from context menu to home")) {
+                    waitUntilGone("deep_shortcuts_container");
+                }
             }
             if (hasLauncherObject(WORKSPACE_RES_ID)) {
                 log(action = "already at home");
             } else {
-                log(action = "swiping up to home");
+                log("Hierarchy before swiping up to home");
+                dumpViewHierarchy();
+                log(action = "swiping up to home from " + getVisibleStateMessage());
                 final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName()))
                         ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL;
 
@@ -725,7 +733,7 @@
 
     @NonNull
     UiObject2 waitForFallbackLauncherObject(String resName) {
-        return waitForObjectBySelector(getFallbackLauncherObjectSelector(resName));
+        return waitForObjectBySelector(getOverviewObjectSelector(resName));
     }
 
     private UiObject2 waitForObjectBySelector(BySelector selector) {
@@ -742,7 +750,7 @@
         return By.res(getLauncherPackageName(), resName);
     }
 
-    BySelector getFallbackLauncherObjectSelector(String resName) {
+    BySelector getOverviewObjectSelector(String resName) {
         return By.res(getOverviewPackageName(), resName);
     }
 
@@ -769,7 +777,36 @@
                 TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
     }
 
-    void scroll(UiObject2 container, Direction direction, float percent, Rect margins, int steps) {
+    int getBottomGestureSize() {
+        return ResourceUtils.getNavbarSize(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
+    }
+
+    int getBottomGestureMargin(UiObject2 container) {
+        return container.getVisibleBounds().bottom - getRealDisplaySize().y
+                + getBottomGestureSize();
+    }
+
+    void scrollToLastVisibleRow(UiObject2 container, Collection<UiObject2> items, int topPadding) {
+        final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
+                Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
+
+        final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop();
+        final int distance = gestureStart - container.getVisibleBounds().top - topPadding;
+        final int bottomMargin = container.getVisibleBounds().height() - distance;
+
+        scroll(
+                container,
+                Direction.DOWN,
+                new Rect(
+                        0,
+                        0,
+                        0,
+                        Math.max(bottomMargin, getBottomGestureMargin(container))),
+                150);
+    }
+
+    void scroll(UiObject2 container, Direction direction, Rect margins, int steps) {
         final Rect rect = container.getVisibleBounds();
         if (margins != null) {
             rect.left += margins.left;
@@ -786,34 +823,26 @@
         switch (direction) {
             case UP: {
                 startX = endX = rect.centerX();
-                final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
-                startY = (int) (vertCenter - halfGestureHeight) + 1;
-                endY = (int) (vertCenter + halfGestureHeight);
+                startY = rect.top;
+                endY = rect.bottom - 1;
             }
             break;
             case DOWN: {
                 startX = endX = rect.centerX();
-                final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
-                startY = (int) (vertCenter + halfGestureHeight) - 1;
-                endY = (int) (vertCenter - halfGestureHeight);
+                startY = rect.bottom - 1;
+                endY = rect.top;
             }
             break;
             case LEFT: {
                 startY = endY = rect.centerY();
-                final int horizCenter = rect.centerX();
-                final float halfGestureWidth = rect.width() * percent / 2.0f;
-                startX = (int) (horizCenter - halfGestureWidth) + 1;
-                endX = (int) (horizCenter + halfGestureWidth);
+                startX = rect.left;
+                endX = rect.right - 1;
             }
             break;
             case RIGHT: {
                 startY = endY = rect.centerY();
-                final int horizCenter = rect.centerX();
-                final float halfGestureWidth = rect.width() * percent / 2.0f;
-                startX = (int) (horizCenter + halfGestureWidth) - 1;
-                endX = (int) (horizCenter - halfGestureWidth);
+                startX = rect.right - 1;
+                endX = rect.left;
             }
             break;
             default:
@@ -844,10 +873,6 @@
         mDevice.waitForIdle();
     }
 
-    float getDisplayDensity() {
-        return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
-    }
-
     int getTouchSlop() {
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
@@ -957,8 +982,30 @@
         getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
 
-    public long getAllocatedMemory() {
-        return getTestInfo(TestProtocol.REQUEST_ALLOCATED_MEMORY).
-                getLong(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    public int getTotalPssKb() {
+        return getTestInfo(TestProtocol.REQUEST_TOTAL_PSS_KB).
+                getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    public void produceJavaLeak() {
+        getTestInfo(TestProtocol.REQUEST_JAVA_LEAK);
+    }
+
+    public void produceNativeLeak() {
+        getTestInfo(TestProtocol.REQUEST_NATIVE_LEAK);
+    }
+
+    public void produceViewLeak() {
+        getTestInfo(TestProtocol.REQUEST_VIEW_LEAK);
+    }
+
+    public ArrayList<ComponentName> getRecentTasks() {
+        ArrayList<ComponentName> tasks = new ArrayList<>();
+        ArrayList<String> components = getTestInfo(TestProtocol.REQUEST_RECENT_TASKS_LIST)
+                .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        for (String s : components) {
+            tasks.add(ComponentName.unflattenFromString(s));
+        }
+        return tasks;
     }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java
index da68da3..4f8aeb1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -67,4 +67,13 @@
             }
         }
     }
+
+    @Override
+    public void dismissAllTasks() {
+        super.dismissAllTasks();
+        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                "dismissed all tasks")) {
+            new Workspace(mLauncher);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index a089a52..e882171 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -77,13 +77,18 @@
         return launchers.get(0).activityInfo;
     }
 
-    public static String getOverviewPackageName() {
+    public static ComponentName getOverviewComponentName() {
         Resources res = Resources.getSystem();
         int id = res.getIdentifier("config_recentsComponentName", "string", "android");
         if (id != 0) {
-            return ComponentName.unflattenFromString(res.getString(id)).getPackageName();
+            return ComponentName.unflattenFromString(res.getString(id));
         }
-        return "com.android.systemui";
+        return new ComponentName("com.android.systemui",
+                "com.android.systemui.recents.RecentsActivity");
+    }
+
+    public static String getOverviewPackageName() {
+        return getOverviewComponentName().getPackageName();
     }
 
     private static String truncateCrash(String text, int maxLines) {
@@ -101,11 +106,11 @@
         return ret.toString();
     }
 
-    private static String checkCrash(Context context, String label) {
+    private static String checkCrash(Context context, String label, long startTime) {
         DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
         Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
 
-        long timestamp = System.currentTimeMillis() - 5 * 60000;
+        long timestamp = startTime;
         DropBoxManager.Entry entry;
         StringBuilder errorDetails = new StringBuilder();
         while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
@@ -123,7 +128,7 @@
         return errorDetails.length() != 0 ? errorDetails.toString() : null;
     }
 
-    public static String getSystemHealthMessage(Context context) {
+    public static String getSystemHealthMessage(Context context, long startTime) {
         try {
             StringBuilder errors = new StringBuilder();
 
@@ -131,7 +136,6 @@
                     "system_app_anr",
                     "system_app_crash",
                     "system_app_native_crash",
-                    "system_app_wtf",
                     "system_server_anr",
                     "system_server_crash",
                     "system_server_native_crash",
@@ -139,7 +143,7 @@
             };
 
             for (String label : labels) {
-                final String crash = checkCrash(context, label);
+                final String crash = checkCrash(context, label, startTime);
                 if (crash != null) errors.append(crash);
             }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 2495933..5fcaa55 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,22 +16,21 @@
 
 package com.android.launcher3.tapl;
 
-import static org.junit.Assert.fail;
-
 import android.graphics.Point;
+import android.graphics.Rect;
 
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.ResourceUtils;
+import java.util.Collection;
 
 /**
  * All widgets container.
  */
 public final class Widgets extends LauncherInstrumentation.VisibleContainer {
-    private static final int FLING_SPEED = 1500;
+    private static final int FLING_STEPS = 10;
 
     Widgets(LauncherInstrumentation launcher) {
         super(launcher);
@@ -46,11 +45,11 @@
                 "want to fling forward in widgets")) {
             LauncherInstrumentation.log("Widgets.flingForward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
-            widgetsContainer.setGestureMargins(0, 0, 0,
-                    ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
-                            mLauncher.getResources()) + 1);
-            widgetsContainer.fling(Direction.DOWN,
-                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
+            mLauncher.scroll(
+                    widgetsContainer,
+                    Direction.DOWN,
+                    new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer) + 1),
+                    FLING_STEPS);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
@@ -66,10 +65,11 @@
                 "want to fling backwards in widgets")) {
             LauncherInstrumentation.log("Widgets.flingBackward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
-            widgetsContainer.setGestureMargin(100);
-            widgetsContainer.fling(Direction.UP,
-                    (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-            mLauncher.waitForIdle();
+            mLauncher.scroll(
+                    widgetsContainer,
+                    Direction.UP,
+                    new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
+                    FLING_STEPS);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
             }
@@ -82,26 +82,34 @@
         return LauncherInstrumentation.ContainerType.WIDGETS;
     }
 
-    public Widget getWidget(String label) {
-        final int margin = ResourceUtils.getNavbarSize(
-                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
+    public Widget getWidget(String labelText) {
         final UiObject2 widgetsContainer = verifyActiveContainer();
-        widgetsContainer.setGestureMargins(0, 0, 0, margin);
-
         final Point displaySize = mLauncher.getRealDisplaySize();
+        final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
 
         int i = 0;
-        final BySelector selector = By.
-                clazz("com.android.launcher3.widget.WidgetCell").
-                hasDescendant(By.text(label));
-
         for (; ; ) {
-            final UiObject2 widget = mLauncher.tryWaitForLauncherObject(selector, 300);
-            if (widget != null && widget.getVisibleBounds().bottom <= displaySize.y - margin) {
-                return new Widget(mLauncher, widget);
+            final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+                    widgetsContainer, "widgets_cell_list_container");
+            mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+            for (UiObject2 cell : cells) {
+                final UiObject2 label = cell.findObject(labelSelector);
+                if (label == null) continue;
+
+                final UiObject2 widget = label.getParent().getParent();
+                mLauncher.assertEquals(
+                        "View is not WidgetCell",
+                        "com.android.launcher3.widget.WidgetCell",
+                        widget.getClassName());
+
+                if (widget.getVisibleBounds().bottom
+                        <= displaySize.y - mLauncher.getBottomGestureSize()) {
+                    return new Widget(mLauncher, widget);
+                }
             }
-            if (++i > 40) fail("Too many attempts");
-            widgetsContainer.scroll(Direction.DOWN, 1f);
+
+            mLauncher.assertTrue("Too many attempts", ++i <= 40);
+            mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
         }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 639902f..d1261e0 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -21,6 +21,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -37,9 +38,8 @@
  * Operations on the workspace screen.
  */
 public final class Workspace extends Home {
-    private static final float FLING_SPEED =
-            LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F;
     private static final int DRAG_DURACTION = 2000;
+    private static final int FLING_STEPS = 10;
     private final UiObject2 mHotseat;
 
     Workspace(LauncherInstrumentation launcher) {
@@ -140,7 +140,7 @@
     }
 
     private boolean isWorkspaceScrollable(UiObject2 workspace) {
-        return workspace.isScrollable();
+        return workspace.getChildCount() > 1;
     }
 
     @NonNull
@@ -180,9 +180,9 @@
      */
     public void flingForward() {
         final UiObject2 workspace = verifyActiveContainer();
-        workspace.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
-        workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
+        mLauncher.scroll(workspace, Direction.RIGHT,
+                new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
+                FLING_STEPS);
         verifyActiveContainer();
     }
 
@@ -192,9 +192,9 @@
      */
     public void flingBackward() {
         final UiObject2 workspace = verifyActiveContainer();
-        workspace.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
-        workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-        mLauncher.waitForIdle();
+        mLauncher.scroll(workspace, Direction.LEFT,
+                new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
+                FLING_STEPS);
         verifyActiveContainer();
     }