Set FLAG_IMMUTABLE flag on FirstScreenBroadcast PendingIntent. am: 6a7a7f6bad am: 3c3e3e0737

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14651127

Change-Id: I309d1c1671abc7b2a035e69606d14e1f90c17f71
diff --git a/Android.mk b/Android.mk
index 9d113d9..78ea02a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,12 +23,7 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
-
-ifneq (,$(wildcard frameworks/base))
-    LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
-else
-    LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore
-endif
+LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src_plugins)
@@ -151,11 +146,10 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
-  LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
-  LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
   LOCAL_SDK_VERSION := system_current
   LOCAL_MIN_SDK_VERSION := 26
 endif
@@ -224,11 +218,10 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
-  LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
-  LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
   LOCAL_SDK_VERSION := system_current
   LOCAL_MIN_SDK_VERSION := 26
 endif
@@ -271,11 +264,10 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
 ifneq (,$(wildcard frameworks/base))
-  LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
-  LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos
   LOCAL_SDK_VERSION := system_current
   LOCAL_MIN_SDK_VERSION := 26
 endif
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 5318a12..555cc73 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
 
 
     <!--
@@ -168,7 +169,7 @@
         <activity
             android:name="com.android.launcher3.settings.SettingsActivity"
             android:label="@string/settings_button_text"
-            android:theme="@android:style/Theme.DeviceDefault.Settings"
+            android:theme="@style/HomeSettingsTheme"
             android:autoRemoveFromRecents="true">
             <intent-filter>
                 <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
diff --git a/OWNERS b/OWNERS
index 6c1273f..538ca33 100644
--- a/OWNERS
+++ b/OWNERS
@@ -10,3 +10,6 @@
 sunnygoyal@google.com
 twickham@google.com
 winsonc@google.com
+
+per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com
+per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..f3db20e
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,2 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/SecondaryDisplayLauncher/res/values-ar/strings.xml b/SecondaryDisplayLauncher/res/values-ar/strings.xml
index ba81c11..aa34c7d 100644
--- a/SecondaryDisplayLauncher/res/values-ar/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-ar/strings.xml
@@ -21,5 +21,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="couldnt_launch" msgid="7873588052226763866">"تعذَّر تشغيل النشاط."</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"إضافة اختصار التطبيق"</string>
-    <string name="set_wallpaper" msgid="6475195450505435904">"تعيين الخلفية"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"ضبط الخلفية"</string>
 </resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml b/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..8d8c419
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-en-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="couldnt_launch" msgid="7873588052226763866">"Couldn\'t launch the activity"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"Add app shortcut"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"Set wallpaper"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml b/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..da69193
--- /dev/null
+++ b/SecondaryDisplayLauncher/res/values-en-rXC/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="couldnt_launch" msgid="7873588052226763866">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎Couldn\'t launch the activity‎‏‎‎‏‎"</string>
+    <string name="add_app_shortcut" msgid="2756755330707509435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‎Add app shortcut‎‏‎‎‏‎"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎Set wallpaper‎‏‎‎‏‎"</string>
+</resources>
diff --git a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
index bf76f29..c02fe2c 100644
--- a/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
+++ b/SecondaryDisplayLauncher/res/values-zh-rTW/strings.xml
@@ -21,5 +21,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="couldnt_launch" msgid="7873588052226763866">"無法啟動活動"</string>
     <string name="add_app_shortcut" msgid="2756755330707509435">"新增應用程式捷徑"</string>
-    <string name="set_wallpaper" msgid="6475195450505435904">"設定桌布"</string>
+    <string name="set_wallpaper" msgid="6475195450505435904">"套用桌布"</string>
 </resources>
diff --git a/go/quickstep/res/values/override.xml b/go/quickstep/res/values/override.xml
index 7636fb3..bb267a3 100644
--- a/go/quickstep/res/values/override.xml
+++ b/go/quickstep/res/values/override.xml
@@ -22,5 +22,7 @@
   <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
 </resources>
 
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d0cfcf9..212ce9b 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -32,6 +32,7 @@
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
+import android.content.Context;
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
@@ -115,10 +116,10 @@
     }
 
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        return getDefaultSwipeHeight(launcher.getDeviceProfile());
+        return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
     }
 
-    public static float getDefaultSwipeHeight(DeviceProfile dp) {
+    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
         return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
     }
 
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
index 1ccd7d7..66aec40 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeStatesTouchController.java
@@ -64,7 +64,7 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
         return LauncherLogProto.ContainerType.WORKSPACE;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index fe159b5..92900f2 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -25,6 +25,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
+import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
 
@@ -151,7 +152,7 @@
     }
 
     @Override
-    public ActivityOptions toActivityOptions(Handler handler, long duration) {
+    public ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
         LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
                 false /* startAtFrontOfQueue */) {
 
@@ -165,7 +166,7 @@
                     );
                     return;
                 }
-                result.setAnimation(createWindowAnimation(targetCompats));
+                result.setAnimation(createWindowAnimation(targetCompats), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 0fa3d86..216972c 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep;
 
-import static com.android.systemui.shared.system.ActivityManagerWrapper
-        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -25,7 +25,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
@@ -43,7 +42,6 @@
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
     private final RecentsModel mRecentsModel;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -51,7 +49,6 @@
     public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
-        mMainThreadExecutor = new MainThreadExecutor();
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
@@ -63,19 +60,19 @@
         }
 
         mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
     public void onOverviewShown(boolean triggeredFromAltTab) {
-        mMainThreadExecutor.execute(new ShowRecentsCommand());
+        MAIN_EXECUTOR.execute(new ShowRecentsCommand());
     }
 
     public void onOverviewHidden() {
-        mMainThreadExecutor.execute(new HideRecentsCommand());
+        MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
     public void onTip(int actionType, int viewType) {
-        mMainThreadExecutor.execute(() ->
+        MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
     }
 
@@ -161,7 +158,7 @@
             // Otherwise, start overview.
             mListener = mHelper.createActivityInitListener(provider::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    provider, mContext, mMainThreadExecutor.getHandler(),
+                    provider, mContext, MAIN_EXECUTOR.getHandler(),
                     provider.getRecentsLaunchDuration());
         }
 
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 900b94e..19dd82f 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -180,4 +180,8 @@
         }
         return mMyBinder;
     }
+
+    public static boolean isInitialized() {
+        return true;
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..fb89013
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.launcher3.Launcher;
+
+/** Empty class, only exists so that l3goWithQuickstepIconRecentsDebug compiles. */
+public class ShelfPeekAnim {
+    public ShelfPeekAnim(Launcher launcher) {
+    }
+
+    public enum ShelfAnimState {
+    }
+
+    public boolean isPeeking() {
+        return false;
+    }
+}
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index b82f362..26c3313 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
 
 package com.android.launcher3.model;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 
 import java.lang.ref.WeakReference;
 
diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 1e44910..42b1194 100644
--- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -25,39 +25,25 @@
 import android.os.UserHandle;
 
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.notification.NotificationKeyData;
 
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc.
  */
 public class DeepShortcutManager {
-    private static DeepShortcutManager sInstance;
-    private static final Object sInstanceLock = new Object();
+
+    private static final DeepShortcutManager sInstance = new DeepShortcutManager();
 
     public static DeepShortcutManager getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                sInstance = new DeepShortcutManager(context.getApplicationContext());
-            }
-            return sInstance;
-        }
+        return sInstance;
     }
 
-    private DeepShortcutManager(Context context) {
-    }
+    private final QueryResult mFailure = new QueryResult();
 
-    public static boolean supportsShortcuts(ItemInfo info) {
-        return false;
-    }
-
-    public boolean wasLastCallSuccess() {
-        return false;
-    }
-
-    public void onShortcutsChanged(List<ShortcutInfo> shortcuts) {
-    }
+    private DeepShortcutManager() { }
 
     /**
      * Queries for the shortcuts with the package name and provided ids.
@@ -65,18 +51,18 @@
      * This method is intended to get the full details for shortcuts when they are added or updated,
      * because we only get "key" fields in onShortcutsChanged().
      */
-    public List<ShortcutInfo> queryForFullDetails(String packageName,
+    public QueryResult queryForFullDetails(String packageName,
             List<String> shortcutIds, UserHandle user) {
-        return Collections.emptyList();
+        return mFailure;
     }
 
     /**
      * Gets all the manifest and dynamic shortcuts associated with the given package and user,
      * to be displayed in the shortcuts container on long press.
      */
-    public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity,
+    public QueryResult queryForShortcutsContainer(ComponentName activity,
             UserHandle user) {
-        return Collections.emptyList();
+        return mFailure;
     }
 
     /**
@@ -106,20 +92,28 @@
      *
      * If packageName is null, returns all pinned shortcuts regardless of package.
      */
-    public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) {
-        return Collections.emptyList();
+    public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
+        return mFailure;
     }
 
-    public List<ShortcutInfo> queryForPinnedShortcuts(String packageName,
+    public QueryResult queryForPinnedShortcuts(String packageName,
             List<String> shortcutIds, UserHandle user) {
-        return Collections.emptyList();
+        return mFailure;
     }
 
-    public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) {
-        return Collections.emptyList();
+    public QueryResult queryForAllShortcuts(UserHandle user) {
+        return mFailure;
     }
 
     public boolean hasHostPermission() {
         return false;
     }
+
+
+    public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+        public boolean wasSuccess() {
+            return true;
+        }
+    }
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 60320d6..5c4f37c 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -23,6 +23,8 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
+
 /**
  * This class will be moved to androidx library. There shouldn't be any dependency outside
  * this package.
@@ -34,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;
@@ -154,7 +158,7 @@
      * @param scale                     returns the scale result from normalization
      * @return a bitmap suitable for disaplaying as an icon at various system UIs.
      */
-    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+    public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
             boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
         if (scale == null) {
             scale = new float[1];
@@ -204,8 +208,11 @@
         mDisableColorExtractor = true;
     }
 
-    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
-            RectF outIconBounds, float[] outScale) {
+    private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
+            boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
+        if (icon == null) {
+            return null;
+        }
         float scale = 1f;
 
         if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
@@ -247,7 +254,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);
@@ -261,7 +268,7 @@
      * @param icon drawable that should be flattened to a bitmap
      * @param scale the scale to apply before drawing {@param icon} on the canvas
      */
-    public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
+    public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
         Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
         if (icon == null) {
             return bitmap;
@@ -328,6 +335,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 d84633d..a886c0a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -36,12 +36,16 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
@@ -57,8 +61,6 @@
 import java.util.Set;
 import java.util.function.Supplier;
 
-import androidx.annotation.NonNull;
-
 public abstract class BaseIconCache {
 
     private static final String TAG = "BaseIconCache";
@@ -84,6 +86,7 @@
 
     protected int mIconDpi;
     protected IconDB mIconDb;
+    protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
     protected String mSystemState = "";
 
     private final String mDbFileName;
@@ -227,12 +230,12 @@
 
     /**
      * Refreshes the system state definition used to check the validity of the cache. It
-     * incorporates all the properties that can affect the cache like locale and system-version.
+     * incorporates all the properties that can affect the cache like the list of enabled locale
+     * and system-version.
      */
     private void updateSystemState() {
-        final String locale =
-                mContext.getResources().getConfiguration().getLocales().toLanguageTags();
-        mSystemState = locale + "," + Build.VERSION.SDK_INT;
+        mLocaleList = mContext.getResources().getConfiguration().getLocales();
+        mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
     }
 
     protected String getIconSystemState(String packageName) {
@@ -264,12 +267,16 @@
             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());
+                componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
         addIconToDB(values, componentName, info, userSerial);
     }
 
@@ -305,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);
             }
 
@@ -445,7 +444,7 @@
                     // Add the icon in the DB here, since these do not get written during
                     // package updates.
                     ContentValues values = newContentValues(
-                            iconInfo, entry.title.toString(), packageName);
+                            iconInfo, entry.title.toString(), packageName, null);
                     addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user));
 
                 } catch (NameNotFoundException e) {
@@ -504,23 +503,35 @@
         return false;
     }
 
-    static final class IconDB extends SQLiteCacheHelper {
-        private final static int RELEASE_VERSION = 26;
+    /**
+     * Returns a cursor for an arbitrary query to the cache db
+     */
+    public synchronized Cursor queryCacheDb(String[] columns, String selection,
+            String[] selectionArgs) {
+        return mIconDb.query(columns, selection, selectionArgs);
+    }
 
-        public final static String TABLE_NAME = "icons";
-        public final static String COLUMN_ROWID = "rowid";
-        public final static String COLUMN_COMPONENT = "componentName";
-        public final static String COLUMN_USER = "profileId";
-        public final static String COLUMN_LAST_UPDATED = "lastUpdated";
-        public final static String COLUMN_VERSION = "version";
-        public final static String COLUMN_ICON = "icon";
-        public final static String COLUMN_ICON_COLOR = "icon_color";
-        public final static String COLUMN_LABEL = "label";
-        public final static String COLUMN_SYSTEM_STATE = "system_state";
+    /**
+     * Cache class to store the actual entries on disk
+     */
+    public static final class IconDB extends SQLiteCacheHelper {
+        private static final int RELEASE_VERSION = 27;
 
-        public final static String[] COLUMNS_HIGH_RES = new String[] {
+        public static final String TABLE_NAME = "icons";
+        public static final String COLUMN_ROWID = "rowid";
+        public static final String COLUMN_COMPONENT = "componentName";
+        public static final String COLUMN_USER = "profileId";
+        public static final String COLUMN_LAST_UPDATED = "lastUpdated";
+        public static final String COLUMN_VERSION = "version";
+        public static final String COLUMN_ICON = "icon";
+        public static final String COLUMN_ICON_COLOR = "icon_color";
+        public static final String COLUMN_LABEL = "label";
+        public static final String COLUMN_SYSTEM_STATE = "system_state";
+        public static final String COLUMN_KEYWORDS = "keywords";
+
+        public static final String[] COLUMNS_HIGH_RES = new String[] {
                 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON };
-        public final static String[] COLUMNS_LOW_RES = new String[] {
+        public static final String[] COLUMNS_LOW_RES = new String[] {
                 IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL };
 
         public IconDB(Context context, String dbFileName, int iconPixelSize) {
@@ -529,21 +540,23 @@
 
         @Override
         protected void onCreateTable(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
-                    COLUMN_COMPONENT + " TEXT NOT NULL, " +
-                    COLUMN_USER + " INTEGER NOT NULL, " +
-                    COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_ICON + " BLOB, " +
-                    COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " +
-                    COLUMN_LABEL + " TEXT, " +
-                    COLUMN_SYSTEM_STATE + " TEXT, " +
-                    "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
-                    ");");
+            db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
+                    + COLUMN_COMPONENT + " TEXT NOT NULL, "
+                    + COLUMN_USER + " INTEGER NOT NULL, "
+                    + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, "
+                    + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, "
+                    + COLUMN_ICON + " BLOB, "
+                    + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, "
+                    + COLUMN_LABEL + " TEXT, "
+                    + COLUMN_SYSTEM_STATE + " TEXT, "
+                    + COLUMN_KEYWORDS + " TEXT, "
+                    + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") "
+                    + ");");
         }
     }
 
-    private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, String packageName) {
+    private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
+            String packageName, @Nullable String keywords) {
         ContentValues values = new ContentValues();
         values.put(IconDB.COLUMN_ICON,
                 bitmapInfo.isLowRes() ? null : GraphicsUtils.flattenBitmap(bitmapInfo.icon));
@@ -551,7 +564,7 @@
 
         values.put(IconDB.COLUMN_LABEL, label);
         values.put(IconDB.COLUMN_SYSTEM_STATE, getIconSystemState(packageName));
-
+        values.put(IconDB.COLUMN_KEYWORDS, keywords);
         return values;
     }
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index addb51f..e40a9c2 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,8 +17,11 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.LocaleList;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.icons.BitmapInfo;
 
 public interface CachingLogic<T> {
@@ -30,4 +33,19 @@
     CharSequence getLabel(T object);
 
     void loadIcon(Context context, T object, BitmapInfo target);
+
+    /**
+     * Provides a option list of keywords to associate with this object
+     */
+    @Nullable
+    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/protos/launcher_log.proto b/protos/launcher_log.proto
index 49fd436..fd36d4b 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -118,6 +118,7 @@
   APP_USAGE_SETTINGS = 18;
   BACK_GESTURE = 19;
   UNDO = 20;
+  DISMISS_PREDICTION = 21;
 }
 
 enum TipType {
@@ -136,7 +137,8 @@
     AUTOMATED = 1;
     COMMAND = 2;
     TIP = 3;
-    // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
+    SOFT_KEYBOARD = 4;
+    // HARD_KEYBOARD, ASSIST
   }
 
   enum Touch {
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 332e0fa..826a275 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -23,6 +23,7 @@
     package="com.android.launcher3" >
 
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+    <uses-permission android:name="android.permission.VIBRATE" />
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
@@ -87,6 +88,7 @@
         <activity android:name="com.android.quickstep.LockScreenRecentsActivity"
                   android:theme="@android:style/Theme.NoDisplay"
                   android:showOnLockScreen="true"
+                  android:taskAffinity="${packageName}.locktask"
                   android:directBootAware="true" />
 
     </application>
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml
index 1ddd3f5..ed3ba92 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/recents_ui_overrides/res/values/override.xml
@@ -24,5 +24,7 @@
   <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
+
+  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
 </resources>
 
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..114fd8e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,31 +17,39 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+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.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;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -56,6 +64,9 @@
     public static final int INDEX_SHELF_ANIM = 0;
     public static final int INDEX_RECENTS_FADE_ANIM = 1;
     public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
+    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
+
+    public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
 
     public LauncherAppTransitionManagerImpl(Context context) {
         super(context);
@@ -144,20 +155,66 @@
 
     @Override
     public int getStateElementAnimationsCount() {
-        return 3;
+        return 4;
     }
 
     @Override
     public Animator createStateElementAnimation(int index, float... values) {
         switch (index) {
-            case INDEX_SHELF_ANIM:
-                return mLauncher.getAllAppsController().createSpringAnimation(values);
+            case INDEX_SHELF_ANIM: {
+                AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+                Animator springAnim = aatc.createSpringAnimation(values);
+
+                if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                    // Translate hotseat with the shelf until reaching overview.
+                    float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+                    ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+                    float shiftRange = aatc.getShiftRange();
+                    if (values.length == 1) {
+                        values = new float[] {aatc.getProgress(), values[0]};
+                    }
+                    ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+                    hotseatAnim.addUpdateListener(anim -> {
+                        float progress = (Float) anim.getAnimatedValue();
+                        if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+                            float hotseatShift = (progress - overviewProgress) * shiftRange;
+                            mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+                        }
+                    });
+                    hotseatAnim.setInterpolator(LINEAR);
+                    hotseatAnim.setDuration(springAnim.getDuration());
+
+                    AnimatorSet anim = new AnimatorSet();
+                    anim.play(hotseatAnim);
+                    anim.play(springAnim);
+                    return anim;
+                }
+
+                return springAnim;
+            }
             case INDEX_RECENTS_FADE_ANIM:
                 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);
+            case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
+                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);
+                }
+                LauncherStateManager stateManager = mLauncher.getStateManager();
+                return stateManager.createAtomicAnimation(
+                        stateManager.getCurrentStableState(), OVERVIEW, builder,
+                        ANIM_ALL, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+            }
+
             default:
                 return super.createStateElementAnimation(index, values);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index 311db21..425fb13 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -31,6 +31,9 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -41,9 +44,6 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.util.Themes;
 
-import androidx.annotation.ColorInt;
-import androidx.core.content.ContextCompat;
-
 /**
  * A view which shows a horizontal divider
  */
@@ -288,10 +288,10 @@
     }
 
     @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
-            PropertySetter setter, Interpolator fadeInterpolator) {
+    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
         // Don't use setViewAlpha as we want to control the visibility ourselves.
-        setter.setFloat(this, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+        setter.setFloat(this, ALPHA, hasAllAppsContent ? 1 : 0, allAppsFade);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 4ecc39c..65e69b6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -17,6 +17,8 @@
 
 import static android.content.pm.PackageManager.MATCH_INSTANT;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -30,8 +32,12 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
@@ -45,11 +51,6 @@
 import java.util.List;
 import java.util.Map;
 
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
 /**
  * Utility class which loads and caches predicted items like instant apps and shortcuts, before
  * they can be displayed on the UI
@@ -77,7 +78,7 @@
 
     public DynamicItemCache(Context context, Runnable onUpdateCallback) {
         mContext = context;
-        mWorker = new Handler(LauncherModel.getWorkerLooper(), this::handleWorkerMessage);
+        mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
         mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
         mInstantAppResolver = InstantAppResolver.newInstance(context);
         mOnUpdateCallback = onUpdateCallback;
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 8f1282d..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");
@@ -16,6 +16,7 @@
 package com.android.launcher3.appprediction;
 
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.prediction.AppPredictionContext;
@@ -34,19 +35,25 @@
 import android.util.Log;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.AppLaunchEventsPlugin;
+import com.android.systemui.plugins.PluginListener;
 
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
+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;
@@ -65,10 +73,14 @@
 
     public PredictionAppTracker(Context context) {
         mContext = context;
-        mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage);
+        mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
         InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
 
         mMessageHandler.sendEmptyMessage(MSG_INIT);
+
+        mAppLaunchEventsPluginsList = new ArrayList<>();
+        PluginManagerWrapper.INSTANCE.get(context)
+                .addPluginListener(this, AppLaunchEventsPlugin.class, true);
     }
 
     @UiThread
@@ -95,6 +107,10 @@
     private AppPredictor createPredictor(Client client, int count) {
         AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
 
+        if (apm == null) {
+            return null;
+        }
+
         AppPredictor predictor = apm.createAppPredictionSession(
                 new AppPredictionContext.Builder(mContext)
                         .setUiSurface(client.id)
@@ -112,7 +128,7 @@
      */
     @WorkerThread
     @Nullable
-    public Bundle getAppPredictionContextExtras(Client client){
+    public Bundle getAppPredictionContextExtras(Client client) {
         return null;
     }
 
@@ -124,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;
@@ -163,37 +179,98 @@
         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)
-                    .setClassName(shortcutId)
-                    .build();
+        AppTarget target = new AppTarget.Builder(
+                new AppTargetId("shortcut:" + shortcutId), packageName, user)
+                .setClassName(shortcutId)
+                .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
     @UiThread
     public void onStartApp(ComponentName cn, UserHandle user, String container) {
         if (cn != null) {
-            AppTarget target = new AppTarget
-                    .Builder(new AppTargetId("app:" + cn), cn.getPackageName(), user)
-                        .setClassName(cn.getClassName())
-                        .build();
+            AppTarget target = new AppTarget.Builder(
+                    new AppTargetId("app:" + cn), cn.getPackageName(), user)
+                    .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)
+            );
         }
     }
 
+    @Override
+    @UiThread
+    public void onDismissApp(ComponentName cn, UserHandle user, String container) {
+        if (cn == null) return;
+        AppTarget target = new AppTarget.Builder(
+                new AppTargetId("app: " + cn), cn.getPackageName(), user)
+                .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
+    private void sendEvent(AppTarget target, String container, int eventId) {
+        AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
+                .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
+                .build();
+        Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
+    }
+
     @UiThread
     private void sendLaunch(AppTarget target, String container) {
-        AppTargetEvent event = new AppTargetEvent.Builder(target, AppTargetEvent.ACTION_LAUNCH)
-                .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container)
-                .build();
-        Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
+        sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH);
+    }
+
+    @UiThread
+    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 cb5cbdd..f82af62 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
@@ -32,6 +32,9 @@
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
@@ -40,6 +43,7 @@
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
@@ -62,9 +66,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 @TargetApi(Build.VERSION_CODES.P)
 public class PredictionRowView extends LinearLayout implements
         LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
@@ -80,7 +81,7 @@
 
                 @Override
                 public Integer get(PredictionRowView view) {
-                    return view.mIconCurrentTextAlpha;
+                    return view.mIconLastSetTextAlpha;
                 }
             };
 
@@ -92,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<>();
@@ -103,6 +104,8 @@
 
     private final int mIconTextColor;
     private final int mIconFullTextAlpha;
+    private int mIconLastSetTextAlpha;
+    // Might use mIconFullTextAlpha instead of mIconLastSetTextAlpha if we are translucent.
     private int mIconCurrentTextAlpha;
 
     private FloatingHeaderView mParent;
@@ -126,7 +129,7 @@
 
         mFocusHelper = new SimpleFocusIndicatorHelper(this);
 
-        mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numColumns;
+        mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns;
         mLauncher = Launcher.getLauncher(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
 
@@ -224,6 +227,7 @@
 
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
+        mNumPredictedAppsPerRow = dp.inv.numAllAppsColumns;
         removeAllViews();
         applyPredictionApps();
     }
@@ -272,14 +276,14 @@
         boolean predictionsEnabled = predictionCount > 0;
         if (predictionsEnabled != mPredictionsEnabled) {
             mPredictionsEnabled = predictionsEnabled;
-            mLauncher.reapplyUi();
+            mLauncher.reapplyUi(false /* cancelCurrentAnimation */);
             updateVisibility();
         }
         mParent.onHeightUpdated();
     }
 
     private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) {
-        if (getAppsStore().getApps().isEmpty()) {
+        if (getAppsStore().getApps().length == 0) {
             // Apps have not been bound yet.
             return Collections.emptyList();
         }
@@ -288,7 +292,9 @@
         for (ComponentKeyMapper mapper : components) {
             ItemInfoWithIcon info = mapper.getApp(getAppsStore());
             if (info != null) {
-                predictedApps.add(info);
+                ItemInfoWithIcon predictedApp = info.clone();
+                predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
+                predictedApps.add(predictedApp);
             } else {
                 if (FeatureFlags.IS_DOGFOOD_BUILD) {
                     Log.e(TAG, "Predicted app not found: " + mapper);
@@ -315,8 +321,14 @@
         }
     }
 
-    public void setTextAlpha(int alpha) {
-        mIconCurrentTextAlpha = alpha;
+    public void setTextAlpha(int textAlpha) {
+        mIconLastSetTextAlpha = textAlpha;
+        if (getAlpha() < 1 && textAlpha > 0) {
+            // If the entire header is translucent, make sure the text is at full opacity so it's
+            // not double-translucent. However, we support keeping the text invisible (alpha == 0).
+            textAlpha = mIconFullTextAlpha;
+        }
+        mIconCurrentTextAlpha = textAlpha;
         int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha);
         for (int i = 0; i < getChildCount(); i++) {
             ((BubbleTextView) getChildAt(i)).setTextColor(iconColor);
@@ -324,6 +336,13 @@
     }
 
     @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+        // Reapply text alpha so that we update it to be full alpha if the row is now translucent.
+        setTextAlpha(mIconLastSetTextAlpha);
+    }
+
+    @Override
     public boolean hasOverlappingRendering() {
         return false;
     }
@@ -351,23 +370,15 @@
     }
 
     @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
-            PropertySetter setter, Interpolator fadeInterpolator) {
-        boolean isDrawn = getAlpha() > 0;
-        int textAlpha = hasHeaderExtra
-                ? (hasContent ? mIconFullTextAlpha : 0) // Text follows the content visibility
-                : mIconCurrentTextAlpha; // Leave as before
-        if (!isDrawn) {
-            // If the header is not drawn, no need to animate the text alpha
-            setTextAlpha(textAlpha);
-        } else {
-            setter.setInt(this, TEXT_ALPHA, textAlpha, fadeInterpolator);
-        }
-
+    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
+        // Text follows all apps visibility
+        int textAlpha = hasHeaderExtra && hasAllAppsContent ? mIconFullTextAlpha : 0;
+        setter.setInt(this, TEXT_ALPHA, textAlpha, allAppsFade);
         setter.setFloat(mOverviewScrollFactor, AnimatedFloat.VALUE,
-                (hasHeaderExtra && !hasContent) ? 1 : 0, LINEAR);
+                (hasHeaderExtra && !hasAllAppsContent) ? 1 : 0, LINEAR);
         setter.setFloat(mContentAlphaFactor, AnimatedFloat.VALUE, hasHeaderExtra ? 1 : 0,
-                fadeInterpolator);
+                headerFade);
     }
 
     @Override
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..9c66107 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,26 +24,33 @@
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+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;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.IntStream;
 
 /**
  * Handler responsible to updating the UI due to predicted apps changes. Operations:
@@ -58,7 +65,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 +160,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 +172,8 @@
             mPendingState = null;
         }
         if (mPendingState == null) {
-            mAppsView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            Launcher.getLauncher(mAppsView.getContext()).getStateManager()
+                    .removeStateListener(this);
         }
     }
 
@@ -170,9 +181,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);
         }
     }
 
@@ -318,6 +328,30 @@
         return mCurrentState;
     }
 
+    /**
+     * Fill in predicted_rank field based on app prediction.
+     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
+     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
+     */
+    public static void fillInPredictedRank(
+            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
+        final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
+        if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
+                || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
+            return;
+        }
+        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
+        final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
+        IntStream.range(0, predictedApps.size())
+                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
+                .findFirst()
+                .ifPresent((rank) -> target.predictedRank = rank);
+    }
+
     public static class PredictionState {
 
         public boolean isEnabled;
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 a3c2e3c..cac170c 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
@@ -28,17 +28,17 @@
 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.NoButtonQuickSwitchTouchController;
 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;
@@ -145,7 +145,7 @@
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(launcher.getDragController());
         if (mode == NO_BUTTON) {
-            list.add(new QuickSwitchTouchController(launcher));
+            list.add(new NoButtonQuickSwitchTouchController(launcher));
             list.add(new NavBarToHomeTouchController(launcher));
             list.add(new FlingAndHoldTouchController(launcher));
         } else {
@@ -164,6 +164,11 @@
             }
         }
 
+        if (FeatureFlags.PULL_DOWN_STATUS_BAR
+                && !launcher.getDeviceProfile().isMultiWindowMode) {
+            list.add(new StatusBarTouchController(launcher));
+        }
+
         list.add(new LauncherTaskViewController(launcher));
         return list.toArray(new TouchController[list.size()]);
     }
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 5ee08c1..63ac528 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
@@ -68,8 +68,16 @@
         if (taskCount == 0) {
             return super.getOverviewScaleAndTranslation(launcher);
         }
-        TaskView dummyTask = recentsView.getTaskViewAt(Math.max(taskCount - 1,
-                recentsView.getCurrentPage()));
+        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.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask)
                 .getScaleAndTranslation();
     }
@@ -90,7 +98,7 @@
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
             // Translate hotseat offscreen if we show it in overview.
             ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
-            scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+            scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
                     launcher.getDeviceProfile());
             return scaleAndTranslation;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index c954762..427206a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.uioverrides.states;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 
@@ -43,6 +45,7 @@
         if (this == OVERVIEW_PEEK && fromState == NORMAL) {
             builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
             builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 5543860..ed5dba1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
@@ -43,10 +44,10 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -91,8 +92,19 @@
     @Override
     public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
         if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
-            // If the hotseat icons are visible in overview, keep them in their normal position.
-            return super.getWorkspaceScaleAndTranslation(launcher);
+            DeviceProfile dp = launcher.getDeviceProfile();
+            if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+                return new ScaleAndTranslation(1, 0, 0);
+            } else {
+                float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+                // Distance between the screen center (which is the pivotY for hotseat) and the
+                // bottom of the hotseat (which we want to preserve)
+                float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+                // On scaling, the bottom edge is moved closer to the pivotY. We move the
+                // hotseat back down so that the bottom edge's position is preserved.
+                float translationY = distanceFromBottom * (1 - scale);
+                return new ScaleAndTranslation(scale, 0, translationY);
+            }
         }
         return getWorkspaceScaleAndTranslation(launcher);
     }
@@ -128,14 +140,15 @@
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
         } else {
+            boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
+                    && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
             return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
-                    (launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
-                            ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
+                    (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
         }
     }
 
     @Override
-    public float getWorkspaceScrimAlpha(Launcher launcher) {
+    public float getOverviewScrimAlpha(Launcher launcher) {
         return 0.5f;
     }
 
@@ -159,11 +172,7 @@
     }
 
     public static float getDefaultSwipeHeight(Launcher launcher) {
-        return getDefaultSwipeHeight(launcher.getDeviceProfile());
-    }
-
-    public static float getDefaultSwipeHeight(DeviceProfile dp) {
-        return dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+        return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
     }
 
     @Override
@@ -197,6 +206,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
             builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
         }
     }
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 3fe4bcf..3231f37 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
@@ -16,34 +16,37 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 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.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
 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.OVERSHOOT_1_2;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
@@ -72,7 +75,7 @@
 
     @Override
     protected long getAtomicDuration() {
-        return 300;
+        return LauncherAppTransitionManagerImpl.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
     }
 
     @Override
@@ -100,8 +103,10 @@
                     }
                 });
                 mPeekAnim.start();
-                recentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+                VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+                mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
+                        peekDuration, 0);
             });
         }
     }
@@ -120,12 +125,36 @@
             LauncherState toState) {
         if (fromState == NORMAL && toState == ALL_APPS) {
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            // 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(
+                    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);
     }
@@ -140,20 +169,14 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
+    public void onDragEnd(float velocity) {
         if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
             if (mPeekAnim != null) {
                 mPeekAnim.cancel();
             }
 
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
-            builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
-                builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
-            }
-            AnimatorSet overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
-                    NORMAL, OVERVIEW, builder, ANIM_ALL, ATOMIC_DURATION);
+            Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+                    INDEX_PAUSE_TO_OVERVIEW_ANIM);
             overviewAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -162,12 +185,32 @@
             });
             overviewAnim.start();
         } else {
-            super.onDragEnd(velocity, fling);
+            super.onDragEnd(velocity);
+        }
+
+        View searchView = mLauncher.getAppsView().getSearchView();
+        if (searchView instanceof FeedbackHandler) {
+            ((FeedbackHandler) searchView).resetFeedback();
         }
         mMotionPauseDetector.clear();
     }
 
     @Override
+    protected void goToTargetState(LauncherState targetState, int logAction) {
+        if (mPeekAnim != null && mPeekAnim.isStarted()) {
+            // Don't jump to the target state until overview is no longer peeking.
+            mPeekAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    FlingAndHoldTouchController.super.goToTargetState(targetState, logAction);
+                }
+            });
+        } else {
+            super.goToTargetState(targetState, logAction);
+        }
+    }
+
+    @Override
     protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
         if (handlingOverviewAnim()) {
             // We don't want the state transition to all apps to animate overview,
@@ -175,4 +218,16 @@
             builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
         }
     }
+
+    /**
+     * Interface for views with feedback animation requiring reset
+     */
+    public interface FeedbackHandler {
+
+        /**
+         * reset searchWidget feedback
+         */
+        void resetFeedback();
+    }
+
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d66af1a..ef50c7b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -43,21 +44,25 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.config.BaseFlags;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 /**
  * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
  */
-public class NavBarToHomeTouchController implements TouchController, SwipeDetector.Listener {
+public class NavBarToHomeTouchController implements TouchController,
+        SingleAxisSwipeDetector.Listener {
 
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
 
     private final Launcher mLauncher;
-    private final SwipeDetector mSwipeDetector;
+    private final SingleAxisSwipeDetector mSwipeDetector;
     private final float mPullbackDistance;
 
     private boolean mNoIntercept;
@@ -67,7 +72,8 @@
 
     public NavBarToHomeTouchController(Launcher launcher) {
         mLauncher = launcher;
-        mSwipeDetector = new SwipeDetector(mLauncher, this, SwipeDetector.VERTICAL);
+        mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
+                SingleAxisSwipeDetector.VERTICAL);
         mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance);
     }
 
@@ -79,7 +85,8 @@
             if (mNoIntercept) {
                 return false;
             }
-            mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+            mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE,
+                    false /* ignoreSlop */);
         }
 
         if (mNoIntercept) {
@@ -101,6 +108,10 @@
         if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
             return true;
         }
+        if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+                && AssistantUtilities.isExcludedAssistantRunning()) {
+            return true;
+        }
         return false;
     }
 
@@ -173,7 +184,8 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
+    public void onDragEnd(float velocity) {
+        boolean fling = mSwipeDetector.isFling(velocity);
         final int logAction = fling ? Touch.FLING : Touch.SWIPE;
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
@@ -190,6 +202,8 @@
                 AbstractFloatingView.closeAllOpenViews(mLauncher);
                 logStateChange(topOpenView.getLogContainerType(), logAction);
             }
+            ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         } else {
             // Quickly return to the state we came from (we didn't move far).
             ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
new file mode 100644
index 0000000..76374af
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -0,0 +1,473 @@
+/*
+ * 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.touchcontrollers;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_PAUSE_TO_OVERVIEW_ANIM;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
+import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
+import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.BothAxesSwipeDetector;
+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;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.LauncherRecentsView;
+
+/**
+ * Handles quick switching to a recent task from the home screen. To give as much flexibility to
+ * the user as possible, also handles swipe up and hold to go to overview and swiping back home.
+ */
+public class NoButtonQuickSwitchTouchController implements TouchController,
+        BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
+
+    /** The minimum progress of the scale/translationY animation until drag end. */
+    private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+    private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
+    private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
+    private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+
+    private final Launcher mLauncher;
+    private final BothAxesSwipeDetector mSwipeDetector;
+    private final float mXRange;
+    private final float mYRange;
+    private final MotionPauseDetector mMotionPauseDetector;
+    private final float mMotionPauseMinDisplacement;
+
+    private boolean mNoIntercept;
+    private LauncherState mStartState;
+
+    private ShelfPeekAnim mShelfPeekAnim;
+    private boolean mIsHomeScreenVisible = true;
+
+    // As we drag, we control 3 animations: one to get non-overview components out of the way,
+    // and the other two to set overview properties based on x and y progress.
+    private AnimatorPlaybackController mNonOverviewAnim;
+    private AnimatorPlaybackController mXOverviewAnim;
+    private AnimatorPlaybackController mYOverviewAnim;
+
+    public NoButtonQuickSwitchTouchController(Launcher launcher) {
+        mLauncher = launcher;
+        mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
+        mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
+        mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+        mMotionPauseDetector = new MotionPauseDetector(mLauncher);
+        mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
+                R.dimen.motion_pause_detector_min_displacement_from_app);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Only detect horizontal swipe for intercept, then we will allow swipe up as well.
+            mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
+                    false /* ignoreSlopWhenSettling */);
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mSwipeDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mSwipeDetector.onTouchEvent(ev);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if (!mLauncher.isInState(LauncherState.NORMAL)) {
+            return false;
+        }
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
+            return false;
+        }
+        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        mMotionPauseDetector.clear();
+        if (start) {
+            mShelfPeekAnim = ((QuickstepAppTransitionManagerImpl) mLauncher
+                    .getAppTransitionManager()).getShelfPeekAnim();
+
+            mStartState = mLauncher.getStateManager().getState();
+
+            mMotionPauseDetector.setOnMotionPauseListener(this);
+
+            // We have detected horizontal drag start, now allow swipe up as well.
+            mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP,
+                    false /* ignoreSlopWhenSettling */);
+
+            setupAnimators();
+        }
+    }
+
+    @Override
+    public void onMotionPauseChanged(boolean isPaused) {
+        ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
+        if (shelfState == PEEK) {
+            // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+            allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
+                    new AnimationConfig(), builder);
+            builder.build().setDuration(0).start();
+
+            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+                // Hotseat was hidden, but we need it visible when peeking.
+                mLauncher.getHotseat().setAlpha(1);
+            }
+        }
+        mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
+                ShelfPeekAnim.DURATION);
+        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    private void setupAnimators() {
+        // Animate the non-overview components (e.g. workspace, shelf) out of the way.
+        AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
+        nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
+        nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
+        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL);
+        mNonOverviewAnim.dispatchOnStart();
+
+        setupOverviewAnimators();
+    }
+
+    /** Create state animation to control non-overview components. */
+    private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
+            @LauncherStateManager.AnimationComponents int animComponents) {
+        builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
+        long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
+        mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
+                builder, accuracy, this::clearState, animComponents);
+    }
+
+    private void setupOverviewAnimators() {
+        final LauncherState fromState = QUICK_SWITCH;
+        final LauncherState toState = OVERVIEW;
+        LauncherState.ScaleAndTranslation fromScaleAndTranslation = fromState
+                .getOverviewScaleAndTranslation(mLauncher);
+        LauncherState.ScaleAndTranslation toScaleAndTranslation = toState
+                .getOverviewScaleAndTranslation(mLauncher);
+        // Update RecentView's translationX to have it start offscreen.
+        LauncherRecentsView recentsView = mLauncher.getOverviewPanel();
+        float startScale = Utilities.mapRange(
+                SCALE_DOWN_INTERPOLATOR.getInterpolation(Y_ANIM_MIN_PROGRESS),
+                fromScaleAndTranslation.scale,
+                toScaleAndTranslation.scale);
+        fromScaleAndTranslation.translationX = recentsView.getOffscreenTranslationX(startScale);
+
+        // Set RecentView's initial properties.
+        recentsView.setScaleX(fromScaleAndTranslation.scale);
+        recentsView.setScaleY(fromScaleAndTranslation.scale);
+        recentsView.setTranslationX(fromScaleAndTranslation.translationX);
+        recentsView.setTranslationY(fromScaleAndTranslation.translationY);
+        recentsView.setContentAlpha(1);
+
+        // As we drag right, animate the following properties:
+        //   - RecentsView translationX
+        //   - OverviewScrim
+        AnimatorSet xOverviewAnim = new AnimatorSet();
+        xOverviewAnim.play(ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_X,
+                toScaleAndTranslation.translationX));
+        xOverviewAnim.play(ObjectAnimator.ofFloat(
+                mLauncher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+                toState.getOverviewScrimAlpha(mLauncher)));
+        long xAccuracy = (long) (mXRange * 2);
+        xOverviewAnim.setDuration(xAccuracy);
+        mXOverviewAnim = AnimatorPlaybackController.wrap(xOverviewAnim, xAccuracy);
+        mXOverviewAnim.dispatchOnStart();
+
+        // As we drag up, animate the following properties:
+        //   - RecentsView translationY
+        //   - RecentsView scale
+        //   - RecentsView fullscreenProgress
+        AnimatorSet yAnimation = new AnimatorSet();
+        Animator translateYAnim = ObjectAnimator.ofFloat(recentsView, View.TRANSLATION_Y,
+                toScaleAndTranslation.translationY);
+        Animator scaleAnim = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY,
+                toScaleAndTranslation.scale);
+        Animator fullscreenProgressAnim = ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS,
+                fromState.getOverviewFullscreenProgress(), toState.getOverviewFullscreenProgress());
+        scaleAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+        fullscreenProgressAnim.setInterpolator(SCALE_DOWN_INTERPOLATOR);
+        yAnimation.play(translateYAnim);
+        yAnimation.play(scaleAnim);
+        yAnimation.play(fullscreenProgressAnim);
+        long yAccuracy = (long) (mYRange * 2);
+        yAnimation.setDuration(yAccuracy);
+        mYOverviewAnim = AnimatorPlaybackController.wrap(yAnimation, yAccuracy);
+        mYOverviewAnim.dispatchOnStart();
+    }
+
+    @Override
+    public boolean onDrag(PointF displacement, MotionEvent ev) {
+        float xProgress = Math.max(0, displacement.x) / mXRange;
+        float yProgress = Math.max(0, -displacement.y) / mYRange;
+        yProgress = Utilities.mapRange(yProgress, Y_ANIM_MIN_PROGRESS, 1f);
+
+        boolean wasHomeScreenVisible = mIsHomeScreenVisible;
+        if (wasHomeScreenVisible && mNonOverviewAnim != null) {
+            mNonOverviewAnim.setPlayFraction(xProgress);
+        }
+        mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
+                <= 1 - ALPHA_CUTOFF_THRESHOLD;
+
+        if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
+            // Get the shelf all the way offscreen so it pops up when we decide to peek it.
+            mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
+        }
+
+        // Only allow motion pause if the home screen is invisible, since some
+        // home screen elements will appear in the shelf on motion pause.
+        mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
+                || -displacement.y < mMotionPauseMinDisplacement);
+        mMotionPauseDetector.addPosition(displacement.y, ev.getEventTime());
+
+        if (mIsHomeScreenVisible) {
+            // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
+            mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
+        }
+
+        if (mXOverviewAnim != null) {
+            mXOverviewAnim.setPlayFraction(xProgress);
+        }
+        if (mYOverviewAnim != null) {
+            mYOverviewAnim.setPlayFraction(yProgress);
+        }
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(PointF velocity) {
+        boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
+        boolean verticalFling = mSwipeDetector.isFling(velocity.y);
+        boolean noFling = !horizontalFling && !verticalFling;
+        int logAction = noFling ? Touch.SWIPE : Touch.FLING;
+        if (mMotionPauseDetector.isPaused() && noFling) {
+            cancelAnimations();
+
+            Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+                    INDEX_PAUSE_TO_OVERVIEW_ANIM);
+            overviewAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onAnimationToStateCompleted(OVERVIEW, logAction);
+                }
+            });
+            overviewAnim.start();
+            return;
+        }
+
+        final LauncherState targetState;
+        if (horizontalFling && verticalFling) {
+            if (velocity.x < 0) {
+                // Flinging left and up or down both go back home.
+                targetState = NORMAL;
+            } else {
+                if (velocity.y > 0) {
+                    // Flinging right and down goes to quick switch.
+                    targetState = QUICK_SWITCH;
+                } else {
+                    // Flinging up and right could go either home or to quick switch.
+                    // Determine the target based on the higher velocity.
+                    targetState = Math.abs(velocity.x) > Math.abs(velocity.y)
+                        ? QUICK_SWITCH : NORMAL;
+                }
+            }
+        } else if (horizontalFling) {
+            targetState = velocity.x > 0 ? QUICK_SWITCH : NORMAL;
+        } else if (verticalFling) {
+            targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
+        } else {
+            // If user isn't flinging, just snap to the closest state based on x progress.
+            boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
+            targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+        }
+
+        // Animate the various components to the target state.
+
+        float xProgress = mXOverviewAnim.getProgressFraction();
+        float startXProgress = Utilities.boundToRange(xProgress
+                + velocity.x * getSingleFrameMs(mLauncher) / mXRange, 0f, 1f);
+        final float endXProgress = targetState == NORMAL ? 0 : 1;
+        long xDuration = BaseSwipeDetector.calculateDuration(velocity.x,
+                Math.abs(endXProgress - startXProgress));
+        ValueAnimator xOverviewAnim = mXOverviewAnim.getAnimationPlayer();
+        xOverviewAnim.setFloatValues(startXProgress, endXProgress);
+        xOverviewAnim.setDuration(xDuration)
+                .setInterpolator(scrollInterpolatorForVelocity(velocity.x));
+        mXOverviewAnim.dispatchOnStartWithVelocity(endXProgress, velocity.x);
+
+        boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
+
+        float yProgress = mYOverviewAnim.getProgressFraction();
+        float startYProgress = Utilities.boundToRange(yProgress
+                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+        final float endYProgress;
+        if (flingUpToNormal) {
+            endYProgress = 1;
+        } else if (targetState == NORMAL) {
+            // Keep overview at its current scale/translationY as it slides off the screen.
+            endYProgress = startYProgress;
+        } else {
+            endYProgress = 0;
+        }
+        long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
+                Math.abs(endYProgress - startYProgress));
+        ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
+        yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+        yOverviewAnim.setDuration(yDuration);
+        mYOverviewAnim.dispatchOnStartWithVelocity(endYProgress, velocity.y);
+
+        ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+        if (flingUpToNormal && !mIsHomeScreenVisible) {
+            // We are flinging to home while workspace is invisible, run the same staggered
+            // animation as from an app.
+            // Update mNonOverviewAnim to do nothing so it doesn't interfere.
+            updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
+            nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
+
+            new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */)
+                    .start();
+        } else {
+            boolean canceled = targetState == NORMAL;
+            if (canceled) {
+                // Let the state manager know that the animation didn't go to the target state,
+                // but don't clean up yet (we already clean up when the animation completes).
+                mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
+            }
+            float startProgress = mNonOverviewAnim.getProgressFraction();
+            float endProgress = canceled ? 0 : 1;
+            nonOverviewAnim.setFloatValues(startProgress, endProgress);
+            mNonOverviewAnim.dispatchOnStartWithVelocity(endProgress,
+                    horizontalFling ? velocity.x : velocity.y);
+        }
+
+        nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
+        mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction));
+
+        cancelAnimations();
+        xOverviewAnim.start();
+        yOverviewAnim.start();
+        nonOverviewAnim.start();
+    }
+
+    private void onAnimationToStateCompleted(LauncherState targetState, int logAction) {
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
+                LauncherLogProto.ContainerType.NAVBAR,
+                mStartState.containerType,
+                targetState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
+        mLauncher.getStateManager().goToState(targetState, false, this::clearState);
+    }
+
+    private int getDirectionForLog() {
+        return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+    }
+
+    private void cancelAnimations() {
+        if (mNonOverviewAnim != null) {
+            mNonOverviewAnim.getAnimationPlayer().cancel();
+        }
+        if (mXOverviewAnim != null) {
+            mXOverviewAnim.getAnimationPlayer().cancel();
+        }
+        if (mYOverviewAnim != null) {
+            mYOverviewAnim.getAnimationPlayer().cancel();
+        }
+        mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
+        mMotionPauseDetector.clear();
+    }
+
+    private void clearState() {
+        cancelAnimations();
+        mNonOverviewAnim = null;
+        mXOverviewAnim = null;
+        mYOverviewAnim = null;
+        mIsHomeScreenVisible = true;
+        mSwipeDetector.finishedScrolling();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
index 73f328b..9091168 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
@@ -75,7 +75,7 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
         return LauncherLogProto.ContainerType.WORKSPACE;
     }
 }
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 18b8af4..14216ff 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
@@ -30,6 +30,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.view.MotionEvent;
@@ -42,7 +43,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.quickstep.OverviewInteractionState;
@@ -50,7 +51,7 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 /**
  * Handles quick switching to a recent task from the home screen.
@@ -60,10 +61,10 @@
     private @Nullable TaskView mTaskToLaunch;
 
     public QuickSwitchTouchController(Launcher launcher) {
-        this(launcher, SwipeDetector.HORIZONTAL);
+        this(launcher, SingleAxisSwipeDetector.HORIZONTAL);
     }
 
-    protected QuickSwitchTouchController(Launcher l, SwipeDetector.Direction dir) {
+    protected QuickSwitchTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
         super(l, dir);
     }
 
@@ -95,6 +96,8 @@
         super.onDragStart(start);
         mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
         mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+        ActivityManagerWrapper.getInstance()
+            .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
     }
 
     @Override
@@ -154,7 +157,7 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
         return LauncherLogProto.ContainerType.NAVBAR;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 8e32bb3..ad02de1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,10 +16,13 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -32,7 +35,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.PendingAnimation;
@@ -46,15 +50,14 @@
  * Touch controller for handling task view card swipes
  */
 public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
-        extends AnimatorListenerAdapter implements TouchController, SwipeDetector.Listener {
-
-    private static final String TAG = "OverviewSwipeController";
+        extends AnimatorListenerAdapter implements TouchController,
+        SingleAxisSwipeDetector.Listener {
 
     // Progress after which the transition is assumed to be a success in case user does not fling
     public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
     protected final T mActivity;
-    private final SwipeDetector mDetector;
+    private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
 
@@ -74,7 +77,7 @@
     public TaskViewTouchController(T activity) {
         mActivity = activity;
         mRecentsView = activity.getOverviewPanel();
-        mDetector = new SwipeDetector(activity, this, SwipeDetector.VERTICAL);
+        mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
     }
 
     private boolean canInterceptTouch() {
@@ -113,7 +116,7 @@
             int directionsToDetectScroll = 0;
             boolean ignoreSlopWhenSettling = false;
             if (mCurrentAnimation != null) {
-                directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                directionsToDetectScroll = DIRECTION_BOTH;
                 ignoreSlopWhenSettling = true;
             } else {
                 mTaskBeingDragged = null;
@@ -126,12 +129,12 @@
                         if (!SysUINavigationMode.getMode(mActivity).hasGestures) {
                             // Don't allow swipe down to open if we don't support swipe up
                             // to enter overview.
-                            directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                            directionsToDetectScroll = DIRECTION_POSITIVE;
                         } else {
                             // The task can be dragged up to dismiss it,
                             // and down to open if it's the current page.
                             directionsToDetectScroll = i == mRecentsView.getCurrentPage()
-                                    ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE;
+                                    ? DIRECTION_BOTH : DIRECTION_POSITIVE;
                         }
                         break;
                     }
@@ -165,8 +168,8 @@
             return;
         }
         int scrollDirections = mDetector.getScrollDirections();
-        if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
-                || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
+        if (goingUp && ((scrollDirections & DIRECTION_POSITIVE) == 0)
+                || !goingUp && ((scrollDirections & DIRECTION_NEGATIVE) == 0)) {
             // Trying to re-init in an unsupported direction.
             return;
         }
@@ -243,7 +246,8 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
+    public void onDragEnd(float velocity) {
+        boolean fling = mDetector.isFling(velocity);
         final boolean goingToEnd;
         final int logAction;
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -260,14 +264,14 @@
             logAction = Touch.SWIPE;
             goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
         }
-        long animationDuration = SwipeDetector.calculateDuration(
+        long animationDuration = BaseSwipeDetector.calculateDuration(
                 velocity, goingToEnd ? (1 - progress) : progress);
         if (blockedFling && !goingToEnd) {
             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
         }
 
-        float nextFrameProgress = Utilities.boundToRange(
-                progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
+        float nextFrameProgress = Utilities.boundToRange(progress
+                + velocity * getSingleFrameMs(mActivity) / Math.abs(mEndDisplacement), 0f, 1f);
 
         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
index f1e4041..0ed5291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
@@ -17,12 +17,12 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 
 public class TransposedQuickSwitchTouchController extends QuickSwitchTouchController {
 
     public TransposedQuickSwitchTouchController(Launcher launcher) {
-        super(launcher, SwipeDetector.VERTICAL);
+        super(launcher, SingleAxisSwipeDetector.VERTICAL);
     }
 
     @Override
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 5e77e0a..ad90e16 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -81,6 +81,7 @@
                 });
         factory.onRemoteAnimationReceived(null);
         factory.createActivityController(RECENTS_LAUNCH_DURATION);
+        factory.setRecentsAttachedToAppWindow(true, false);
         mActivity = activity;
         mRecentsView = mActivity.getOverviewPanel();
         return false;
@@ -101,6 +102,7 @@
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
+                mHelper.onSwipeUpToRecentsComplete(mActivity);
                 if (mRecentsView != null) {
                     mRecentsView.animateUpRunningTaskIconScale();
                 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
new file mode 100644
index 0000000..e5d2b41
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -0,0 +1,493 @@
+/*
+ * 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.Utilities.postAsyncCallback;
+import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+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;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.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.views.RecentsView;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+
+import java.util.function.Consumer;
+
+/**
+ * Base class for swipe up handler with some utility methods
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView>
+        implements SwipeAnimationListener {
+
+    private static final String TAG = "BaseSwipeUpHandler";
+    protected static final Rect TEMP_RECT = new Rect();
+
+    // Start resisting when swiping past this factor of mTransitionDragLength.
+    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
+    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
+    // The distance needed to drag to reach the task size in recents.
+    protected int mTransitionDragLength;
+    // How much further we can drag past recents, as a factor of mTransitionDragLength.
+    protected float mDragLengthFactor = 1;
+
+    protected final Context mContext;
+    protected final OverviewComponentObserver mOverviewComponentObserver;
+    protected final ActivityControlHelper<T> mActivityControlHelper;
+    protected final RecentsModel mRecentsModel;
+    protected final int mRunningTaskId;
+
+    protected final ClipAnimationHelper mClipAnimationHelper;
+    protected final TransformParams mTransformParams = new TransformParams();
+
+    protected final Mode mMode;
+
+    // Shift in the range of [0, 1].
+    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+    // visible.
+    protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+    protected final ActivityInitListener mActivityInitListener;
+    protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+    protected T mActivity;
+    protected Q mRecentsView;
+    protected DeviceProfile mDp;
+    private final int mPageSpacing;
+
+    protected Runnable mGestureEndCallback;
+
+    protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
+    protected MultiStateCallback mStateCallback;
+
+    protected boolean mCanceled;
+    protected int mFinishingRecentsAnimationForNewTaskId = -1;
+
+    protected BaseSwipeUpHandler(Context context,
+            OverviewComponentObserver overviewComponentObserver,
+            RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
+        mContext = context;
+        mOverviewComponentObserver = overviewComponentObserver;
+        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+        mRecentsModel = recentsModel;
+        mActivityInitListener =
+                mActivityControlHelper.createActivityInitListener(this::onActivityInit);
+        mRunningTaskId = runningTaskId;
+        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
+                this::createNewInputProxyHandler);
+        mMode = SysUINavigationMode.getMode(context);
+
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+        mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
+        initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
+                .getDeviceProfile(mContext));
+    }
+
+    protected void setStateOnUiThread(int stateFlag) {
+        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+            mStateCallback.setState(stateFlag);
+        } else {
+            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
+        }
+    }
+
+    protected void performHapticFeedback() {
+        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
+    }
+
+    @UiThread
+    public void updateDisplacement(float displacement) {
+        // We are moving in the negative x/y direction
+        displacement = -displacement;
+        float shift;
+        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
+            shift = mDragLengthFactor;
+        } else {
+            float translation = Math.max(displacement, 0);
+            shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+            if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
+                float pullbackProgress = Utilities.getProgress(shift,
+                        DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
+                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+                shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
+                        * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
+            }
+        }
+
+        mCurrentShift.updateValue(shift);
+    }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        mGestureEndCallback = gestureEndCallback;
+    }
+
+    public abstract Intent getLaunchIntent();
+
+    protected void linkRecentsViewScroll() {
+        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+            mTransformParams.setSyncTransactionApplier(applier);
+            mRecentsAnimationWrapper.runOnInit(() ->
+                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+        });
+
+        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (moveWindowWithRecentsScroll()) {
+                updateFinalShift();
+            }
+        });
+        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
+        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+    }
+
+    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.getNextPageTaskView().launchTask(false /* animate */,
+                    true /* freezeTaskList */);
+        } else {
+            int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
+            mFinishingRecentsAnimationForNewTaskId = taskId;
+            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+                if (!mCanceled) {
+                    TaskView nextTask = mRecentsView.getTaskView(taskId);
+                    if (nextTask != null) {
+                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                                success -> {
+                                    resultCallback.accept(success);
+                                    if (!success) {
+                                        mActivityControlHelper.onLaunchTaskFailed(mActivity);
+                                        nextTask.notifyTaskLaunchFailed(TAG);
+                                    } else {
+                                        mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+                                    }
+                                }, mMainThreadHandler);
+                    }
+                    setStateOnUiThread(successStateFlag);
+                }
+                mCanceled = false;
+                mFinishingRecentsAnimationForNewTaskId = -1;
+            });
+        }
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+    }
+
+    @Override
+    public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
+        final Rect overviewStackBounds;
+        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+
+        if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
+            overviewStackBounds = mActivityControlHelper
+                    .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+            dp = dp.getMultiWindowProfile(mContext, new Point(
+                    overviewStackBounds.width(), overviewStackBounds.height()));
+        } else {
+            // If we are not in multi-window mode, home insets should be same as system insets.
+            dp = dp.copy(mContext);
+            overviewStackBounds = getStackBounds(dp);
+        }
+        dp.updateInsets(targetSet.homeContentInsets);
+        dp.updateIsSeascape(mContext);
+        if (runningTaskTarget != null) {
+            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+        }
+
+        mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
+        initTransitionEndpoints(dp);
+
+        mRecentsAnimationWrapper.setController(targetSet);
+    }
+
+    private Rect getStackBounds(DeviceProfile dp) {
+        if (mActivity != null) {
+            int loc[] = new int[2];
+            View rootView = mActivity.getRootView();
+            rootView.getLocationOnScreen(loc);
+            return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
+                    loc[1] + rootView.getHeight());
+        } else {
+            return new Rect(0, 0, dp.widthPx, dp.heightPx);
+        }
+    }
+
+    protected void initTransitionEndpoints(DeviceProfile dp) {
+        mDp = dp;
+
+        mTransitionDragLength = mActivityControlHelper.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));
+        }
+        mClipAnimationHelper.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;
+        }
+    }
+
+    /**
+     * Return true if the window should be translated horizontally if the recents view scrolls
+     */
+    protected abstract boolean moveWindowWithRecentsScroll();
+
+    protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @UiThread
+    protected abstract InputConsumer createNewInputProxyHandler();
+
+    /**
+     * Called when the value of {@link #mCurrentShift} changes
+     */
+    @UiThread
+    public abstract void updateFinalShift();
+
+    /**
+     * Called when motion pause is detected
+     */
+    public abstract void onMotionPauseChanged(boolean isPaused);
+
+    @UiThread
+    public void onGestureStarted() { }
+
+    @UiThread
+    public abstract void onGestureCancelled();
+
+    @UiThread
+    public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
+
+    public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
+
+    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
+
+    public void initWhenReady() {
+        // Preload the plan
+        mRecentsModel.getTasks(null);
+
+        mActivityInitListener.register();
+    }
+
+    /**
+     * Applies the transform on the recents animation without any additional null checks
+     */
+    protected void applyTransformUnchecked() {
+        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);
+    }
+
+    private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
+        float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
+        float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+        return TaskView.getCurveScaleForInterpolation(interpolation);
+    }
+
+    /**
+     * Creates an animation that transforms the current app window into the home app.
+     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+     * @param homeAnimationFactory The home animation factory.
+     */
+    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 targetRect = homeAnimationFactory.getWindowTargetRect();
+
+        final View floatingView = homeAnimationFactory.getFloatingView();
+        final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
+        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources());
+        if (isFloatingIconView) {
+            FloatingIconView fiv = (FloatingIconView) floatingView;
+            anim.addAnimatorListener(fiv);
+            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
+        }
+
+        AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
+
+        // 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 endRadius = startRect.width() / 6f;
+
+        float startTransformProgress = mTransformParams.getProgress();
+        float endTransformProgress = 1;
+
+        // 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.
+        final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+        anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
+
+            // Alpha interpolates between [1, 0] between progress values [start, end]
+            final float start = 0f;
+            final float end = 0.85f;
+
+            private float getWindowAlpha(float progress) {
+                if (progress <= start) {
+                    return 1f;
+                }
+                if (progress >= end) {
+                    return 0f;
+                }
+                return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5);
+            }
+
+            @Override
+            public void onUpdate(RectF currentRect, float progress) {
+                homeAnim.setPlayFraction(progress);
+
+                mTransformParams.setProgress(
+                        Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+                        .setCurrentRectAndTargetAlpha(currentRect, getWindowAlpha(progress));
+                if (isFloatingIconView) {
+                    mTransformParams.setCornerRadius(endRadius * progress + startRadius
+                            * (1f - progress));
+                }
+                mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
+                        false /* launcherOnTop */);
+
+                if (isFloatingIconView) {
+                    ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
+                            windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(),
+                            false);
+                }
+            }
+
+            @Override
+            public void onCancel() {
+                if (isFloatingIconView) {
+                    ((FloatingIconView) floatingView).fastFinish();
+                }
+            }
+        });
+        anim.addAnimatorListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                homeAnim.dispatchOnStart();
+            }
+
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                homeAnim.getAnimationPlayer().end();
+            }
+        });
+        return anim;
+    }
+
+    public interface Factory {
+
+        BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
+                long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
+    }
+
+    protected interface RunningWindowAnim {
+        void end();
+
+        void cancel();
+
+        static RunningWindowAnim wrap(Animator animator) {
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    animator.end();
+                }
+
+                @Override
+                public void cancel() {
+                    animator.cancel();
+                }
+            };
+        }
+
+        static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    rectFSpringAnim.end();
+                }
+
+                @Override
+                public void cancel() {
+                    rectFSpringAnim.cancel();
+                }
+            };
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index c43155b..8c5a788 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -80,7 +80,9 @@
 
     @Override
     public void onAssistantVisibilityChanged(float visibility) {
-        // TODO:
+        // This class becomes active when the screen is locked.
+        // Rather than having it handle assistant visibility changes, the assistant visibility is
+        // set to zero prior to this class becoming active.
     }
 
     @NonNull
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index b2a71a4..54a366d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -40,7 +39,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -54,16 +52,15 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListenerEx;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -151,16 +148,10 @@
             @NonNull
             @Override
             public RectF getWindowTargetRect() {
-                final int halfIconSize = dp.iconSizePx / 2;
-                final float targetCenterX = dp.availableWidthPx / 2f;
-                final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
-
                 if (canUseWorkspaceView) {
                     return iconLocation;
                 } else {
-                    // Fallback to animate to center of screen.
-                    return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
-                            targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+                    return HomeAnimationFactory.getDefaultWindowTargetRect(dp);
                 }
             }
 
@@ -175,18 +166,8 @@
 
             @Override
             public void playAtomicAnimation(float velocity) {
-                // Setup workspace with 0 duration to prepare for our staggered animation.
-                LauncherStateManager stateManager = activity.getStateManager();
-                AnimatorSetBuilder builder = new AnimatorSetBuilder();
-                // setRecentsAttachedToAppWindow() will animate recents out.
-                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
-                stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
-                builder.build().start();
-
-                // Stop scrolling so that it doesn't interfere with the translation offscreen.
-                recentsView.getScroller().forceFinished(true);
-
-                new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
+                new StaggeredWorkspaceAnim(activity, velocity, true /* animateOverviewScrim */)
+                        .start();
             }
         };
     }
@@ -194,9 +175,6 @@
     @Override
     public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "prepareRecentsUI");
-        }
         final LauncherState startState = activity.getStateManager().getState();
 
         LauncherState resetState = startState;
@@ -211,11 +189,10 @@
         // 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 final ShelfPeekAnim mShelfAnim =
+                    ((QuickstepAppTransitionManagerImpl) activity.getAppTransitionManager())
+                            .getShelfPeekAnim();
             private boolean mIsAttachedToWindow;
 
             @Override
@@ -244,30 +221,7 @@
             @Override
             public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
                     long duration) {
-                if (mShelfState == shelfState) {
-                    return;
-                }
-                mShelfState = shelfState;
-                activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
-                if (mShelfState == ShelfAnimState.CANCEL) {
-                    return;
-                }
-                float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
-                float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
-                // Peek based on default overview progress so we can see hotseat if we're showing
-                // that instead of predictions in overview.
-                float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity);
-                float shelfPeekingProgress = shelfHiddenProgress
-                        - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
-                float toProgress = mShelfState == ShelfAnimState.HIDE
-                        ? shelfHiddenProgress
-                        : mShelfState == ShelfAnimState.PEEK
-                                ? shelfPeekingProgress
-                                : shelfOverviewProgress;
-                Animator shelfAnim = activity.getStateManager()
-                        .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
-                shelfAnim.setInterpolator(interpolator);
-                shelfAnim.setDuration(duration).start();
+                mShelfAnim.setShelfState(shelfState, interpolator, duration);
             }
 
             @Override
@@ -282,22 +236,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;
@@ -365,8 +309,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;
         }
 
@@ -394,7 +337,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();
@@ -493,4 +440,4 @@
     public void onLaunchTaskSuccess(Launcher launcher) {
         launcher.getStateManager().moveToRestState();
     }
-}
\ 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 6533c63..a8d402e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep;
 
-import static com.android.systemui.shared.system.ActivityManagerWrapper
-        .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -25,11 +25,12 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.MainThreadExecutor;
 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.views.RecentsView;
@@ -47,7 +48,6 @@
     private final Context mContext;
     private final ActivityManagerWrapper mAM;
     private final RecentsModel mRecentsModel;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -55,7 +55,6 @@
     public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
         mContext = context;
         mAM = ActivityManagerWrapper.getInstance();
-        mMainThreadExecutor = new MainThreadExecutor();
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
@@ -67,19 +66,19 @@
         }
 
         mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mMainThreadExecutor.execute(new RecentsActivityCommand<>());
+        MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
     public void onOverviewShown(boolean triggeredFromAltTab) {
-        mMainThreadExecutor.execute(new ShowRecentsCommand(triggeredFromAltTab));
+        MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
     }
 
     public void onOverviewHidden() {
-        mMainThreadExecutor.execute(new HideRecentsCommand());
+        MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
     public void onTip(int actionType, int viewType) {
-        mMainThreadExecutor.execute(() ->
+        MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
     }
 
@@ -99,6 +98,7 @@
 
         @Override
         protected void onTransitionComplete() {
+            // TODO(b/138729100) This doesn't execute first time launcher is run
             if (mTriggeredFromAltTab) {
                 RecentsView rv = (RecentsView) mHelper.getVisibleRecentsView();
                 if (rv == null) {
@@ -109,7 +109,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();
@@ -177,7 +177,7 @@
             // Otherwise, start overview.
             mListener = mHelper.createActivityInitListener(this::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(),
+                    this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
                     mAnimationProvider.getRecentsLaunchDuration());
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
new file mode 100644
index 0000000..3c78dd8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -0,0 +1,82 @@
+package com.android.quickstep;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.launcher3.testing.TestInformationHandler;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.RecentsView;
+
+import java.util.concurrent.ExecutionException;
+
+public class QuickstepTestInformationHandler extends TestInformationHandler {
+
+    public QuickstepTestInformationHandler(Context context) {
+    }
+
+    @Override
+    public Bundle call(String method) {
+        final Bundle response = new Bundle();
+        switch (method) {
+            case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
+                final float swipeHeight =
+                        LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
+                final float swipeHeight =
+                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
+                return response;
+            }
+
+            case TestProtocol.REQUEST_HOTSEAT_TOP: {
+                if (mLauncher == null) return null;
+
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        PortraitStatesTouchController.getHotseatTop(mLauncher));
+                return response;
+            }
+
+            case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
+                try {
+                    final int leftMargin = MAIN_EXECUTOR.submit(() ->
+                            mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get();
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
+                } catch (ExecutionException e) {
+                    e.printStackTrace();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                return response;
+            }
+
+            case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
+                try {
+                    final int rightMargin = MAIN_EXECUTOR.submit(() ->
+                            mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()).
+                            get();
+                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
+                } catch (ExecutionException e) {
+                    e.printStackTrace();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                return response;
+            }
+        }
+
+        return super.call(method);
+    }
+
+    @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 fc29a56..9bdc98b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -28,8 +28,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.view.View;
 
@@ -42,7 +44,9 @@
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ObjectWrapper;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -54,6 +58,9 @@
  */
 public final class RecentsActivity extends BaseRecentsActivity {
 
+    public static final String EXTRA_THUMBNAIL = "thumbnailData";
+    public static final String EXTRA_TASK_ID = "taskID";
+
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
@@ -79,6 +86,22 @@
     }
 
     @Override
+    protected void onNewIntent(Intent intent) {
+        if (intent.getExtras() != null) {
+            int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
+            IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
+            if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
+                ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
+                mFallbackRecentsView.showCurrentTask(taskID);
+                mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
+            }
+        }
+        intent.removeExtra(EXTRA_TASK_ID);
+        intent.removeExtra(EXTRA_THUMBNAIL);
+        super.onNewIntent(intent);
+    }
+
+    @Override
     protected void onHandleConfigChanged() {
         super.onHandleConfigChanged();
         mRecentsRootView.setup();
@@ -113,6 +136,12 @@
     }
 
     @Override
+    public void returnToHomescreen() {
+        super.returnToHomescreen();
+        // TODO(b/137318995) This should go home, but doing so removes freeform windows
+    }
+
+    @Override
     public ActivityOptions getActivityLaunchOptions(final View v) {
         if (!(v instanceof TaskView)) {
             return null;
@@ -132,7 +161,7 @@
                         mFallbackRecentsView.resetViewUI();
                     }
                 });
-                result.setAnimation(anim);
+                result.setAnimation(anim, RecentsActivity.this);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
@@ -178,6 +207,12 @@
         mFallbackRecentsView.resetTaskVisuals();
     }
 
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mFallbackRecentsView.reset();
+    }
+
     public void onTaskLaunched() {
         mFallbackRecentsView.resetTaskVisuals();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index ddd28a3..c4d3fa0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -19,12 +19,14 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.Utilities.FLAG_NO_GESTURES;
-
+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.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
@@ -33,13 +35,13 @@
 import java.util.ArrayList;
 import java.util.function.Supplier;
 
-import androidx.annotation.UiThread;
-
 /**
  * Wrapper around RecentsAnimationController to help with some synchronization
  */
 public class RecentsAnimationWrapper {
 
+    private static final String TAG = "RecentsAnimationWrapper";
+
     // A list of callbacks to run when we receive the recents animation target. There are different
     // than the state callbacks as these run on the current worker thread.
     private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
@@ -127,6 +129,7 @@
             boolean sendUserLeaveHint) {
         SwipeAnimationTargetSet controller = targetSet;
         targetSet = null;
+        disableInputProxy();
         if (controller != null) {
             controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
         }
@@ -155,6 +158,16 @@
         mInputConsumerController.setInputListener(this::onInputConsumerEvent);
     }
 
+    private void disableInputProxy() {
+        if (mInputConsumer != null && mTouchInProgress) {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
+            mInputConsumer.onMotionEvent(dummyCancel);
+            dummyCancel.recycle();
+        }
+        mInputConsumerController.setInputListener(null);
+    }
+
     private boolean onInputConsumerEvent(InputEvent ev) {
         if (ev instanceof MotionEvent) {
             onInputConsumerMotionEvent((MotionEvent) ev);
@@ -170,6 +183,18 @@
 
     private boolean onInputConsumerMotionEvent(MotionEvent ev) {
         int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
             if (mInputConsumer == null) {
@@ -184,18 +209,15 @@
             }
         }
         if (mInputConsumer != null) {
-            int flags = ev.getEdgeFlags();
-            ev.setEdgeFlags(flags | FLAG_NO_GESTURES);
             mInputConsumer.onMotionEvent(ev);
-            ev.setEdgeFlags(flags);
         }
 
         return true;
     }
 
-    public void setCancelWithDeferredScreenshot(boolean deferredWithScreenshot) {
+    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
         if (targetSet != null) {
-            targetSet.controller.setCancelWithDeferredScreenshot(deferredWithScreenshot);
+            targetSet.controller.setDeferCancelUntilNextTransition(defer, screenshot);
         }
     }
 
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 c55f656..8783ee3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.util.Log;
 
@@ -25,6 +25,7 @@
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+
 import java.io.PrintWriter;
 
 /**
@@ -44,6 +45,7 @@
     public boolean goingToLauncher;
     public boolean recentsAnimationFinishInterrupted;
     public int nextRunningTaskId = -1;
+    private int mLogId;
 
     public void setOverviewComponentObserver(OverviewComponentObserver observer) {
         mOverviewComponentObserver = observer;
@@ -77,7 +79,7 @@
             mRecentsAnimationListener.removeListener(this);
             mRecentsAnimationListener.cancelListener();
             if (mLastAnimationRunning && mLastAnimationTarget != null) {
-                Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(),
+                Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                         finishAnimation
                                 ? mLastAnimationTarget::finishAnimation
                                 : mLastAnimationTarget::cancelAnimation);
@@ -155,5 +157,10 @@
         pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId);
         pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled);
         pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning);
+        pw.println(prefix + "logTraceId=" + mLogId);
+    }
+
+    public void setLogTraceId(int logId) {
+        this.mLogId = logId;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
index b90f6c2..17457aa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
 import android.graphics.Matrix;
 import android.view.View;
 
@@ -47,8 +49,7 @@
     };
 
     public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
-            new MainThreadInitializedObject<>(c -> Overrides.getObject(TaskOverlayFactory.class,
-                    c, R.string.task_overlay_factory_class));
+            forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
 
     public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
         final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
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 213c5d3..1af0db0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -36,8 +36,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
@@ -207,8 +205,7 @@
                         }
                     };
                     WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
-                            future, animStartedListener, mHandler, true /* scaleUp */,
-                            v.getDisplay().getDisplayId());
+                            future, animStartedListener, mHandler, true /* scaleUp */, displayId);
                 }
             });
         }
@@ -268,12 +265,16 @@
 
         @Override
         protected ActivityOptions makeLaunchOptions(Activity activity) {
-            return ActivityOptionsCompat.makeFreeformOptions();
+            ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
+            // Arbitrary bounds only because freeform is in dev mode right now
+            Rect r = new Rect(50, 50, 200, 200);
+            activityOptions.setLaunchBounds(r);
+            return activityOptions;
         }
 
         @Override
         protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            Launcher.getLauncher(activity).getStateManager().goToState(LauncherState.NORMAL);
+            activity.returnToHomescreen();
             return true;
         }
     }
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 53da0f9..2fa789f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,8 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+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.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;
@@ -31,8 +33,10 @@
 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;
 import android.app.ActivityManager;
@@ -43,11 +47,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+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;
@@ -57,25 +60,26 @@
 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.MainThreadExecutor;
 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.config.BaseFlags;
 import com.android.launcher3.logging.EventLogArray;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
+import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -88,6 +92,7 @@
 import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
 import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
+import com.android.quickstep.util.AssistantUtilities;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -96,6 +101,7 @@
 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 java.io.FileDescriptor;
@@ -119,10 +125,6 @@
     public String nextArg() {
         return pollFirst().toLowerCase();
     }
-
-    public String nextArgExact() {
-        return pollFirst();
-    }
 }
 
 /**
@@ -130,17 +132,25 @@
  */
 @TargetApi(Build.VERSION_CODES.Q)
 public class TouchInteractionService extends Service implements
-        NavigationModeChangeListener, DisplayListener {
+        NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener {
 
-    public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
-    public static final LooperExecutor BACKGROUND_EXECUTOR =
-            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+    /**
+     * 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);
 
     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 int MAX_BACK_NOTIFICATION_COUNT = 3;
+    private int mBackGestureNotificationCounter = -1;
+
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
         public void onActiveNavBarRegionChanges(Region region) {
@@ -150,8 +160,10 @@
         public void onInitialize(Bundle bundle) {
             mISystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
+            MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
+            sIsInitialized = true;
         }
 
         @Override
@@ -185,7 +197,7 @@
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
             mLastAssistantVisibility = visibility;
-            MAIN_THREAD_EXECUTOR.execute(
+            MAIN_EXECUTOR.execute(
                     TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
@@ -199,11 +211,15 @@
                     mOverviewComponentObserver.getActivityControlHelper();
             UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
                     isButton, gestureSwipeLeft, activityControl.getContainerType());
+
+            if (completed && !isButton && shouldNotifyBackGesture()) {
+                UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
+            }
         }
 
         public void onSystemUiStateChanged(int stateFlags) {
             mSystemUiStateFlags = stateFlags;
-            MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
+            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
         }
 
         /** Deprecated methods **/
@@ -225,12 +241,18 @@
     };
 
     private static boolean sConnected = false;
+    private static boolean sIsInitialized = false;
     private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
+    private int mLogId;
 
     public static boolean isConnected() {
         return sConnected;
     }
 
+    public static boolean isInitialized() {
+        return sIsInitialized;
+    }
+
     public static SwipeSharedState getSwipeSharedState() {
         return sSwipeSharedState;
     }
@@ -238,6 +260,11 @@
     private final InputConsumer mResetGestureInputConsumer =
             new ResetGestureInputConsumer(sSwipeSharedState);
 
+    private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
+            this::createWindowTransformSwipeHandler;
+    private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
+            this::createFallbackNoButtonSwipeHandler;
+
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
     private ISystemUiProxy mISystemUiProxy;
@@ -296,8 +323,7 @@
             registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
         }
 
-        mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay()
-                .getDisplayId();
+        mDefaultDisplayId = DefaultDisplay.INSTANCE.get(this).getInfo().id;
         String blockingActivity = getString(R.string.gesture_blocking_activity);
         mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null :
                 ComponentName.unflattenFromString(blockingActivity);
@@ -319,6 +345,9 @@
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
             mInputEventReceiver = null;
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "disposeEventHandlers");
+            }
         }
         if (mInputMonitorCompat != null) {
             mInputMonitorCompat.dispose();
@@ -327,16 +356,25 @@
     }
 
     private void initInputMonitor() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
+        }
         if (!mMode.hasGestures || mISystemUiProxy == null) {
             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);
         }
@@ -352,9 +390,8 @@
             return;
         }
 
-        Display defaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
-        Point realSize = new Point();
-        defaultDisplay.getRealSize(realSize);
+        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(this).getInfo();
+        Point realSize = new Point(displayInfo.realSize);
         mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y);
         if (mMode == Mode.NO_BUTTON) {
             int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
@@ -376,7 +413,7 @@
         } else {
             mAssistantLeftRegion.setEmpty();
             mAssistantRightRegion.setEmpty();
-            switch (defaultDisplay.getRotation()) {
+            switch (displayInfo.rotation) {
                 case Surface.ROTATION_90:
                     mSwipeTouchRegion.left = mSwipeTouchRegion.right
                             - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
@@ -394,12 +431,14 @@
 
     @Override
     public void onNavigationModeChanged(Mode newMode) {
+        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_THREAD_EXECUTOR.getHandler());
+                DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
             } else {
-                getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+                DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
             }
         }
         mMode = newMode;
@@ -415,14 +454,8 @@
     }
 
     @Override
-    public void onDisplayAdded(int i) { }
-
-    @Override
-    public void onDisplayRemoved(int i) { }
-
-    @Override
-    public void onDisplayChanged(int displayId) {
-        if (displayId != mDefaultDisplayId) {
+    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+        if (info.id != mDefaultDisplayId) {
             return;
         }
 
@@ -448,6 +481,8 @@
 
         // Temporarily disable model preload
         // new ModelPreload().start(this);
+        mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
+                .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
 
         Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
     }
@@ -478,13 +513,14 @@
 
     @Override
     public void onDestroy() {
+        sIsInitialized = false;
         if (mIsUserUnlocked) {
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.onDestroy();
         }
         disposeEventHandlers();
         if (mMode.hasGestures) {
-            getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+            DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
         }
 
         sConnected = false;
@@ -502,13 +538,19 @@
     }
 
     private void onInputEvent(InputEvent ev) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "onInputEvent " + ev);
+        }
         if (!(ev instanceof MotionEvent)) {
             Log.e(TAG, "Unknown event " + ev);
             return;
         }
+
         MotionEvent event = (MotionEvent) ev;
-        TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
         if (event.getAction() == ACTION_DOWN) {
+            mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId();
+            sSwipeSharedState.setLogTraceId(mLogId);
+
             if (mSwipeTouchRegion.contains(event.getX(), event.getY())) {
                 boolean useSharedState = mConsumer.useSharedSwipeState();
                 mConsumer.onConsumerAboutToBeSwitched();
@@ -521,19 +563,26 @@
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
                 mUncheckedConsumer =
-                        new AssistantTouchConsumer(this, mISystemUiProxy,
+                        new AssistantTouchConsumer(
+                                this,
+                                mISystemUiProxy,
                                 mOverviewComponentObserver.getActivityControlHelper(),
-                                InputConsumer.NO_OP, mInputMonitorCompat);
+                                InputConsumer.NO_OP,
+                                mInputMonitorCompat,
+                                mOverviewComponentObserver.assistantGestureIsConstrained());
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
         }
+
+        TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked());
         mUncheckedConsumer.onMotionEvent(event);
     }
 
     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);
     }
@@ -553,7 +602,7 @@
             if (isInValidSystemUiState) {
                 // 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(0));
+                return createDeviceLockedInputConsumer(mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
             } else {
                 return mResetGestureInputConsumer;
             }
@@ -567,8 +616,13 @@
             final ActivityControlHelper activityControl =
                     mOverviewComponentObserver.getActivityControlHelper();
             if (canTriggerAssistantAction(event)) {
-                base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
-                        mInputMonitorCompat);
+                base = new AssistantTouchConsumer(
+                        this,
+                        mISystemUiProxy,
+                        activityControl,
+                        base,
+                        mInputMonitorCompat,
+                        mOverviewComponentObserver.assistantGestureIsConstrained());
             }
 
             if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) {
@@ -591,7 +645,7 @@
     }
 
     private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
-        final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+        RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
             sSwipeSharedState.clearAllState(false /* finishAnimation */);
         }
@@ -603,6 +657,17 @@
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
 
+        boolean forceOverviewInputConsumer = false;
+        if (AssistantUtilities.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 /* ignoreActivityType */);
+            ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
+            ComponentName runningComponent = runningTaskInfo.baseIntent.getComponent();
+            forceOverviewInputConsumer =
+                runningComponent != null && runningComponent.equals(homeComponent);
+        }
+
         if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
                 && !sSwipeSharedState.recentsAnimationFinishInterrupted) {
             return mResetGestureInputConsumer;
@@ -612,17 +677,14 @@
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
             info.id = sSwipeSharedState.nextRunningTaskId;
             return createOtherActivityInputConsumer(event, info);
-        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
-            return createOverviewInputConsumer(event);
+        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
+                || forceOverviewInputConsumer) {
+            return createOverviewInputConsumer(event, forceOverviewInputConsumer);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
-            return createOverviewInputConsumer(event);
+            return createOverviewInputConsumer(event, forceOverviewInputConsumer);
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return mResetGestureInputConsumer;
-        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            return new FallbackNoButtonInputConsumer(this, activityControl,
-                    mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
-                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
@@ -635,29 +697,38 @@
                 && exclusionRegion.contains((int) event.getX(), (int) event.getY());
     }
 
-    private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
+    private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
             RunningTaskInfo runningTaskInfo) {
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
 
-        return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
-                mOverviewComponentObserver.getOverviewIntent(), activityControl,
-                shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
+        final boolean shouldDefer;
+        final BaseSwipeUpHandler.Factory factory;
+
+        if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
+            factory = mFallbackNoButtonFactory;
+        } else {
+            shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
+                    .deferStartingActivity(mActiveNavBarRegion, event);
+            factory = mWindowTreansformFactory;
+        }
+
+        return new OtherActivityInputConsumer(this, runningTaskInfo,
+                shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
                 sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
-                disableHorizontalSwipe(event));
+                disableHorizontalSwipe(event), factory, mLogId);
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
             return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat,
-                    mSwipeTouchRegion, taskInfo.taskId);
+                    mSwipeTouchRegion, taskInfo.taskId, mLogId);
         } else {
             return mResetGestureInputConsumer;
         }
     }
 
-    public InputConsumer createOverviewInputConsumer(MotionEvent event) {
+    public InputConsumer createOverviewInputConsumer(MotionEvent event,
+            boolean forceOverviewInputConsumer) {
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
         BaseDraggingActivity activity = activityControl.getCreatedActivity();
@@ -665,11 +736,14 @@
             return mResetGestureInputConsumer;
         }
 
-        if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
+        if (activity.getRootView().hasWindowFocus()
+                || sSwipeSharedState.goingToLauncher
+                || (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+                    && forceOverviewInputConsumer)) {
             return new OverviewInputConsumer(activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
-            return new OverviewWithoutFocusInputConsumer(this, mInputMonitorCompat,
+            return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
                     disableHorizontalSwipe(event));
         }
     }
@@ -684,6 +758,61 @@
         }
     }
 
+    private void preloadOverview(boolean fromInit) {
+        if (!mIsUserUnlocked) {
+            return;
+        }
+        if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            // Prevent the overview from being started before the real home on first boot.
+            return;
+        }
+
+        if (RestoreDbTask.isPending(this)) {
+            // Preloading while a restore is pending may cause launcher to start the restore
+            // too early.
+            return;
+        }
+
+        final ActivityControlHelper<BaseDraggingActivity> activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        if (activityControl.getCreatedActivity() == null) {
+            // Make sure that UI states will be initialized.
+            activityControl.createActivityInitListener((activity, wasVisible) -> {
+                AppLaunchTracker.INSTANCE.get(activity);
+                return false;
+            }).register();
+        } else if (fromInit) {
+            // The activity has been created before the initialization of overview service. It is
+            // usually happens when booting or launcher is the top activity, so we should already
+            // have the latest state.
+            return;
+        }
+
+        // Pass null animation handler to indicate this start is preload.
+        startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
+                null);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (!mIsUserUnlocked) {
+            return;
+        }
+        final ActivityControlHelper activityControl =
+                mOverviewComponentObserver.getActivityControlHelper();
+        final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+        if (activity == null || activity.isStarted()) {
+            // We only care about the existing background activity.
+            return;
+        }
+        if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
+                activity.getResources().getConfiguration().diff(newConfig))) {
+            return;
+        }
+
+        preloadOverview(false /* fromInit */);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
         if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
@@ -708,8 +837,9 @@
             pw.println("  assistantAvailable=" + mAssistantAvailable);
             pw.println("  assistantDisabled="
                     + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
-            pw.println("  resumed="
-                    + mOverviewComponentObserver.getActivityControlHelper().isResumed());
+            boolean resumed = mOverviewComponentObserver != null
+                    && mOverviewComponentObserver.getActivityControlHelper().isResumed();
+            pw.println("  resumed=" + resumed);
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
                 sSwipeSharedState.dump("    ", pw);
@@ -739,4 +869,37 @@
                 break;
         }
     }
+
+    private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
+            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
+        return  new WindowTransformSwipeHandler(runningTask, this, 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);
+    }
+
+    protected boolean shouldNotifyBackGesture() {
+        return mBackGestureNotificationCounter > 0 &&
+                mGestureBlockingActivity != null;
+    }
+
+    @WorkerThread
+    protected void tryNotifyBackGesture() {
+        if (shouldNotifyBackGesture()) {
+            mBackGestureNotificationCounter--;
+            Utilities.getDevicePrefs(this).edit()
+                    .putInt(KEY_BACK_NOTIFICATION_COUNT, mBackGestureNotificationCounter).apply();
+            sendBroadcast(new Intent(NOTIFY_ACTION_BACK).setPackage(
+                    mGestureBlockingActivity.getPackageName()));
+        }
+    }
+
+    public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
+        UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+                .startRecentsActivity(intent, null, listener, null, null));
+    }
 }
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 f790c40..2fa4feb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -17,91 +17,72 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
-import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 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.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
 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.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 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;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Canvas;
-import android.graphics.Point;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemClock;
-import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.WindowInsets;
-import android.view.WindowManager;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.testing.TestProtocol;
 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.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.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;
+import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -109,19 +90,14 @@
 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.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
 
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
-        implements SwipeAnimationListener, OnApplyWindowInsetsListener {
+        extends BaseSwipeUpHandler<T, RecentsView>
+        implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
-    private static final Rect TEMP_RECT = new Rect();
-
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
     private static int getFlagForIndex(int index, String name) {
@@ -217,67 +193,31 @@
             Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
     private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
 
-    private static final long SHELF_ANIM_DURATION = 240;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
-    // Start resisting when swiping past this factor of mTransitionDragLength.
-    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
-    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
-    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private final ClipAnimationHelper mClipAnimationHelper;
-    private final ClipAnimationHelper.TransformParams mTransformParams;
-
-    private Runnable mGestureEndCallback;
     private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
-    private DeviceProfile mDp;
-    // The distance needed to drag to reach the task size in recents.
-    private int mTransitionDragLength;
-    // How much further we can drag past recents, as a factor of mTransitionDragLength.
-    private float mDragLengthFactor = 1;
 
-    // Shift in the range of [0, 1].
-    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
-    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
-    // visible.
-    private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
     private boolean mContinuingLastGesture;
-    // To avoid UI jump when gesture is started, we offset the animation by the threshold.
-    private float mShiftAtGestureStart = 0;
 
-    private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
-
-    private final Context mContext;
-    private final ActivityControlHelper<T> mActivityControlHelper;
-    private final ActivityInitListener mActivityInitListener;
-
-    private final SysUINavigationMode.Mode mMode;
-
-    private final int mRunningTaskId;
     private ThumbnailData mTaskSnapshot;
 
-    private MultiStateCallback mStateCallback;
     // Used to control launcher components throughout the swipe gesture.
     private AnimatorPlaybackController mLauncherTransitionController;
     private boolean mHasLauncherTransitionControllerStarted;
 
-    private T mActivity;
-    private RecentsView mRecentsView;
     private AnimationFactory mAnimationFactory = (t) -> { };
     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
+    private boolean mLiveTileOverlayAttached = false;
 
-    private boolean mCanceled;
     private boolean mWasLauncherAlreadyVisible;
-    private int mFinishingRecentsAnimationForNewTaskId = -1;
 
     private boolean mPassedOverviewThreshold;
     private boolean mGestureStarted;
@@ -286,31 +226,17 @@
     private PointF mDownPos;
     private boolean mIsLikelyToStartNewTask;
 
-    private final RecentsAnimationWrapper mRecentsAnimationWrapper;
-
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
     public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
-            long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
-            InputConsumerController inputConsumer) {
-        mContext = context;
-        mRunningTaskId = runningTaskInfo.id;
+            long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
+            boolean continuingLastGesture,
+            InputConsumerController inputConsumer, RecentsModel recentsModel) {
+        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
         mTouchTimeMs = touchTimeMs;
-        mActivityControlHelper = controller;
-        mActivityInitListener = mActivityControlHelper
-                .createActivityInitListener(this::onActivityInit);
         mContinuingLastGesture = continuingLastGesture;
-        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
-                this::createNewInputProxyHandler);
-        mClipAnimationHelper = new ClipAnimationHelper(context);
-        mTransformParams = new ClipAnimationHelper.TransformParams();
-
-        mMode = SysUINavigationMode.getMode(context);
         initStateCallbacks();
-
-        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
-        initTransitionEndpoints(dp);
     }
 
     private void initStateCallbacks() {
@@ -373,44 +299,8 @@
         }
     }
 
-    private void setStateOnUiThread(int stateFlag) {
-        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
-            mStateCallback.setState(stateFlag);
-        } else {
-            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
-        }
-    }
-
-    private void initTransitionEndpoints(DeviceProfile dp) {
-        mDp = dp;
-
-        Rect tempRect = new Rect();
-        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
-                dp, mContext, tempRect);
-        mClipAnimationHelper.updateTargetRect(tempRect);
-        if (mMode == Mode.NO_BUTTON) {
-            // We can drag all the way to the top of the screen.
-            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-        }
-    }
-
-    private long getFadeInDuration() {
-        if (mCurrentShift.getCurrentAnimation() != null) {
-            ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
-            long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
-
-            // TODO: Find a better heuristic
-            return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION));
-        } else {
-            return MAX_SWIPE_DURATION;
-        }
-    }
-
-    public void initWhenReady() {
-        mActivityInitListener.register();
-    }
-
-    private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+    @Override
+    protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
         if (mActivity == activity) {
             return true;
         }
@@ -431,21 +321,8 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
-        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
-            mTransformParams.setSyncTransactionApplier(applier);
-            mRecentsAnimationWrapper.runOnInit(() ->
-                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
-            });
-
-        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (mGestureEndTarget != HOME) {
-                updateFinalShift();
-            }
-        });
-        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
-        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
-        mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
-        mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
+        linkRecentsViewScroll();
+        addLiveTileOverlay();
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
@@ -458,33 +335,23 @@
         return true;
     }
 
+    @Override
+    protected boolean moveWindowWithRecentsScroll() {
+        return mGestureEndTarget != HOME;
+    }
+
     private void onLauncherStart(final T activity) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
-        }
         if (mActivity != activity) {
             return;
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 1");
-        }
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return;
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 2");
-        }
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
         if (mGestureEndTarget != HOME) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 3");
-            }
             Runnable initAnimFactory = () -> {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 4");
-                }
                 mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
@@ -494,14 +361,8 @@
                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
                 // wait until the next gesture (and possibly launcher) starts.
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 5");
-                }
                 mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
             } else {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart 6");
-                }
                 initAnimFactory.run();
             }
         }
@@ -577,32 +438,9 @@
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
-    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
-    }
-
-    @UiThread
-    public void updateDisplacement(float displacement) {
-        // We are moving in the negative x/y direction
-        displacement = -displacement;
-        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
-            mCurrentShift.updateValue(mDragLengthFactor);
-        } else {
-            float translation = Math.max(displacement, 0);
-            float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
-            if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
-                float pullbackProgress = Utilities.getProgress(shift,
-                        DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
-                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
-                shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
-                        * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
-            }
-            mCurrentShift.updateValue(shift);
-        }
-    }
-
+    @Override
     public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
+        setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -639,8 +477,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))
@@ -651,6 +489,7 @@
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
     }
 
+    @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
@@ -666,9 +505,8 @@
         if (mIsShelfPeeking != wasShelfPeeking) {
             maybeUpdateRecentsAttachedState();
         }
-        if (mRecentsView != null && shelfState.shouldPreformHaptic) {
-            mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        if (shelfState.shouldPreformHaptic) {
+            performHapticFeedback();
         }
     }
 
@@ -697,19 +535,18 @@
         updateLauncherTransitionProgress();
     }
 
-    @UiThread
-    private void updateFinalShift() {
-        float shift = mCurrentShift.value;
+    @Override
+    public Intent getLaunchIntent() {
+        return mOverviewComponentObserver.getOverviewIntent();
+    }
+
+    @Override
+    public void updateFinalShift() {
 
         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
-            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);
-            updateSysUiFlags(shift);
+            applyTransformUnchecked();
+            updateSysUiFlags(mCurrentShift.value);
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -722,9 +559,8 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (mRecentsView != null && mMode != Mode.NO_BUTTON) {
-                mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            if (mMode != Mode.NO_BUTTON) {
+                performHapticFeedback();
             }
         }
 
@@ -742,9 +578,7 @@
         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
         // anyway. The controller mimics the drag length factor by applying it to its interpolators.
         float progress = mCurrentShift.value / mDragLengthFactor;
-        mLauncherTransitionController.setPlayFraction(
-                progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1
-                        ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart));
+        mLauncherTransitionController.setPlayFraction(progress);
     }
 
     /**
@@ -752,8 +586,7 @@
      */
     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;
@@ -767,39 +600,7 @@
 
     @Override
     public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
-        final Rect overviewStackBounds;
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
-
-        if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
-            overviewStackBounds = mActivityControlHelper
-                    .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
-            dp = dp.getMultiWindowProfile(mContext, new Point(
-                    targetSet.minimizedHomeBounds.width(), targetSet.minimizedHomeBounds.height()));
-            dp.updateInsets(targetSet.homeContentInsets);
-        } else {
-            if (mActivity != null) {
-                int loc[] = new int[2];
-                View rootView = mActivity.getRootView();
-                rootView.getLocationOnScreen(loc);
-                overviewStackBounds = new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
-                        loc[1] + rootView.getHeight());
-            } else {
-                overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
-            }
-            // If we are not in multi-window mode, home insets should be same as system insets.
-            dp = dp.copy(mContext);
-            dp.updateInsets(targetSet.homeContentInsets);
-        }
-        dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
-
-        if (runningTaskTarget != null) {
-            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
-        }
-        mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
-        initTransitionEndpoints(dp);
-
-        mRecentsAnimationWrapper.setController(targetSet);
+        super.onRecentsAnimationStart(targetSet);
         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimationCallback", targetSet.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
@@ -814,10 +615,9 @@
         TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
     }
 
-    @UiThread
+    @Override
     public void onGestureStarted() {
         notifyGestureStartedAsync();
-        mShiftAtGestureStart = mCurrentShift.value;
         setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
     }
@@ -838,7 +638,7 @@
     /**
      * Called as a result on ACTION_CANCEL to return the UI to the start state.
      */
-    @UiThread
+    @Override
     public void onGestureCancelled() {
         updateDisplacement(0);
         setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -851,7 +651,7 @@
      * @param velocity The x and y components of the velocity when the gesture ends.
      * @param downPos The x and y value of where the gesture started.
      */
-    @UiThread
+    @Override
     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -869,9 +669,9 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    @UiThread
-    private InputConsumer createNewInputProxyHandler() {
-        endRunningWindowAnim();
+    @Override
+    protected InputConsumer createNewInputProxyHandler() {
+        endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
         endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // Hide the task view, if not already hidden
@@ -883,9 +683,13 @@
                 ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
     }
 
-    private void endRunningWindowAnim() {
+    private void endRunningWindowAnim(boolean cancel) {
         if (mRunningWindowAnim != null) {
-            mRunningWindowAnim.end();
+            if (cancel) {
+                mRunningWindowAnim.cancel();
+            } else {
+                mRunningWindowAnim.end();
+            }
         }
     }
 
@@ -971,14 +775,14 @@
             interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
         } else {
             startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
-                    * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor);
+                    * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
             float minFlingVelocity = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_min_velocity);
             if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
                 if (endTarget == RECENTS && mMode != Mode.NO_BUTTON) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, velocityPxPerMs.y,
-                            mTransitionDragLength);
+                            startShift, endShift, endShift, endVelocity / 1000,
+                            mTransitionDragLength, mContext);
                     endShift = overshoot.end;
                     interpolator = overshoot.interpolator;
                     duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION,
@@ -1113,7 +917,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();
@@ -1152,65 +964,22 @@
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
      * @param homeAnimationFactory The home animation factory.
      */
-    private RectFSpringAnim createWindowAnimationToHome(float startProgress,
+    @Override
+    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 targetRect = homeAnimationFactory.getWindowTargetRect();
-
-        final View floatingView = homeAnimationFactory.getFloatingView();
-        final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
-        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mActivity.getResources());
-        if (isFloatingIconView) {
-            FloatingIconView fiv = (FloatingIconView) floatingView;
-            anim.addAnimatorListener(fiv);
-            fiv.setOnTargetChangeListener(anim::onTargetPositionChanged);
-        }
-
-        AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome();
-
-        // 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 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.
-        final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
-        anim.addOnUpdateListener((currentRect, progress) -> {
-            homeAnim.setPlayFraction(progress);
-
-            float alphaProgress = ACCEL_1_5.getInterpolation(progress);
-            float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0,
-                    windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1);
-            mTransformParams.setProgress(progress)
-                    .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
-            if (isFloatingIconView) {
-                mTransformParams.setCornerRadius(endRadius * progress + startRadius
-                        * (1f - progress));
-            }
-            mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
-                    false /* launcherOnTop */);
-
-            if (isFloatingIconView) {
-                ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
-                        windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
-            }
-
-            updateSysUiFlags(Math.max(progress, mCurrentShift.value));
-        });
+        RectFSpringAnim anim =
+                super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
+        anim.addOnUpdateListener((r, p) -> updateSysUiFlags(Math.max(p, mCurrentShift.value)));
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                homeAnim.dispatchOnStart();
                 if (mActivity != null) {
-                    mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+                    removeLiveTileOverlay();
                 }
             }
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                homeAnim.getAnimationPlayer().end();
                 if (mRecentsView != null) {
                     mRecentsView.post(mRecentsView::resetTaskVisuals);
                 }
@@ -1222,11 +991,18 @@
         return anim;
     }
 
-    /**
-     * @return The GestureEndTarget if the gesture has ended, else null.
-     */
-    public @Nullable GestureEndTarget getGestureEndTarget() {
-        return mGestureEndTarget;
+    @Override
+    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+        if (mGestureEndTarget != null) {
+            sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
+            sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
+        }
+
+        if (sharedState.canGestureBeContinued) {
+            cancelCurrentAnimation(sharedState);
+        } else {
+            reset();
+        }
     }
 
     @UiThread
@@ -1239,43 +1015,18 @@
 
     @UiThread
     private void startNewTask() {
-        // 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 */,
-                    true /* freezeTaskList */);
-        } else {
-            int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
-            mFinishingRecentsAnimationForNewTaskId = taskId;
-            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
-                if (!mCanceled) {
-                    TaskView nextTask = mRecentsView.getTaskView(taskId);
-                    if (nextTask != null) {
-                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                                success -> {
-                            if (!success) {
-                                // We couldn't launch the task, so take user to overview so they can
-                                // decide what to do instead of staying in this broken state.
-                                endLauncherTransitionController();
-                                mActivityControlHelper.onLaunchTaskFailed(mActivity);
-                                nextTask.notifyTaskLaunchFailed(TAG);
-                                updateSysUiFlags(1 /* windowProgress == overview */);
-                            } else {
-                                mActivityControlHelper.onLaunchTaskSuccess(mActivity);
-                            }
-                        }, mMainThreadHandler);
-                        doLogGesture(NEW_TASK);
-                    }
-                    reset();
-                }
-                mCanceled = false;
-                mFinishingRecentsAnimationForNewTaskId = -1;
-            });
-        }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+        startNewTask(STATE_HANDLER_INVALIDATED, success -> {
+            if (!success) {
+                // We couldn't launch the task, so take user to overview so they can
+                // decide what to do instead of staying in this broken state.
+                endLauncherTransitionController();
+                updateSysUiFlags(1 /* windowProgress == overview */);
+            }
+            doLogGesture(NEW_TASK);
+        });
     }
 
-    public void reset() {
+    private void reset() {
         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
@@ -1283,7 +1034,7 @@
      * Cancels any running animation so that the active target can be overriden by a new swipe
      * handle (in case of quick switch).
      */
-    public void cancelCurrentAnimation(SwipeSharedState sharedState) {
+    private void cancelCurrentAnimation(SwipeSharedState sharedState) {
         mCanceled = true;
         mCurrentShift.cancelAnimation();
         if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1306,7 +1057,7 @@
     }
 
     private void invalidateHandler() {
-        endRunningWindowAnim();
+        endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
@@ -1322,7 +1073,7 @@
         mRecentsView.onGestureAnimationEnd();
 
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
-        mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+        removeLiveTileOverlay();
     }
 
     private void endLauncherTransitionController() {
@@ -1437,7 +1188,8 @@
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
-        mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
+        mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+                true /* screenshot */);
         mRecentsView.onSwipeUpAnimationSuccess();
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1446,17 +1198,28 @@
         reset();
     }
 
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    private void setTargetAlphaProvider(
-            BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
+    private void setTargetAlphaProvider(TargetAlphaProvider provider) {
         mClipAnimationHelper.setTaskAlphaCallback(provider);
         updateFinalShift();
     }
 
-    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
+    private synchronized void addLiveTileOverlay() {
+        if (!mLiveTileOverlayAttached) {
+            mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
+            mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
+            mLiveTileOverlayAttached = true;
+        }
+    }
+
+    private synchronized void removeLiveTileOverlay() {
+        if (mLiveTileOverlayAttached) {
+            mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+            mRecentsView.setLiveTileOverlay(null);
+            mLiveTileOverlayAttached = false;
+        }
+    }
+
+    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
         if (!isNotInRecents(app)) {
             return 0;
         }
@@ -1467,16 +1230,4 @@
         return app.isNotInRecents
                 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
     }
-
-    private interface RunningWindowAnim {
-        void end();
-
-        static RunningWindowAnim wrap(Animator animator) {
-            return animator::end;
-        }
-
-        static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
-            return rectFSpringAnim::end;
-        }
-    }
 }
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 c2876180..7f1aae5 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
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -31,6 +32,10 @@
 import com.android.quickstep.util.LayoutUtils;
 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.recents.model.Task.TaskKey;
+
+import java.util.ArrayList;
 
 public class FallbackRecentsView extends RecentsView<RecentsActivity> {
 
@@ -54,6 +59,8 @@
     private float mZoomScale = 1f;
     private float mZoomTranslationY = 0f;
 
+    private RunningTaskInfo mRunningTaskInfo;
+
     public FallbackRecentsView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -88,6 +95,12 @@
     }
 
     @Override
+    public void reset() {
+        super.reset();
+        resetViewUI();
+    }
+
+    @Override
     protected void getTaskSize(DeviceProfile dp, Rect outRect) {
         LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
     }
@@ -115,6 +128,12 @@
     }
 
     @Override
+    public void resetTaskVisuals() {
+        super.resetTaskVisuals();
+        setFullscreenProgress(mFullscreenProgress);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
@@ -139,4 +158,41 @@
         TRANSLATION_Y.set(this, Utilities.mapRange(mZoomInProgress, 0, mZoomTranslationY));
         FULLSCREEN_PROGRESS.set(this, mZoomInProgress);
     }
+
+    public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+        mRunningTaskInfo = runningTaskInfo;
+        onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
+    }
+
+    @Override
+    public void setCurrentTask(int runningTaskId) {
+        super.setCurrentTask(runningTaskId);
+        if (mRunningTaskInfo != null && mRunningTaskInfo.taskId != runningTaskId) {
+            mRunningTaskInfo = null;
+        }
+    }
+
+    @Override
+    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 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;
+            for (Task t : tasks) {
+                if (t.key.id == mRunningTaskId) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1);
+                newList.addAll(tasks);
+                newList.add(Task.from(new TaskKey(mRunningTaskInfo), mRunningTaskInfo, false));
+                tasks = newList;
+            }
+        }
+        super.applyLoadPlan(tasks);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 1820729..2b369e7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -80,7 +80,8 @@
         if (!insets.equals(mInsets)) {
             super.setInsets(insets);
         }
-        setBackground(insets.top == 0 ? null
+        setBackground(insets.top == 0  || !mAllowSysuiScrims
+                ? null
                 : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 38b5a13..d4cdaf0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2019 The Android Open Source Project
  *
@@ -82,22 +83,26 @@
     private int mDirection;
     private ActivityControlHelper mActivityControlHelper;
 
-    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;
+    private final boolean mIsAssistGestureConstrained;
 
     public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
             ActivityControlHelper activityControlHelper, InputConsumer delegate,
-            InputMonitorCompat inputMonitor) {
+        InputMonitorCompat inputMonitor, boolean isAssistGestureConstrained) {
         super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
         mSysUiProxy = systemUiProxy;
-        mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
+        mIsAssistGestureConstrained = isAssistGestureConstrained;
+        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);
 
@@ -117,8 +122,6 @@
     @Override
     public void onMotionEvent(MotionEvent ev) {
         // TODO add logging
-        mGestureDetector.onTouchEvent(ev);
-
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
                 mActivePointerId = ev.getPointerId(0);
@@ -213,6 +216,8 @@
                 break;
         }
 
+        mGestureDetector.onTouchEvent(ev);
+
         if (mState != STATE_ACTIVE) {
             mDelegate.onMotionEvent(ev);
         }
@@ -220,9 +225,9 @@
 
     private void updateAssistantProgress() {
         if (!mLaunchedAssistant) {
-            mLastProgress = Math.min(mDistance * 1f / mDistThreshold, 1) * mTimeFraction;
+            mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
             try {
-                if (mDistance >= mDistThreshold && mTimeFraction >= 1) {
+                if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
                     mSysUiProxy.onAssistantGestureCompletion(0);
                     startAssistantInternal(SWIPE);
 
@@ -270,8 +275,11 @@
     private class AssistantGestureListener extends SimpleOnGestureListener {
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            if (isValidAssistantGestureAngle(velocityX, -velocityY)
-                && !mLaunchedAssistant && mState != STATE_DELEGATE_ACTIVE) {
+            if (!mIsAssistGestureConstrained
+                    && isValidAssistantGestureAngle(velocityX, -velocityY)
+                    && mDistance >= mFlingDistThreshold
+                    && !mLaunchedAssistant
+                    && mState != STATE_DELEGATE_ACTIVE) {
                 mLastProgress = 1;
                 try {
                     mSysUiProxy.onAssistantGestureCompletion(
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 db2af59..b24c788 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
@@ -22,6 +22,8 @@
 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 android.content.ComponentName;
@@ -34,18 +36,16 @@
 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.LockScreenRecentsActivity;
 import com.android.quickstep.MultiStateCallback;
 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.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -77,6 +77,7 @@
 
     private final PointF mTouchDown = new PointF();
     private final ClipAnimationHelper mClipAnimationHelper;
+    private int mLogId;
     private final ClipAnimationHelper.TransformParams mTransformParams;
     private final Point mDisplaySize;
     private final MultiStateCallback mStateCallback;
@@ -91,19 +92,20 @@
     private SwipeAnimationTargetSet mTargetSet;
 
     public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState,
-            InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) {
+            InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId,
+            int logId) {
         mContext = context;
         mTouchSlopSquared = squaredTouchSlop(context);
         mSwipeSharedState = swipeSharedState;
         mClipAnimationHelper = new ClipAnimationHelper(context);
+        mLogId = logId;
         mTransformParams = new ClipAnimationHelper.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);
@@ -206,12 +208,11 @@
         Intent intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
 
         mInputMonitorCompat.pilferPointers();
-        BackgroundExecutor.get().submit(
-                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                        intent, null, newListenerSet, null, null));
+        startRecentsActivityAsync(intent, newListenerSet);
     }
 
     @Override
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 d05ca2a..e0ff8af 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
@@ -15,344 +15,442 @@
  */
 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 android.view.MotionEvent.INVALID_POINTER_ID;
-
-import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
+import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
-import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
+import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
+import android.animation.AnimatorSet;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 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 android.os.Bundle;
 
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
-import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
+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.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.InputConsumerController;
 
-public class FallbackNoButtonInputConsumer implements InputConsumer, SwipeAnimationListener {
+public class FallbackNoButtonInputConsumer extends
+        BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
 
-    private static final int STATE_NOT_FINISHED = 0;
-    private static final int STATE_FINISHED_TO_HOME = 1;
-    private static final int STATE_FINISHED_TO_APP = 2;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
 
-    private static final float PROGRESS_TO_END_GESTURE = -2;
+    private static int getFlagForIndex(int index, String name) {
+        if (DEBUG_STATES) {
+            STATE_NAMES[index] = name;
+        }
+        return 1 << index;
+    }
 
-    private final ActivityControlHelper mActivityControlHelper;
-    private final InputMonitorCompat mInputMonitor;
-    private final Context mContext;
-    private final NavBarPosition mNavBarPosition;
-    private final SwipeSharedState mSwipeSharedState;
-    private final OverviewComponentObserver mOverviewComponentObserver;
-    private final int mRunningTaskId;
+    private static final int STATE_RECENTS_PRESENT =
+            getFlagForIndex(0, "STATE_RECENTS_PRESENT");
+    private static final int STATE_HANDLER_INVALIDATED =
+            getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
 
-    private final ClipAnimationHelper mClipAnimationHelper;
-    private final TransformParams mTransformParams = new TransformParams();
-    private final float mTransitionDragLength;
-    private final DeviceProfile mDP;
+    private static final int STATE_GESTURE_CANCELLED =
+            getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
+    private static final int STATE_GESTURE_COMPLETED =
+            getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
+    private static final int STATE_APP_CONTROLLER_RECEIVED =
+            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
 
-    private final RectF mSwipeTouchRegion;
-    private final boolean mDisableHorizontalSwipe;
+    public enum GestureEndTarget {
+        HOME(3, 100, 1),
+        RECENTS(1, 300, 0),
+        LAST_TASK(0, 150, 1),
+        NEW_TASK(0, 150, 1);
 
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
+        private final float mEndProgress;
+        private final long mDurationMultiplier;
+        private final float mLauncherAlpha;
 
-    private int mActivePointerId = -1;
-    // Slop used to determine when we say that the gesture has started.
-    private boolean mPassedPilferInputSlop;
+        GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
+            mEndProgress = endProgress;
+            mDurationMultiplier = durationMultiplier;
+            mLauncherAlpha = launcherAlpha;
+        }
+    }
 
-    private VelocityTracker mVelocityTracker;
+    private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
-    // Distance after which we start dragging the window.
-    private final float mTouchSlop;
+    private boolean mIsMotionPaused = false;
+    private GestureEndTarget mEndTarget;
 
-    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
-    private float mStartDisplacement;
-    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
-    private float mProgress;
+    private final boolean mInQuickSwitchMode;
+    private final boolean mContinuingLastGesture;
+    private final boolean mRunningOverHome;
+    private final boolean mSwipeUpOverHome;
 
-    private int mState = STATE_NOT_FINISHED;
+    private final RunningTaskInfo mRunningTaskInfo;
+
+    private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
+    private RunningWindowAnim mFinishAnimation;
 
     public FallbackNoButtonInputConsumer(Context context,
-            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
-            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
             OverviewComponentObserver overviewComponentObserver,
-            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
-        mContext = context;
-        mActivityControlHelper = activityControlHelper;
-        mInputMonitor = inputMonitor;
-        mOverviewComponentObserver = overviewComponentObserver;
-        mRunningTaskId = runningTaskInfo.id;
+            RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
+            InputConsumerController inputConsumer,
+            boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
+        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        mLauncherAlpha.value = 1;
 
-        mSwipeSharedState = swipeSharedState;
-        mSwipeTouchRegion = swipeTouchRegion;
-        mDisableHorizontalSwipe = disableHorizontalSwipe;
+        mRunningTaskInfo = runningTaskInfo;
+        mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
+        mContinuingLastGesture = continuingLastGesture;
+        mRunningOverHome = ActivityManagerWrapper.isHomeTask(runningTaskInfo);
+        mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
 
-        mNavBarPosition = new NavBarPosition(context);
-        mVelocityTracker = VelocityTracker.obtain();
-
-        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
-                * ViewConfiguration.get(context).getScaledTouchSlop();
-
-        mClipAnimationHelper = new ClipAnimationHelper(context);
-
-        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
-        Rect tempRect = new Rect();
-        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
-                mDP, context, tempRect);
-        mClipAnimationHelper.updateTargetRect(tempRect);
-    }
-
-    @Override
-    public int getType() {
-        return TYPE_FALLBACK_NO_BUTTON;
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            return;
-        }
-
-        mVelocityTracker.addMovement(ev);
-        if (ev.getActionMasked() == ACTION_POINTER_UP) {
-            mVelocityTracker.clear();
-        }
-
-        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 (!mPassedPilferInputSlop) {
-                    // Cancel interaction in case of multi-touch interaction
-                    int ptrIdx = ev.getActionIndex();
-                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
-                        forceCancelGesture(ev);
-                    }
-                }
-                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: {
-                int pointerIndex = ev.findPointerIndex(mActivePointerId);
-                if (pointerIndex == INVALID_POINTER_ID) {
-                    break;
-                }
-                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                float displacement = getDisplacement(ev);
-
-                if (!mPassedPilferInputSlop) {
-                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
-                            > Math.abs(mLastPos.y - mDownPos.y)) {
-                        // Horizontal gesture is not allowed in this region
-                        forceCancelGesture(ev);
-                        break;
-                    }
-
-                    if (Math.abs(displacement) >= mTouchSlop) {
-                        mPassedPilferInputSlop = true;
-
-                        // Deferred gesture, start the animation and gesture tracking once
-                        // we pass the actual touch slop
-                        startTouchTrackingForWindowAnimation(displacement);
-                    }
-                } else {
-                    updateDisplacement(displacement - mStartDisplacement);
-                }
-                break;
-            }
-            case ACTION_CANCEL:
-            case ACTION_UP: {
-                finishTouchTracking(ev);
-                break;
-            }
-        }
-    }
-
-    private void startTouchTrackingForWindowAnimation(float displacement) {
-        mStartDisplacement = Math.min(displacement, -mTouchSlop);
-
-        RecentsAnimationListenerSet listenerSet =
-                mSwipeSharedState.newRecentsAnimationListenerSet();
-        listenerSet.addListener(this);
-        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
-        BackgroundExecutor.get().submit(
-                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                        homeIntent, null, listenerSet, null, null));
-
-        ActivityManagerWrapper.getInstance().closeSystemWindows(
-                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mInputMonitor.pilferPointers();
-    }
-
-    private void updateDisplacement(float displacement) {
-        mProgress = displacement / mTransitionDragLength;
-        mTransformParams.setProgress(mProgress);
-
-        if (mSwipeAnimationTargetSet != null) {
-            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
-        }
-    }
-
-    private void forceCancelGesture(MotionEvent ev) {
-        int action = ev.getAction();
-        ev.setAction(ACTION_CANCEL);
-        finishTouchTracking(ev);
-        ev.setAction(action);
-    }
-
-    /**
-     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
-     * the animation can still be running.
-     */
-    private void finishTouchTracking(MotionEvent ev) {
-        if (ev.getAction() == ACTION_CANCEL) {
-            mState = STATE_FINISHED_TO_APP;
+        if (mSwipeUpOverHome) {
+            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
         } else {
-            mVelocityTracker.computeCurrentVelocity(1000,
-                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
-            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
-            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-            float velocity = mNavBarPosition.isRightEdge() ? velocityX
-                    : mNavBarPosition.isLeftEdge() ? -velocityX
-                            : velocityY;
+            mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+        }
+
+        initStateCallbacks();
+    }
+
+    private void initStateCallbacks() {
+        mStateCallback = new MultiStateCallback(STATE_NAMES);
+
+        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidated);
+        mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidatedWithRecents);
+
+        mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+                this::finishAnimationTargetSetAnimationComplete);
+
+        if (mInQuickSwitchMode) {
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+                            | STATE_RECENTS_PRESENT,
+                    this::finishAnimationTargetSet);
+        } else {
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+                    this::finishAnimationTargetSet);
+        }
+    }
+
+    private void onLauncherAlphaChanged() {
+        if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
+            applyTransformUnchecked();
+        }
+    }
+
+    @Override
+    protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
+        mActivity = activity;
+        mRecentsView = activity.getOverviewPanel();
+        linkRecentsViewScroll();
+        mRecentsView.setDisallowScrollToClearAll(true);
+        mRecentsView.getClearAllButton().setVisibilityAlpha(0);
+
+        mRecentsView.setZoomProgress(1);
+
+        if (!mContinuingLastGesture) {
+            if (mRunningOverHome) {
+                mRecentsView.onGestureAnimationStart(mRunningTaskInfo);
+            } else {
+                mRecentsView.onGestureAnimationStart(mRunningTaskId);
+            }
+        }
+        setStateOnUiThread(STATE_RECENTS_PRESENT);
+        return true;
+    }
+
+    @Override
+    protected boolean moveWindowWithRecentsScroll() {
+        return mInQuickSwitchMode;
+    }
+
+    @Override
+    public void initWhenReady() {
+        if (mInQuickSwitchMode) {
+            // Only init if we are in quickswitch mode
+            super.initWhenReady();
+        }
+    }
+
+    @Override
+    public void updateDisplacement(float displacement) {
+        if (!mInQuickSwitchMode) {
+            super.updateDisplacement(displacement);
+        }
+    }
+
+    @Override
+    protected InputConsumer createNewInputProxyHandler() {
+        // Just consume all input on the active task
+        return InputConsumer.NO_OP;
+    }
+
+    @Override
+    public void onMotionPauseChanged(boolean isPaused) {
+        if (!mInQuickSwitchMode) {
+            mIsMotionPaused = isPaused;
+            mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+                    .setDuration(150).start();
+            performHapticFeedback();
+        }
+    }
+
+    @Override
+    public Intent getLaunchIntent() {
+        if (mInQuickSwitchMode || mSwipeUpOverHome) {
+            return mOverviewComponentObserver.getOverviewIntent();
+        } else {
+            return mOverviewComponentObserver.getHomeIntent();
+        }
+    }
+
+    @Override
+    public void updateFinalShift() {
+        mTransformParams.setProgress(mCurrentShift.value);
+        mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
+                && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
+        if (mRecentsAnimationWrapper.targetSet != null) {
+            applyTransformUnchecked();
+        }
+    }
+
+    @Override
+    public void onGestureCancelled() {
+        updateDisplacement(0);
+        mEndTarget = LAST_TASK;
+        setStateOnUiThread(STATE_GESTURE_CANCELLED);
+    }
+
+    @Override
+    public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+        mEndVelocityPxPerMs.set(0, velocity.y / 1000);
+        if (mInQuickSwitchMode) {
+            // For now set it to non-null, it will be reset before starting the animation
+            mEndTarget = LAST_TASK;
+        } else {
             float flingThreshold = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_threshold_velocity);
-            boolean isFling = Math.abs(velocity) > flingThreshold;
+            boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
-            boolean goingHome;
-            if (!isFling) {
-                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+            if (isFling) {
+                mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
+            } else if (mIsMotionPaused) {
+                mEndTarget = RECENTS;
             } else {
-                goingHome = velocity < 0;
+                mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+            }
+        }
+        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+    }
+
+    @Override
+    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+        if (mInQuickSwitchMode && mEndTarget != null) {
+            sharedState.canGestureBeContinued = true;
+            sharedState.goingToLauncher = false;
+
+            mCanceled = true;
+            mCurrentShift.cancelAnimation();
+            if (mFinishAnimation != null) {
+                mFinishAnimation.cancel();
             }
 
-            if (goingHome) {
-                mState = STATE_FINISHED_TO_HOME;
-            } else {
-                mState = STATE_FINISHED_TO_APP;
+            if (mRecentsView != null) {
+                if (mFinishingRecentsAnimationForNewTaskId != -1) {
+                    TaskView newRunningTaskView = mRecentsView.getTaskView(
+                            mFinishingRecentsAnimationForNewTaskId);
+                    int newRunningTaskId = newRunningTaskView != null
+                            ? newRunningTaskView.getTask().key.id
+                            : -1;
+                    mRecentsView.setCurrentTask(newRunningTaskId);
+                    sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+                }
+                mRecentsView.setOnScrollChangeListener(null);
+            }
+        } else {
+            setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+        }
+    }
+
+    private void onHandlerInvalidated() {
+        mActivityInitListener.unregister();
+        if (mGestureEndCallback != null) {
+            mGestureEndCallback.run();
+        }
+        if (mFinishAnimation != null) {
+            mFinishAnimation.end();
+        }
+    }
+
+    private void onHandlerInvalidatedWithRecents() {
+        mRecentsView.onGestureAnimationEnd();
+        mRecentsView.setDisallowScrollToClearAll(false);
+        mRecentsView.getClearAllButton().setVisibilityAlpha(1);
+    }
+
+    private void finishAnimationTargetSetAnimationComplete() {
+        switch (mEndTarget) {
+            case HOME: {
+                if (mSwipeUpOverHome) {
+                    mRecentsAnimationWrapper.finish(false, null, false);
+                    // Send a home intent to clear the task stack
+                    mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
+                } else {
+                    mRecentsAnimationWrapper.finish(true, null, true);
+                }
+                break;
+            }
+            case LAST_TASK:
+                mRecentsAnimationWrapper.finish(false, null, false);
+                break;
+            case RECENTS: {
+                if (mSwipeUpOverHome) {
+                    mRecentsAnimationWrapper.finish(true, null, true);
+                    break;
+                }
+
+                ThumbnailData thumbnail =
+                        mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
+                mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+                        false /* screenshot */);
+
+                ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+                ActivityOptionsCompat.setFreezeRecentTasksList(options);
+
+                Bundle extras = new Bundle();
+                extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
+                extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
+
+                Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
+                        .putExtras(extras);
+                mContext.startActivity(intent, options.toBundle());
+                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+                break;
+            }
+            case NEW_TASK: {
+                startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
+                break;
             }
         }
 
-        if (mSwipeAnimationTargetSet != null) {
-            finishAnimationTargetSet();
-        }
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     private void finishAnimationTargetSet() {
-        if (mState == STATE_FINISHED_TO_APP) {
-            mSwipeAnimationTargetSet.finishController(false, null, false);
-        } else {
-            if (mProgress < PROGRESS_TO_END_GESTURE) {
-                mSwipeAnimationTargetSet.finishController(true, null, true);
+        if (mInQuickSwitchMode) {
+            // Recalculate the end target, some views might have been initialized after
+            // gesture has ended.
+            if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
+                mEndTarget = LAST_TASK;
             } else {
-                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
-                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
-                if (duration < 0) {
-                    duration = MIN_SWIPE_DURATION;
-                }
-
-                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
-                anim.addUpdateListener(a -> {
-                    float p = (Float) anim.getAnimatedValue();
-                    mTransformParams.setProgress(p);
-                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
-                });
-                anim.setDuration(duration);
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mSwipeAnimationTargetSet.finishController(true, null, true);
-                    }
-                });
-                anim.start();
+                final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+                final int taskToLaunch = mRecentsView.getNextPage();
+                mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
+                        ? NEW_TASK : LAST_TASK;
             }
         }
+
+        float endProgress = mEndTarget.mEndProgress;
+        long duration = (long) (mEndTarget.mDurationMultiplier *
+                Math.abs(endProgress - mCurrentShift.value));
+        if (mRecentsView != null) {
+            duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+        }
+        if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
+            AnimationSuccessListener endListener = new AnimationSuccessListener() {
+
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    finishAnimationTargetSetAnimationComplete();
+                    mFinishAnimation = null;
+                }
+            };
+
+            if (mEndTarget == HOME && !mRunningOverHome) {
+                RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
+                anim.addAnimatorListener(endListener);
+                anim.start(mEndVelocityPxPerMs);
+                mFinishAnimation = RunningWindowAnim.wrap(anim);
+            } else {
+
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(mLauncherAlpha.animateToValue(
+                        mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
+                anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
+
+                anim.setDuration(duration);
+                anim.addListener(endListener);
+                anim.start();
+                mFinishAnimation = RunningWindowAnim.wrap(anim);
+            }
+
+        } else {
+            finishAnimationTargetSetAnimationComplete();
+        }
     }
 
     @Override
     public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        mSwipeAnimationTargetSet = targetSet;
-        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+        super.onRecentsAnimationStart(targetSet);
+        mRecentsAnimationWrapper.enableInputConsumer();
 
-        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
-        if (runningTaskTarget != null) {
-            mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
+        if (mRunningOverHome) {
+            mClipAnimationHelper.prepareAnimation(mDp, true);
         }
-        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+        applyTransformUnchecked();
 
-        overviewStackBounds
-                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
-        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
-        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
-
-        if (mState != STATE_NOT_FINISHED) {
-            finishAnimationTargetSet();
-        }
+        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
     }
 
     @Override
-    public void onRecentsAnimationCanceled() { }
-
-    private float getDisplacement(MotionEvent ev) {
-        if (mNavBarPosition.isRightEdge()) {
-            return ev.getX() - mDownPos.x;
-        } else if (mNavBarPosition.isLeftEdge()) {
-            return mDownPos.x - ev.getX();
-        } else {
-            return ev.getY() - mDownPos.y;
-        }
+    public void onRecentsAnimationCanceled() {
+        mRecentsAnimationWrapper.setController(null);
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
-    @Override
-    public boolean allowInterceptByParent() {
-        return !mPassedPilferInputSlop;
+    /**
+     * Creates an animation that transforms the current app window into the home app.
+     * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
+     */
+    private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
+        HomeAnimationFactory factory = new HomeAnimationFactory() {
+            @Override
+            public RectF getWindowTargetRect() {
+                return HomeAnimationFactory.getDefaultWindowTargetRect(mDp);
+            }
+
+            @Override
+            public AnimatorPlaybackController createActivityAnimationToHome() {
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1));
+                anim.setDuration(duration);
+                return AnimatorPlaybackController.wrap(anim, duration);
+            }
+        };
+        return createWindowAnimationToHome(startProgress, factory);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index f5cf654..a1e5d47 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,7 +33,6 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
-    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -45,7 +44,6 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
-            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
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 4c137d3..e41880d 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,12 +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.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
@@ -43,32 +44,25 @@
 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.ActivityControlHelper;
+import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.BaseSwipeUpHandler.Factory;
 import com.android.quickstep.OverviewCallbacks;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.WindowTransformSwipeHandler;
-import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
 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.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
-
 import java.util.function.Consumer;
 
-import androidx.annotation.UiThread;
-
 /**
  * Input consumer for handling events originating from an activity other than Launcher
  */
@@ -83,16 +77,14 @@
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
-    private final RecentsModel mRecentsModel;
-    private final Intent mHomeIntent;
-    private final ActivityControlHelper mActivityControlHelper;
     private final OverviewCallbacks mOverviewCallbacks;
-    private final InputConsumerController mInputConsumer;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
     private final RectF mSwipeTouchRegion;
 
+    private final BaseSwipeUpHandler.Factory mHandlerFactory;
+
     private final NavBarPosition mNavBarPosition;
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
@@ -100,7 +92,7 @@
     private final float mMotionPauseMinDisplacement;
     private VelocityTracker mVelocityTracker;
 
-    private WindowTransformSwipeHandler mInteractionHandler;
+    private BaseSwipeUpHandler mInteractionHandler;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -114,7 +106,7 @@
     private final boolean mDisableHorizontalSwipe;
 
     // Slop used to check when we start moving window.
-    private boolean mPaddedWindowMoveSlop;
+    private boolean mPassedWindowMoveSlop;
     // Slop used to determine when we say that the gesture has started.
     private boolean mPassedPilferInputSlop;
 
@@ -126,22 +118,22 @@
         ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
                 true /* restoreHomeStackPosition */);
     };
+    private int mLogId;
 
     public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
-            InputConsumerController inputConsumer,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            RectF swipeTouchRegion, boolean disableHorizontalSwipe) {
+            RectF swipeTouchRegion, boolean disableHorizontalSwipe,
+            Factory handlerFactory, int logId) {
         super(base);
+        mLogId = logId;
 
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
-        mRecentsModel = recentsModel;
-        mHomeIntent = homeIntent;
         mMode = SysUINavigationMode.getMode(base);
         mSwipeTouchRegion = swipeTouchRegion;
+        mHandlerFactory = handlerFactory;
 
         mMotionPauseDetector = new MotionPauseDetector(base);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -150,11 +142,9 @@
         mVelocityTracker = VelocityTracker.obtain();
         mInputMonitorCompat = inputMonitorCompat;
 
-        mActivityControlHelper = activityControl;
         boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
-        mInputConsumer = inputConsumer;
         mSwipeSharedState = swipeSharedState;
 
         mNavBarPosition = new NavBarPosition(base);
@@ -163,7 +153,7 @@
         float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
         mSquaredTouchSlop = slop * slop;
 
-        mPassedPilferInputSlop = mPaddedWindowMoveSlop = continuingPreviousGesture;
+        mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
     }
 
@@ -186,7 +176,7 @@
         }
 
         // Proxy events to recents view
-        if (mPaddedWindowMoveSlop && mInteractionHandler != null
+        if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
                     mNavBarPosition.getRotationMode()));
@@ -213,7 +203,7 @@
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
                 if (!mIsDeferredDownTarget) {
-                    startTouchTrackingForWindowAnimation(ev.getEventTime());
+                    startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
                 }
 
                 RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
@@ -251,17 +241,21 @@
                 float displacement = getDisplacement(ev);
                 float displacementX = mLastPos.x - mDownPos.x;
 
-                if (!mPaddedWindowMoveSlop) {
+                if (!mPassedWindowMoveSlop) {
                     if (!mIsDeferredDownTarget) {
                         // Normal gesture, ensure we pass the drag slop before we start tracking
                         // the gesture
                         if (Math.abs(displacement) > mTouchSlop) {
-                            mPaddedWindowMoveSlop = true;
+                            mPassedWindowMoveSlop = true;
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
                         }
                     }
                 }
 
+                float horizontalDist = Math.abs(displacementX);
+                float upDist = -displacement;
+                boolean isLikelyToStartNewTask = horizontalDist > upDist;
+
                 if (!mPassedPilferInputSlop) {
                     float displacementY = mLastPos.y - mDownPos.y;
                     if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
@@ -277,10 +271,11 @@
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
-                            startTouchTrackingForWindowAnimation(ev.getEventTime());
+                            startTouchTrackingForWindowAnimation(
+                                    ev.getEventTime(), isLikelyToStartNewTask);
                         }
-                        if (!mPaddedWindowMoveSlop) {
-                            mPaddedWindowMoveSlop = true;
+                        if (!mPassedWindowMoveSlop) {
+                            mPassedWindowMoveSlop = true;
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
 
                         }
@@ -289,15 +284,12 @@
                 }
 
                 if (mInteractionHandler != null) {
-                    if (mPaddedWindowMoveSlop) {
+                    if (mPassedWindowMoveSlop) {
                         // Move
                         mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                     }
 
                     if (mMode == Mode.NO_BUTTON) {
-                        float horizontalDist = Math.abs(displacementX);
-                        float upDist = -displacement;
-                        boolean isLikelyToStartNewTask = horizontalDist > upDist;
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
@@ -329,16 +321,14 @@
         mInteractionHandler.onGestureStarted();
     }
 
-    private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
+    private void startTouchTrackingForWindowAnimation(
+            long touchTimeMs, boolean isLikelyToStartNewTask) {
         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
         RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
-        final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                listenerSet != null, mInputConsumer);
+        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
+                listenerSet != null, isLikelyToStartNewTask);
 
-        // Preload the plan
-        mRecentsModel.getTasks(null);
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
@@ -352,9 +342,9 @@
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
             newListenerSet.addListener(handler);
-            BackgroundExecutor.get().submit(
-                    () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                            mHomeIntent, null, newListenerSet, null, null));
+            Intent intent = handler.getLaunchIntent();
+            intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
+            startRecentsActivityAsync(intent, newListenerSet);
         }
     }
 
@@ -366,7 +356,7 @@
         RaceConditionTracker.onEvent(UP_EVT, ENTER);
         TraceHelper.endSection("TouchInt");
 
-        if (mPaddedWindowMoveSlop && mInteractionHandler != null) {
+        if (mPassedWindowMoveSlop && mInteractionHandler != null) {
             if (ev.getActionMasked() == ACTION_CANCEL) {
                 mInteractionHandler.onGestureCancelled();
             } else {
@@ -409,14 +399,7 @@
             // The consumer is being switched while we are active. Set up the shared state to be
             // used by the next animation
             removeListener();
-            GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
-            mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
-            mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
-            if (mSwipeSharedState.canGestureBeContinued) {
-                mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
-            } else {
-                mInteractionHandler.reset();
-            }
+            mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState);
         }
     }
 
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 b021df8..581ab6d 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
@@ -19,6 +19,7 @@
 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;
 
@@ -26,6 +27,7 @@
 
 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.systemui.shared.system.ActivityManagerWrapper;
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 425b8b6..f40d552 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
@@ -22,18 +22,21 @@
 
 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;
+import android.content.Intent;
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
-import com.android.quickstep.OverviewCallbacks;
+import com.android.launcher3.logging.StatsLogUtils;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.quickstep.util.NavBarPosition;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer {
@@ -131,19 +134,28 @@
                 ? -velocityX : (mNavBarPosition.isLeftEdge() ? velocityX : -velocityY);
 
         final boolean triggerQuickstep;
+        int touch = Touch.FLING;
         if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
             triggerQuickstep = velocity > 0;
         } else {
             float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
             float displacementY = ev.getY() - mDownPos.y;
             triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+            touch = Touch.SWIPE;
         }
 
         if (triggerQuickstep) {
-            OverviewCallbacks.get(mContext).closeAllWindows();
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
             TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+            BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+            int pageIndex = -1; // This number doesn't reflect workspace page index.
+                                // It only indicates that launcher client screen was shown.
+            int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+            activity.getUserEventDispatcher().logActionOnContainer(
+                    touch, Direction.UP, containerType, pageIndex);
+            activity.getUserEventDispatcher().setPreviousHomeGesture(true);
         } else {
             // ignore
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
new file mode 100644
index 0000000..b251f9e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
@@ -0,0 +1,47 @@
+/*
+ * 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.logging;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * This class handles AOSP MetricsLogger function calls and logging around
+ * quickstep interactions and app launches.
+ */
+@SuppressWarnings("unused")
+public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
+
+    public static final int ALL_APPS_PREDICTION_TIPS = 2;
+
+    private static final String TAG = "UserEventDispatcher";
+
+    public UserEventDispatcherAppPredictionExtension(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onFillInLogContainerData(
+            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
+            @NonNull LauncherLogProto.Target targetParent) {
+        PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 6dc672e..68007da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
@@ -50,8 +51,6 @@
 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
  */
@@ -99,8 +98,8 @@
     // Whether to boost the opening animation target layers, or the closing
     private int mBoostModeTargetLayers = -1;
 
-    private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
-            (t, a1) -> a1;
+    private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
+    private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
 
     public ClipAnimationHelper(Context context) {
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
@@ -119,8 +118,12 @@
     }
 
     public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
-        mHomeStackBounds.set(homeStackBounds);
         updateSourceStack(target);
+        updateHomeBounds(homeStackBounds);
+    }
+
+    public void updateHomeBounds(Rect homeStackBounds) {
+        mHomeStackBounds.set(homeStackBounds);
     }
 
     public void updateTargetRect(Rect targetRect) {
@@ -187,12 +190,12 @@
             Rect crop = mTmpRect;
             crop.set(app.sourceContainerBounds);
             crop.offsetTo(0, 0);
-            float alpha = 1f;
+            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.apply(app, params.targetAlpha);
+                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);
@@ -209,14 +212,22 @@
                         }
                         mCurrentCornerRadius = cornerRadius;
                     }
+                    // Fade out Assistant overlay.
+                    if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
+                            && app.isNotInRecents) {
+                        alpha = 1 - Interpolators.DEACCEL_2_5.getInterpolation(progress);
+                    }
                 } else if (targetSet.hasRecents) {
                     // If home has a different target then recents, reverse anim the
                     // home target.
                     alpha = 1 - (progress * params.targetAlpha);
                 }
-            } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
-                crop = null;
-                layer = Integer.MAX_VALUE;
+            } 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
@@ -247,11 +258,14 @@
         }
     }
 
-    public void setTaskAlphaCallback(
-            BiFunction<RemoteAnimationTargetCompat, Float, Float> callback) {
+    public void setTaskAlphaCallback(TargetAlphaProvider callback) {
         mTaskAlphaCallback = callback;
     }
 
+    public void setBaseAlphaCallback(TargetAlphaProvider callback) {
+        mBaseAlphaCallback = callback;
+    }
+
     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
         fromTaskThumbnailView(ttv, rv, null);
     }
@@ -357,6 +371,10 @@
         return mCurrentCornerRadius;
     }
 
+    public interface TargetAlphaProvider {
+        float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
+    }
+
     public static class TransformParams {
         float progress;
         public float offsetX;
@@ -384,6 +402,10 @@
             return this;
         }
 
+        public float getProgress() {
+            return progress;
+        }
+
         public TransformParams setCornerRadius(float cornerRadius) {
             this.cornerRadius = cornerRadius;
             return this;
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/ObjectWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
new file mode 100644
index 0000000..abfe3ad
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.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.quickstep.util;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Utility class to pass non-parcealable objects within same process using parcealable payload.
+ *
+ * It wraps the object in a binder as binders are singleton within a process
+ */
+public class ObjectWrapper<T> extends Binder {
+
+    private final T mObject;
+
+    public ObjectWrapper(T object) {
+        mObject = object;
+    }
+
+    public T get() {
+        return mObject;
+    }
+
+    public static IBinder wrap(Object obj) {
+        return new ObjectWrapper<>(obj);
+    }
+}
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
index 83601e6..b1999d7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -15,14 +15,17 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+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;
@@ -30,8 +33,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 
-import androidx.annotation.UiThread;
-
 /**
  * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners
  * on the main thread
@@ -39,7 +40,7 @@
 public class RecentsAnimationListenerSet implements RecentsAnimationListener {
 
     // The actual app surface is replaced by a screenshot upon recents animation cancelation when
-    // deferredWithScreenshot is true. Launcher takes the responsibility to clean up this screenshot
+    // 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;
@@ -81,7 +82,7 @@
         if (mCancelled) {
             targetSet.cancelAnimation();
         } else {
-            Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
                 for (SwipeAnimationListener listener : getListeners()) {
                     listener.onRecentsAnimationStart(targetSet);
                 }
@@ -90,15 +91,15 @@
     }
 
     @Override
-    public final void onAnimationCanceled(boolean deferredWithScreenshot) {
-        Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
+    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 (deferredWithScreenshot) {
-            MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
+        if (thumbnailData != null) {
+            MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(),
                     TRANSITION_DELAY);
         }
     }
@@ -109,6 +110,6 @@
 
     public void cancelListener() {
         mCancelled = true;
-        onAnimationCanceled(false);
+        onAnimationCanceled(null);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 77dc6f3..c6eafe6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -225,7 +225,18 @@
         }
     }
 
+    public void cancel() {
+        if (mAnimsStarted) {
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onCancel();
+            }
+        }
+        end();
+    }
+
     public interface OnUpdateListener {
         void onUpdate(RectF currentRect, float progress);
+
+        default void onCancel() { }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..83bc416
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,105 @@
+/*
+ * 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.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.states.OverviewState;
+
+/**
+ * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
+ */
+
+public class ShelfPeekAnim {
+
+    public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
+    public static final long DURATION = 240;
+
+    private final Launcher mLauncher;
+
+    private ShelfAnimState mShelfState;
+    private boolean mIsPeeking;
+
+    public ShelfPeekAnim(Launcher launcher) {
+        mLauncher = launcher;
+    }
+
+    /**
+     * Animates to the given state, canceling the previous animation if it was still running.
+     */
+    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+        if (mShelfState == shelfState) {
+            return;
+        }
+        mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
+        mShelfState = shelfState;
+        mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
+        if (mShelfState == ShelfAnimState.CANCEL) {
+            return;
+        }
+        float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
+        float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+        // Peek based on default overview progress so we can see hotseat if we're showing
+        // that instead of predictions in overview.
+        float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+        float shelfPeekingProgress = shelfHiddenProgress
+                - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
+        float toProgress = mShelfState == ShelfAnimState.HIDE
+                ? shelfHiddenProgress
+                : mShelfState == ShelfAnimState.PEEK
+                        ? shelfPeekingProgress
+                        : shelfOverviewProgress;
+        Animator shelfAnim = mLauncher.getStateManager()
+                .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
+        shelfAnim.setInterpolator(interpolator);
+        shelfAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mShelfState = ShelfAnimState.CANCEL;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                mIsPeeking = mShelfState == ShelfAnimState.PEEK;
+            }
+        });
+        shelfAnim.setDuration(duration).start();
+    }
+
+    /** @return Whether the shelf is currently peeking or animating to or from peeking. */
+    public boolean isPeeking() {
+        return mIsPeeking;
+    }
+
+    /** The various shelf states we can animate to. */
+    public enum ShelfAnimState {
+        HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
+
+        ShelfAnimState(boolean shouldPreformHaptic) {
+            this.shouldPreformHaptic = shouldPreformHaptic;
+        }
+
+        public final boolean shouldPreformHaptic;
+    }
+}
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 07e9686..958ef7d 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
@@ -15,32 +15,37 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+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.graphics.OverviewScrim;
+import com.android.quickstep.views.RecentsView;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-
 /**
  * Creates an animation where all the workspace items are moved into their final location,
  * staggered row by row from the bottom up.
@@ -58,19 +63,13 @@
 
     private final float mVelocity;
     private final float mSpringTransY;
-    private final View mViewToIgnore;
 
     private final List<Animator> mAnimators = new ArrayList<>();
 
-    /**
-     * @param floatingViewOriginalView The FloatingIconView's original view.
-     */
-    public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView,
-            float velocity) {
+    public StaggeredWorkspaceAnim(Launcher launcher, float velocity, boolean animateOverviewScrim) {
+        prepareToAnimate(launcher);
+
         mVelocity = velocity;
-        // We ignore this view since it's visibility and position is controlled by
-        // the FloatingIconView.
-        mViewToIgnore = floatingViewOriginalView;
 
         // Scale the translationY based on the initial velocity to better sync the workspace items
         // with the floating view.
@@ -79,9 +78,24 @@
                 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
 
         DeviceProfile grid = launcher.getDeviceProfile();
-        ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace()
-                .getChildAt(launcher.getWorkspace().getCurrentPage()))
-                .getShortcutsAndWidgets();
+        Workspace workspace = launcher.getWorkspace();
+        CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
+        ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
+        ViewGroup hotseat = launcher.getHotseat();
+
+        boolean workspaceClipChildren = workspace.getClipChildren();
+        boolean workspaceClipToPadding = workspace.getClipToPadding();
+        boolean cellLayoutClipChildren = cellLayout.getClipChildren();
+        boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
+        boolean hotseatClipChildren = hotseat.getClipChildren();
+        boolean hotseatClipToPadding = hotseat.getClipToPadding();
+
+        workspace.setClipChildren(false);
+        workspace.setClipToPadding(false);
+        cellLayout.setClipChildren(false);
+        cellLayout.setClipToPadding(false);
+        hotseat.setClipChildren(false);
+        hotseat.setClipToPadding(false);
 
         // Hotseat and QSB takes up two additional rows.
         int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
@@ -94,23 +108,65 @@
         }
 
         // Set up springs for the hotseat and qsb.
+        ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
         if (grid.isVerticalBarLayout()) {
-            ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0);
-            for (int i = hotseat.getChildCount() - 1; i >= 0; i--) {
-                View child = hotseat.getChildAt(i);
+            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatChild.getChildAt(i);
                 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
                 addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
             }
         } else {
-            View hotseat = launcher.getHotseat().getChildAt(0);
-            addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows);
+            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatChild.getChildAt(i);
+                addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+            }
 
             View qsb = launcher.findViewById(R.id.search_container_all_apps);
             addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows);
         }
 
-        addWorkspaceScrimAnimationForState(launcher, BACKGROUND_APP, 0);
-        addWorkspaceScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        if (animateOverviewScrim) {
+            addScrimAnimationForState(launcher, BACKGROUND_APP, 0);
+            addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        }
+
+        AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
+            int numAnimations = mAnimators.size();
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                numAnimations--;
+                if (numAnimations > 0) {
+                    return;
+                }
+
+                workspace.setClipChildren(workspaceClipChildren);
+                workspace.setClipToPadding(workspaceClipToPadding);
+                cellLayout.setClipChildren(cellLayoutClipChildren);
+                cellLayout.setClipToPadding(cellLayoutClipToPadding);
+                hotseat.setClipChildren(hotseatClipChildren);
+                hotseat.setClipToPadding(hotseatClipToPadding);
+            }
+        };
+
+        for (Animator a : mAnimators) {
+            a.addListener(resetClipListener);
+        }
+    }
+
+    /**
+     * Setup workspace with 0 duration to prepare for our staggered animation.
+     */
+    private void prepareToAnimate(Launcher launcher) {
+        LauncherStateManager stateManager = launcher.getStateManager();
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        // setRecentsAttachedToAppWindow() will animate recents out.
+        builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+        stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
+        builder.build().start();
+
+        // Stop scrolling so that it doesn't interfere with the translation offscreen.
+        launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
     }
 
     /**
@@ -134,10 +190,6 @@
      * @param totalRows Total number of rows.
      */
     private void addStaggeredAnimationForView(View v, int row, int totalRows) {
-        if (v == mViewToIgnore) {
-            return;
-        }
-
         // Invert the rows, because we stagger starting from the bottom of the screen.
         int invertedRow = totalRows - row;
         // Add 1 to the inverted row so that the bottom most row has a start delay.
@@ -157,13 +209,17 @@
         mAnimators.add(alpha);
     }
 
-    private void addWorkspaceScrimAnimationForState(Launcher launcher, LauncherState state,
-            long duration) {
+    private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
         AnimatorSetBuilder scrimAnimBuilder = new AnimatorSetBuilder();
         AnimationConfig scrimAnimConfig = new AnimationConfig();
         scrimAnimConfig.duration = duration;
         PropertySetter scrimPropertySetter = scrimAnimConfig.getPropertySetter(scrimAnimBuilder);
         launcher.getWorkspace().getStateTransitionAnimation().setScrim(scrimPropertySetter, state);
         mAnimators.add(scrimAnimBuilder.build());
+        Animator fadeOverviewScrim = ObjectAnimator.ofFloat(
+                launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
+                state.getOverviewScrimAlpha(launcher));
+        fadeOverviewScrim.setDuration(duration);
+        mAnimators.add(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
index 381c27a..3619d3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -15,8 +15,8 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+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;
@@ -68,25 +68,25 @@
 
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
         mOnFinishListener.accept(this);
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             controller.setInputConsumerEnabled(false);
             controller.finish(toRecents, sendUserLeaveHint);
 
             if (callback != null) {
-                MAIN_THREAD_EXECUTOR.execute(callback);
+                MAIN_EXECUTOR.execute(callback);
             }
         });
     }
 
     public void enableInputConsumer() {
-        BACKGROUND_EXECUTOR.submit(() -> {
+        UI_HELPER_EXECUTOR.submit(() -> {
             controller.hideCurrentInputMethod();
             controller.setInputConsumerEnabled(true);
         });
     }
 
     public void setWindowThresholdCrossed(boolean thresholdCrossed) {
-        BACKGROUND_EXECUTOR.execute(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
             if (mShouldMinimizeSplitScreen && thresholdCrossed) {
                 // NOTE: As a workaround for conflicting animations (Launcher animating the task
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 7fac813..b06d4bc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
 
 import static com.android.launcher3.Utilities.prefixTextWithIcon;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
@@ -41,7 +42,6 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -117,7 +117,7 @@
             return;
         }
 
-        Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
+        THREAD_POOL_EXECUTOR.execute(() -> {
             final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
                     task.getTopComponent().getPackageName(),
                     UserHandle.of(task.key.userId));
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..c2cb720 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,14 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+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.LayoutUtils;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.RecentsExtraCard;
 
 /**
  * {@link RecentsView} used in Launcher activity
@@ -57,8 +61,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);
     }
@@ -144,6 +169,25 @@
         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()) {
@@ -264,4 +308,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 6cb2506..ca3b92a 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
@@ -17,7 +17,6 @@
 package com.android.quickstep.views;
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS;
-
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -35,6 +34,7 @@
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
@@ -72,6 +72,7 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
@@ -95,6 +96,7 @@
 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;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
@@ -108,7 +110,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -185,7 +186,7 @@
     private final ViewPool<TaskView> mTaskViewPool;
 
     private boolean mDwbToastShown;
-    private boolean mDisallowScrollToClearAll;
+    protected boolean mDisallowScrollToClearAll;
     private boolean mOverlayEnabled;
     private boolean mFreezeViewVisibility;
 
@@ -226,7 +227,7 @@
                 return;
             }
 
-            BackgroundExecutor.get().submit(() -> {
+            UI_HELPER_EXECUTOR.execute(() -> {
                 TaskView taskView = getTaskView(taskId);
                 if (taskView == null) {
                     return;
@@ -269,8 +270,8 @@
     private int mTaskListChangeId = -1;
 
     // Only valid until the launcher state changes to NORMAL
-    private int mRunningTaskId = -1;
-    private boolean mRunningTaskTileHidden;
+    protected int mRunningTaskId = -1;
+    protected boolean mRunningTaskTileHidden;
     private Task mTmpRunningTask;
 
     private boolean mRunningTaskIconScaledDown = false;
@@ -287,9 +288,9 @@
     private LayoutTransition mLayoutTransition;
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mContentAlpha = 1;
+    protected float mContentAlpha = 1;
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mFullscreenProgress = 0;
+    protected float mFullscreenProgress = 0;
 
     // Keeps track of task id whose visual state should not be reset
     private int mIgnoreResetTaskId = -1;
@@ -304,6 +305,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) {
@@ -327,7 +331,6 @@
         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 */);
 
@@ -428,7 +431,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);
@@ -442,7 +445,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;
             }
@@ -527,35 +530,33 @@
         return true;
     }
 
-    private void applyLoadPlan(ArrayList<Task> tasks) {
+    protected void applyLoadPlan(ArrayList<Task> tasks) {
         if (mPendingAnimation != null) {
             mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks));
             return;
         }
 
         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) {
@@ -565,17 +566,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.
@@ -586,19 +593,31 @@
         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);
             }
         }
         if (mRunningTaskTileHidden) {
@@ -701,7 +720,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() {
@@ -712,15 +731,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
@@ -795,6 +815,10 @@
         return tv == null ? -1 : indexOfChild(tv);
     }
 
+    public int getTaskViewStartIndex() {
+        return mTaskViewStartIndex;
+    }
+
     /**
      * Reloads the view if anything in recents changed.
      */
@@ -822,7 +846,7 @@
      */
     public void onSwipeUpAnimationSuccess() {
         if (getRunningTaskView() != null) {
-            float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get()
+            float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() && mLiveTileOverlay != null
                     ? mLiveTileOverlay.cancelIconAnimation()
                     : 0f;
             animateUpRunningTaskIconScale(startProgress);
@@ -849,12 +873,14 @@
      * is called.  Also scrolls the view to this task.
      */
     public void showCurrentTask(int runningTaskId) {
-        if (getChildCount() == 0) {
+        if (getTaskView(runningTaskId) == null) {
+            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);
-            addView(mClearAllButton);
-
+            addView(taskView, mTaskViewStartIndex);
+            if (wasEmpty) {
+                addView(mClearAllButton);
+            }
             // The temporary running task is only used for the duration between the start of the
             // gesture and the task list is loaded and applied
             mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
@@ -917,14 +943,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);
             }
         }
     }
@@ -936,6 +961,10 @@
         }
     }
 
+    public boolean isTaskIconScaledDown(TaskView taskView) {
+        return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
+    }
+
     private void applyRunningTaskIconScale() {
         TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
@@ -1047,9 +1076,10 @@
         if (task != null) {
             ActivityManagerWrapper.getInstance().removeTask(task.key.id);
             if (shouldLog) {
+                ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
                 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                        onEndListener.logAction, Direction.UP, index,
-                        TaskUtils.getLaunchComponentKeyForTask(task.key));
+                        onEndListener.logAction, Direction.UP, index, componentKey);
+                mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
             }
         }
     }
@@ -1183,7 +1213,7 @@
 
         int count = getTaskViewCount();
         for (int i = 0; i < count; i++) {
-            addDismissedTaskAnimations(getChildAt(i), anim, duration);
+            addDismissedTaskAnimations(getTaskViewAt(i), anim, duration);
         }
 
         mPendingAnimation = pendingAnimation;
@@ -1191,7 +1221,7 @@
             if (onEndListener.isSuccess) {
                 // Remove all the task views now
                 ActivityManagerWrapper.getInstance().removeAllRecentTasks();
-                removeAllViews();
+                removeTasksViewsAndClearAllButton();
                 startHome();
             }
             mPendingAnimation = null;
@@ -1338,26 +1368,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) {
@@ -1491,7 +1545,7 @@
             throw new IllegalStateException("Another pending animation is still running");
         }
 
-        int count = getChildCount();
+        int count = getTaskViewCount();
         if (count == 0) {
             return new PendingAnimation(new AnimatorSet());
         }
@@ -1664,18 +1718,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();
     }
@@ -1688,6 +1762,9 @@
      * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
      */
     public float getScrollOffset() {
+        if (getRunningTaskIndex() == -1) {
+            return 0;
+        }
         int startScroll = getScrollForPage(getRunningTaskIndex());
         int offsetX = startScroll - getScrollX();
         offsetX *= getScaleX();
@@ -1724,7 +1801,7 @@
         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);
         }
     }
 
@@ -1734,4 +1811,35 @@
             updateEnabledOverlays();
         }
     }
+
+    public int getLeftGestureMargin() {
+        final WindowInsets insets = getRootWindowInsets();
+        return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
+    }
+
+    public int getRightGestureMargin() {
+        final WindowInsets insets = getRootWindowInsets();
+        return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
+    }
+
+    @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 d55a520..044292a 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,28 @@
 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 +104,7 @@
 
     private boolean mOverlayEnabled;
     private boolean mRotated;
+    private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -147,6 +152,11 @@
             mPaint.setShader(null);
             mOverlay.reset();
         }
+
+        if (mOverviewScreenshotActionsPlugin != null) {
+            mOverviewScreenshotActionsPlugin
+                .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+        }
         updateThumbnailPaintFilter();
     }
 
@@ -211,6 +221,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;
@@ -410,4 +447,11 @@
 
         return new ColorMatrixColorFilter(COLOR_MATRIX);
     }
+
+    public Bitmap getThumbnail() {
+        if (mThumbnailData == null) {
+            return null;
+        }
+        return mThumbnailData.thumbnail;
+    }
 }
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 b26fdce..51802df 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;
@@ -192,9 +191,6 @@
         super(context, attrs, defStyleAttr);
         mActivity = BaseDraggingActivity.fromContext(context);
         setOnClickListener((view) -> {
-            if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "TaskView onClick");
-            }
             if (getTask() == null) {
                 return;
             }
@@ -291,9 +287,6 @@
 
     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTask");
-        }
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (isRunningTask()) {
                 getRecentsView().finishRecentsAnimation(false /* toRecents */,
@@ -308,9 +301,6 @@
 
     private void launchTaskInternal(boolean animate, boolean freezeTaskList,
             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTaskInternal");
-        }
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
@@ -827,8 +817,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-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..2d1418e
--- /dev/null
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recent_task_option_split_screen" msgid="5353188922202653570">"Split screen"</string>
+    <string name="recent_task_option_pin" msgid="7929860679018978258">"Pin"</string>
+    <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"Overview"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
+    <string name="accessibility_close_task" msgid="5354563209433803643">"Close"</string>
+    <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
+    <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
+    <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
+    <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
+    <string name="title_app_suggestions" msgid="4185902664111965088">"App suggestions"</string>
+    <string name="all_apps_label" msgid="8542784161730910663">"All apps"</string>
+    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Your predicted apps"</string>
+</resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..bb186db
--- /dev/null
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="recent_task_option_split_screen" msgid="5353188922202653570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎Split screen‎‏‎‎‏‎"</string>
+    <string name="recent_task_option_pin" msgid="7929860679018978258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‎‎Pin‎‏‎‎‏‎"</string>
+    <string name="recent_task_option_freeform" msgid="48863056265284071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎Freeform‎‏‎‎‏‎"</string>
+    <string name="accessibility_desc_recent_apps" msgid="1444379410873162882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‎Overview‎‏‎‎‏‎"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‏‎No recent items‎‏‎‎‏‎"</string>
+    <string name="accessibility_close_task" msgid="5354563209433803643">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎Close‎‏‎‎‏‎"</string>
+    <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎App usage settings‎‏‎‎‏‎"</string>
+    <string name="recents_clear_all" msgid="5328176793634888831">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎Clear all‎‏‎‎‏‎"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎Recent apps‎‏‎‎‏‎"</string>
+    <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>‎‏‎‎‏‏‏‎, ‎‏‎‎‏‏‎<xliff:g id="REMAINING_TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‏‎‏‎‎‎‎&lt; 1 minute‎‏‎‎‏‎"</string>
+    <string name="time_left_for_app" msgid="3111996412933644358">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ left today‎‏‎‎‏‎"</string>
+    <string name="title_app_suggestions" msgid="4185902664111965088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‎‎‎App suggestions‎‏‎‎‏‎"</string>
+    <string name="all_apps_label" msgid="8542784161730910663">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‏‎All apps‎‏‎‎‏‎"</string>
+    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎Your predicted apps‎‏‎‎‏‎"</string>
+</resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 5394f49..01dcff2 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -33,5 +33,5 @@
     <string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
     <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
     <string name="all_apps_label" msgid="8542784161730910663">"Toutes les applications"</string>
-    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos applications prévues"</string>
+    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Applications prévues pour vous"</string>
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 0467af4..387d509 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"बंद करें"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सभी ऐप्लिकेशन बंद करें"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए एेप्लिकेशन"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 मिनट"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 1ca558a..cccece7 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"बंद"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अ‍ॅप वापर सेटिंग्ज"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सर्व साफ करा"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अ‍ॅप्स"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"१मिहून कमी"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g>शिल्लक आहे"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 5c4d6d8..292eaaa 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -21,8 +21,6 @@
     <!-- Activity which blocks home gesture -->
     <string name="gesture_blocking_activity" translatable="false"></string>
 
-    <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
-
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
 
     <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</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 78f6ffa..a8e2956 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 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;
 
@@ -24,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
+import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 
@@ -66,7 +67,7 @@
 
     /**
      * Called on the UI thread when the animation targets are received. The implementation must
-     * call {@link AnimationResult#setAnimation(AnimatorSet)} with the target animation to be run.
+     * call {@link AnimationResult#setAnimation} with the target animation to be run.
      */
     @UiThread
     public abstract void onCreateAnimation(
@@ -110,7 +111,7 @@
         }
 
         @UiThread
-        public void setAnimation(AnimatorSet animation) {
+        public void setAnimation(AnimatorSet animation, Context context) {
             if (mInitialized) {
                 throw new IllegalStateException("Animation already initialized");
             }
@@ -134,7 +135,7 @@
 
                 // Because t=0 has the app icon in its original spot, we can skip the
                 // first frame and have the same movement one frame earlier.
-                mAnimator.setCurrentPlayTime(SINGLE_FRAME_MS);
+                mAnimator.setCurrentPlayTime(getSingleFrameMs(context));
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index c5c5323..b9ce1ce 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -85,7 +85,7 @@
 
         register();
 
-        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
         context.startActivity(addToIntent(new Intent((intent))), options);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index b60a017..a91410c 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;
@@ -69,6 +72,7 @@
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -80,9 +84,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.
@@ -150,6 +151,8 @@
 
     private RemoteAnimationProvider mRemoteAnimationProvider;
 
+    private final ShelfPeekAnim mShelfPeekAnim;
+
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
         public void onAnimationStart(Animator animation) {
@@ -177,6 +180,12 @@
 
         mLauncher.addOnDeviceProfileChangeListener(this);
         registerRemoteAnimations();
+
+        mShelfPeekAnim = new ShelfPeekAnim(mLauncher);
+    }
+
+    public ShelfPeekAnim getShelfPeekAnim() {
+        return mShelfPeekAnim;
     }
 
     @Override
@@ -219,7 +228,7 @@
                         anim.addListener(mForceInvisibleListener);
                     }
 
-                    result.setAnimation(anim);
+                    result.setAnimation(anim, mLauncher);
                 }
             };
 
@@ -822,7 +831,7 @@
             }
 
             mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
-            result.setAnimation(anim);
+            result.setAnimation(anim, mLauncher);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index f0204b9..174e49b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -19,16 +19,20 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 
 import android.util.FloatProperty;
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
@@ -36,8 +40,7 @@
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
-
-import androidx.annotation.NonNull;
+import com.android.launcher3.graphics.OverviewScrim;
 
 /**
  * State handler for recents view. Manages UI changes and animations for recents view based off the
@@ -67,6 +70,8 @@
         mRecentsView.setTranslationX(translationX);
         mRecentsView.setTranslationY(scaleAndTranslation.translationY);
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
+        OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
+        SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
     }
 
     @Override
@@ -110,6 +115,9 @@
                 translateYInterpolator);
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+        OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
+        setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
+                builder.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
new file mode 100644
index 0000000..853a1c6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -0,0 +1,49 @@
+/*
+ * 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.content.Context;
+import android.provider.DeviceConfig;
+import com.android.launcher3.config.BaseFlags.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 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 97cd38a..0790cf6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -28,13 +28,16 @@
 import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
 import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.app.Activity;
+import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.util.Base64;
@@ -56,6 +59,7 @@
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
@@ -244,4 +248,15 @@
         }
         return new ScaleAndTranslation(1.1f, 0f, 0f);
     }
+
+    public static Person[] getPersons(ShortcutInfo si) {
+        Person[] persons = si.getPersons();
+        return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
+    }
+
+    /** Closes system windows. */
+    public static void closeSystemWindows() {
+        ActivityManagerWrapper.getInstance()
+            .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
index 910fa0d..7beb9db 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java
@@ -14,16 +14,17 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.os.Looper;
 
-import com.android.launcher3.LauncherModel;
 import com.android.systemui.shared.plugins.PluginInitializer;
 
 public class PluginInitializerImpl implements PluginInitializer {
     @Override
     public Looper getBgLooper() {
-        return LauncherModel.getWorkerLooper();
+        return MODEL_EXECUTOR.getLooper();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 0605953..39b0f8d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -11,7 +11,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.quickstep.RecentsModel;
@@ -24,7 +24,7 @@
     private static final String TAG = "LandscapeEdgeSwipeCtrl";
 
     public LandscapeEdgeSwipeController(Launcher l) {
-        super(l, SwipeDetector.HORIZONTAL);
+        super(l, SingleAxisSwipeDetector.HORIZONTAL);
     }
 
     @Override
@@ -46,7 +46,7 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
         return LauncherLogProto.ContainerType.NAVBAR;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 6030cea..db6a40f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -51,7 +51,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -63,7 +62,7 @@
     /**
      * The progress at which all apps content will be fully visible when swiping up from overview.
      */
-    private static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
+    protected static final float ALL_APPS_CONTENT_FADE_THRESHOLD = 0.08f;
 
     /**
      * The progress at which recents will begin fading out when swiping up from overview.
@@ -80,7 +79,7 @@
     private boolean mFinishFastOnSecondTouch;
 
     public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
-        super(l, SwipeDetector.VERTICAL);
+        super(l, SingleAxisSwipeDetector.VERTICAL);
         mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
         mAllowDragToOverview = allowDragToOverview;
     }
@@ -148,8 +147,8 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
-        return ContainerType.HOTSEAT;
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
     }
 
     private AnimatorSetBuilder getNormalToOverviewAnimation() {
@@ -178,6 +177,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) {
@@ -188,6 +201,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;
     }
@@ -296,9 +313,13 @@
      * @return true if the event is over the hotseat
      */
     static boolean isTouchOverHotseat(Launcher launcher, MotionEvent ev) {
+        return (ev.getY() >= getHotseatTop(launcher));
+    }
+
+    public static int getHotseatTop(Launcher launcher) {
         DeviceProfile dp = launcher.getDeviceProfile();
         int hotseatHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
-        return (ev.getY() >= (launcher.getDragLayer().getHeight() - hotseatHeight));
+        return launcher.getDragLayer().getHeight() - hotseatHeight;
     }
 
     private static class InterpolatorWrapper implements Interpolator {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fee1820..11a8043 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -17,17 +17,25 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+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;
+import android.view.Window;
+import android.view.WindowManager;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.touch.TouchEventTranslator;
+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.TouchController;
 import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -36,18 +44,29 @@
 
 /**
  * TouchController for handling touch events that get sent to the StatusBar. Once the
- * Once the event delta y passes the touch slop, the events start getting forwarded.
+ * Once the event delta mDownY passes the touch slop, the events start getting forwarded.
  * All events are offset by initial Y value of the pointer.
  */
 public class StatusBarTouchController implements TouchController {
 
     private static final String TAG = "StatusBarController";
 
+    /**
+     * Window flag: Enable touches to slide out of a window into neighboring
+     * windows in mid-gesture instead of being captured for the duration of
+     * the gesture.
+     *
+     * This flag changes the behavior of touch focus for this window only.
+     * Touches can slide out of the window but they cannot necessarily slide
+     * back in (unless the other window with touch focus permits it).
+     */
+    private static final int FLAG_SLIPPERY = 0x20000000;
+
     protected final Launcher mLauncher;
-    protected final TouchEventTranslator mTranslator;
     private final float mTouchSlop;
     private ISystemUiProxy mSysUiProxy;
     private int mLastAction;
+    private final SparseArray<PointF> mDownEvents;
 
     /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/
     private boolean mCanIntercept;
@@ -56,7 +75,7 @@
         mLauncher = l;
         // Guard against TAPs by increasing the touch slop.
         mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
-        mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev));
+        mDownEvents = new SparseArray<>();
     }
 
     @Override
@@ -64,7 +83,6 @@
         writer.println(prefix + "mCanIntercept:" + mCanIntercept);
         writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
         writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
-
     }
 
     private void dispatchTouchEvent(MotionEvent ev) {
@@ -81,26 +99,31 @@
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        int idx = ev.getActionIndex();
+        int pid = ev.getPointerId(idx);
         if (action == ACTION_DOWN) {
             mCanIntercept = canInterceptTouch(ev);
             if (!mCanIntercept) {
                 return false;
             }
-            mTranslator.reset();
-            mTranslator.setDownParameters(0, ev);
+            mDownEvents.put(pid, new PointF(ev.getX(), ev.getY()));
         } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-            // Check!! should only set it only when threshold is not entered.
-            mTranslator.setDownParameters(ev.getActionIndex(), ev);
+           // Check!! should only set it only when threshold is not entered.
+           mDownEvents.put(pid, new PointF(ev.getX(idx), ev.getY(idx)));
         }
         if (!mCanIntercept) {
             return false;
         }
         if (action == ACTION_MOVE) {
-            float dy = ev.getY() - mTranslator.getDownY();
-            float dx = ev.getX() - mTranslator.getDownX();
-            if (dy > mTouchSlop && dy > Math.abs(dx)) {
-                mTranslator.dispatchDownEvents(ev);
-                mTranslator.processMotionEvent(ev);
+            float dy = ev.getY(idx) - mDownEvents.get(pid).y;
+            float dx = ev.getX(idx) - mDownEvents.get(pid).x;
+            // Currently input dispatcher will not do touch transfer if there are more than
+            // one touch pointer. Hence, even if slope passed, only set the slippery flag
+            // when there is single touch event. (context: InputDispatcher.cpp line 1445)
+            if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
+                ev.setAction(ACTION_DOWN);
+                dispatchTouchEvent(ev);
+                setWindowSlippery(true);
                 return true;
             }
             if (Math.abs(dx) > mTouchSlop) {
@@ -110,13 +133,31 @@
         return false;
     }
 
-
     @Override
     public final boolean onControllerTouchEvent(MotionEvent ev) {
-        mTranslator.processMotionEvent(ev);
+        int action = ev.getAction();
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            dispatchTouchEvent(ev);
+            mLauncher.getUserEventDispatcher().logActionOnContainer(action == ACTION_UP ?
+                    Touch.FLING : Touch.SWIPE, Direction.DOWN, ContainerType.WORKSPACE,
+                    mLauncher.getWorkspace().getCurrentPage());
+            setWindowSlippery(false);
+            return true;
+        }
         return true;
     }
 
+    private void setWindowSlippery(boolean enable) {
+        Window w = mLauncher.getWindow();
+        WindowManager.LayoutParams wlp = w.getAttributes();
+        if (enable) {
+            wlp.flags |= FLAG_SLIPPERY;
+        } else {
+            wlp.flags &= ~FLAG_SLIPPERY;
+        }
+        w.setAttributes(wlp);
+    }
+
     private boolean canInterceptTouch(MotionEvent ev) {
         if (!mLauncher.isInState(LauncherState.NORMAL) ||
                 AbstractFloatingView.getTopOpenViewWithType(mLauncher,
@@ -132,4 +173,4 @@
         mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
         return mSysUiProxy != null;
     }
-}
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index cd2c9cb..110cc23 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.BiPredicate;
@@ -109,16 +110,6 @@
 
     interface AnimationFactory {
 
-        enum ShelfAnimState {
-            HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
-            ShelfAnimState(boolean shouldPreformHaptic) {
-                this.shouldPreformHaptic = shouldPreformHaptic;
-            }
-
-            public final boolean shouldPreformHaptic;
-        }
-
         default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
 
         void createActivityController(long transitionLength);
@@ -127,8 +118,8 @@
 
         default void onTransitionCancelled() { }
 
-        default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
-                long duration) { }
+        default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
+                Interpolator interpolator, long duration) { }
 
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
@@ -152,5 +143,15 @@
         default void playAtomicAnimation(float velocity) {
             // No-op
         }
+
+        static RectF getDefaultWindowTargetRect(DeviceProfile dp) {
+            final int halfIconSize = dp.iconSizePx / 2;
+            final float targetCenterX = dp.availableWidthPx / 2f;
+            final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
+            // Fallback to animate to center of screen.
+            return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
+                    targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+        }
+
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
index c840132..1ac7ed4 100644
--- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -157,6 +157,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/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0738aff..93a6127 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,6 +22,7 @@
 
 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_ASSIST_GESTURE_CONSTRAINED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 
 import android.content.BroadcastReceiver;
@@ -29,11 +30,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.util.SparseIntArray;
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * Class to keep track of the current overview component based off user preferences and app updates
@@ -53,22 +58,41 @@
         }
     };
     private final Context mContext;
-    private final ComponentName mMyHomeComponent;
+    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 Intent mOverviewIntent;
-    private Intent mHomeIntent;
     private int mSystemUiStateFlags;
     private boolean mIsHomeAndOverviewSame;
+    private boolean mIsDefaultHome;
 
     public OverviewComponentObserver(Context context) {
         mContext = context;
 
-        Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
+        mCurrentHomeIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(mContext.getPackageName());
-        ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
-        mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
+        ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
+        ComponentName myHomeComponent =
+                new ComponentName(context.getPackageName(), info.activityInfo.name);
+        mMyHomeIntent.setComponent(myHomeComponent);
+        mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+
+        ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
+        mFallbackIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .setComponent(fallbackComponent)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        try {
+            ActivityInfo fallbackInfo = context.getPackageManager().getActivityInfo(
+                    mFallbackIntent.getComponent(), 0 /* flags */);
+            mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
+        } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
 
         mContext.registerReceiver(mUserPreferenceChangeReceiver,
                 new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
@@ -84,6 +108,10 @@
         }
     }
 
+    public boolean assistantGestureIsConstrained() {
+        return (mSystemUiStateFlags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+    }
+
     /**
      * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
      * component.
@@ -92,17 +120,22 @@
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
 
-        final String overviewIntentCategory;
-        ComponentName overviewComponent;
-        mHomeIntent = null;
+        mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
 
-        if ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 &&
-                (defaultHome == null || mMyHomeComponent.equals(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 ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            overviewComponent = mMyHomeComponent;
             mActivityControlHelper = new LauncherActivityControllerHelper();
             mIsHomeAndOverviewSame = true;
-            overviewIntentCategory = Intent.CATEGORY_HOME;
+            mOverviewIntent = mMyHomeIntent;
+            mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
 
             if (mUpdateRegisteredPackage != null) {
                 // Remove any update listener as we don't care about other packages.
@@ -111,14 +144,12 @@
             }
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
-            overviewComponent = new ComponentName(mContext, RecentsActivity.class);
+
             mActivityControlHelper = new FallbackActivityControllerHelper();
             mIsHomeAndOverviewSame = false;
-            overviewIntentCategory = Intent.CATEGORY_DEFAULT;
+            mOverviewIntent = mFallbackIntent;
+            mCurrentHomeIntent.setComponent(defaultHome);
 
-            mHomeIntent = new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_HOME)
-                    .setComponent(defaultHome);
             // User's default home app can change as a result of package updates of this app (such
             // as uninstalling the app or removing the "Launcher" feature in an update).
             // Listen for package updates of this app (and remove any previously attached
@@ -138,14 +169,6 @@
                         ACTION_PACKAGE_REMOVED));
             }
         }
-
-        mOverviewIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(overviewIntentCategory)
-                .setComponent(overviewComponent)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        if (mHomeIntent == null) {
-            mHomeIntent = mOverviewIntent;
-        }
     }
 
     /**
@@ -161,6 +184,32 @@
     }
 
     /**
+     * @return {@code true} if the overview component is able to handle the configuration changes.
+     */
+    boolean canHandleConfigChanges(ComponentName component, int changes) {
+        final int orientationChange =
+                ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_SCREEN_SIZE;
+        if ((changes & orientationChange) == orientationChange) {
+            // This is just an approximate guess for simple orientation change because the changes
+            // may contain non-public bits (e.g. window configuration).
+            return true;
+        }
+
+        int configMask = mConfigChangesMap.get(component.hashCode());
+        return configMask != 0 && (~configMask & changes) == 0;
+    }
+
+    /**
+     * Get the intent for overview activity. It is used when lockscreen is shown and home was died
+     * in background, we still want to restart the one that will be used after unlock.
+     *
+     * @return the overview intent
+     */
+    Intent getOverviewIntentIgnoreSysUiState() {
+        return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
+    }
+
+    /**
      * Get the current intent for going to the overview activity.
      *
      * @return the overview intent
@@ -173,7 +222,7 @@
      * Get the current intent for going to the home activity.
      */
     public Intent getHomeIntent() {
-        return mHomeIntent;
+        return mCurrentHomeIntent;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 78b48d7..858c3b6 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,20 +15,21 @@
  */
 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.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
-import androidx.annotation.WorkerThread;
-
 /**
  * Sets alpha for the back button
  */
@@ -62,7 +63,7 @@
         // 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(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
+        mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
 
         onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
                 .addModeChangeListener(this::onNavigationModeChanged));
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 7bfa9a0..befeee0 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.UserManager;
@@ -23,29 +22,17 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
-import com.android.launcher3.Utilities;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
 public class QuickstepProcessInitializer extends MainProcessInitializer {
 
     private static final String TAG = "QuickstepProcessInitializer";
-    private static final int HEAP_LIMIT_MB = 250;
 
     public QuickstepProcessInitializer(Context context) { }
 
     @Override
     protected void init(Context context) {
-        if (Utilities.IS_DEBUG_DEVICE) {
-            try {
-                // Trigger a heap dump if the PSS reaches beyond the target heap limit
-                final ActivityManager am = context.getSystemService(ActivityManager.class);
-                am.setWatchHeapLimit(HEAP_LIMIT_MB * 1024 * 1024);
-            } catch (SecurityException e) {
-                // Do nothing
-            }
-        }
-
         // Workaround for b/120550382, an external app can cause the launcher process to start for
         // a work profile user which we do not support. Disable the application immediately when we
         // detect this to be the case.
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
deleted file mode 100644
index 8951363..0000000
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.quickstep;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import com.android.launcher3.testing.TestInformationHandler;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.quickstep.util.LayoutUtils;
-
-public class QuickstepTestInformationHandler extends TestInformationHandler {
-
-    public QuickstepTestInformationHandler(Context context) { }
-
-    @Override
-    public Bundle call(String method) {
-        final Bundle response = new Bundle();
-        switch (method) {
-            case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
-                final float swipeHeight =
-                        OverviewState.getDefaultSwipeHeight(mDeviceProfile);
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
-                return response;
-            }
-
-            case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: {
-                final float swipeHeight =
-                        LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile);
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
-                return response;
-            }
-        }
-
-        return super.call(method);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index f27ba85..10f9feb 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,20 +16,22 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
-import android.content.Context;
 import android.os.Build;
 import android.os.Process;
 import android.util.SparseBooleanArray;
-import com.android.launcher3.MainThreadExecutor;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.util.LooperExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
-import com.android.systemui.shared.system.RecentTaskInfoCompat;
-import com.android.systemui.shared.system.TaskDescriptionCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -42,8 +44,8 @@
 public class RecentTasksList extends TaskStackChangeListener {
 
     private final KeyguardManagerCompat mKeyguardManager;
-    private final MainThreadExecutor mMainThreadExecutor;
-    private final BackgroundExecutor mBgThreadExecutor;
+    private final LooperExecutor mMainThreadExecutor;
+    private final ActivityManagerWrapper mActivityManagerWrapper;
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
@@ -54,12 +56,13 @@
 
     ArrayList<Task> mTasks = new ArrayList<>();
 
-    public RecentTasksList(Context context) {
-        mMainThreadExecutor = new MainThreadExecutor();
-        mBgThreadExecutor = BackgroundExecutor.get();
-        mKeyguardManager = new KeyguardManagerCompat(context);
+    public RecentTasksList(LooperExecutor mainThreadExecutor,
+            KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) {
+        mMainThreadExecutor = mainThreadExecutor;
+        mKeyguardManager = keyguardManager;
         mChangeId = 1;
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+        mActivityManagerWrapper = activityManagerWrapper;
+        mActivityManagerWrapper.registerTaskStackListener(this);
     }
 
     /**
@@ -67,7 +70,7 @@
      */
     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
         // Kick off task loading in the background
-        mBgThreadExecutor.submit(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
             mMainThreadExecutor.execute(() -> callback.accept(tasks));
         });
@@ -87,13 +90,14 @@
                 : () -> callback.accept(copyOf(mTasks));
 
         if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) {
-            // The list is up to date, callback with the same list
-            mMainThreadExecutor.execute(resultCallback);
+            // The list is up to date, send the callback on the next frame,
+            // so that requestID can be returned first.
+            mMainThreadExecutor.post(resultCallback);
             return requestLoadId;
         }
 
         // Kick off task loading in the background
-        mBgThreadExecutor.submit(() -> {
+        UI_HELPER_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
 
             mMainThreadExecutor.execute(() -> {
@@ -121,12 +125,7 @@
 
     @Override
     public void onTaskRemoved(int taskId) {
-        for (int i = mTasks.size() - 1; i >= 0; i--) {
-            if (mTasks.get(i).key.id == taskId) {
-                mTasks.remove(i);
-                return;
-            }
-        }
+        mTasks = loadTasksInBackground(Integer.MAX_VALUE, false);
     }
 
     @Override
@@ -143,12 +142,13 @@
     /**
      * Loads and creates a list of all the recent tasks.
      */
-    private ArrayList<Task> loadTasksInBackground(int numTasks,
+    @VisibleForTesting
+    ArrayList<Task> loadTasksInBackground(int numTasks,
             boolean loadKeysOnly) {
         int currentUserId = Process.myUserHandle().getIdentifier();
         ArrayList<Task> allTasks = new ArrayList<>();
         List<ActivityManager.RecentTaskInfo> rawTasks =
-                ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId);
+                mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId);
         // The raw tasks are given in most-recent to least-recent order, we need to reverse it
         Collections.reverse(rawTasks);
 
@@ -166,15 +166,11 @@
         int taskCount = rawTasks.size();
         for (int i = 0; i < taskCount; i++) {
             ActivityManager.RecentTaskInfo rawTask = rawTasks.get(i);
-            RecentTaskInfoCompat t = new RecentTaskInfoCompat(rawTask);
             Task.TaskKey taskKey = new Task.TaskKey(rawTask);
             Task task;
             if (!loadKeysOnly) {
-                ActivityManager.TaskDescription rawTd = t.getTaskDescription();
-                TaskDescriptionCompat td = new TaskDescriptionCompat(rawTd);
-                boolean isLocked = tmpLockedUsers.get(t.getUserId());
-                task = new Task(taskKey, td.getPrimaryColor(), td.getBackgroundColor(),
-                        t.supportsSplitScreenMultiWindow(), isLocked, rawTd, t.getTopActivity());
+                boolean isLocked = tmpLockedUsers.get(taskKey.userId);
+                task = Task.from(taskKey, rawTask, isLocked);
             } else {
                 task = new Task(taskKey);
             }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 0822e61..4d1d9ef 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -15,6 +15,8 @@
  */
 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;
@@ -22,7 +24,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 
@@ -68,7 +69,7 @@
             Context context, Handler handler, long duration) {
         register();
 
-        Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+        Bundle options = animProvider.toActivityOptions(handler, duration, context).toBundle();
         context.startActivity(intent, options);
     }
 
@@ -92,14 +93,10 @@
     private static class Scheduler implements Runnable {
 
         private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
-        private MainThreadExecutor mMainThreadExecutor;
 
         public synchronized void schedule(RecentsActivityTracker tracker) {
             mPendingTracker = new WeakReference<>(tracker);
-            if (mMainThreadExecutor == null) {
-                mMainThreadExecutor = new MainThreadExecutor();
-            }
-            mMainThreadExecutor.execute(this);
+            MAIN_EXECUTOR.execute(this);
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 9f12484..2e59ed5 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,27 +15,31 @@
  */
 package com.android.quickstep;
 
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.os.Build;
-import android.os.Bundle;
-import android.os.HandlerThread;
+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;
-import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
@@ -52,7 +56,7 @@
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
-            new MainThreadInitializedObject<>(c -> new RecentsModel(c));
+            new MainThreadInitializedObject<>(RecentsModel::new);
 
     private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
     private final Context mContext;
@@ -65,13 +69,14 @@
 
     private RecentsModel(Context context) {
         mContext = context;
-        HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",
-                Process.THREAD_PRIORITY_BACKGROUND);
-        loaderThread.start();
-        mTaskList = new RecentTasksList(context);
-        mIconCache = new TaskIconCache(context, loaderThread.getLooper());
-        mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());
+        Looper looper =
+                createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND);
+        mTaskList = new RecentTasksList(MAIN_EXECUTOR,
+                new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
+        mIconCache = new TaskIconCache(context, looper);
+        mThumbnailCache = new TaskThumbnailCache(context, looper);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
+        setupPackageListener();
     }
 
     public TaskIconCache getIconCache() {
@@ -170,6 +175,7 @@
     public void onTaskRemoved(int taskId) {
         Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
         mThumbnailCache.remove(dummyKey);
+        mIconCache.onTaskRemoved(dummyKey);
     }
 
     public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
@@ -204,6 +210,21 @@
         }
     }
 
+    private void setupPackageListener() {
+        LauncherAppsCompat.getInstance(mContext)
+                .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() {
+                    @Override
+                    public void onPackageRemoved(String packageName, UserHandle user) {
+                        mIconCache.invalidatePackage(packageName);
+                    }
+
+                    @Override
+                    public void onPackageChanged(String packageName, UserHandle user) {
+                        mIconCache.invalidatePackage(packageName);
+                    }
+                });
+    }
+
     public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {
         mThumbnailChangeListeners.add(listener);
     }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 07af9b3..289a129 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -27,26 +28,25 @@
 import android.util.LruCache;
 import android.view.accessibility.AccessibilityManager;
 
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.HandlerRunnable;
-import com.android.launcher3.uioverrides.RecentsUiFactory;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
+import java.util.Map;
 import java.util.function.Consumer;
 
 /**
  * Manages the caching of task icons and related data.
- * TODO: This class should later be merged into IconCache.
+ * TODO(b/138944598): This class should later be merged into IconCache.
  */
 public class TaskIconCache {
 
     private final Handler mBackgroundHandler;
-    private final MainThreadExecutor mMainThreadExecutor;
     private final AccessibilityManager mAccessibilityManager;
 
     private final NormalizedIconLoader mIconLoader;
@@ -67,7 +67,6 @@
 
     public TaskIconCache(Context context, Looper backgroundLooper) {
         mBackgroundHandler = new Handler(backgroundLooper);
-        mMainThreadExecutor = new MainThreadExecutor();
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
 
         Resources res = context.getResources();
@@ -103,7 +102,7 @@
                     // We don't call back to the provided callback in this case
                     return;
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     task.icon = icon;
                     task.titleDescription = contentDescription;
                     callback.accept(task);
@@ -149,6 +148,21 @@
         return label;
     }
 
+
+    void onTaskRemoved(TaskKey taskKey) {
+        mIconCache.remove(taskKey);
+    }
+
+    void invalidatePackage(String packageName) {
+        // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level
+        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+        for (ComponentName cn : activityInfoCache.keySet()) {
+            if (cn.getPackageName().equals(packageName)) {
+                mActivityInfoCache.remove(cn);
+            }
+        }
+    }
+
     public static abstract class IconLoadRequest extends HandlerRunnable {
         IconLoadRequest(Handler handler) {
             super(handler, null);
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 57c5a27..3b50c26 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,12 +15,14 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
-import com.android.launcher3.MainThreadExecutor;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.cache.HandlerRunnable;
@@ -30,13 +32,13 @@
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+
 import java.util.ArrayList;
 import java.util.function.Consumer;
 
 public class TaskThumbnailCache {
 
     private final Handler mBackgroundHandler;
-    private final MainThreadExecutor mMainThreadExecutor;
 
     private final int mCacheSize;
     private final ThumbnailCache mCache;
@@ -94,7 +96,6 @@
 
     public TaskThumbnailCache(Context context, Looper backgroundLooper) {
         mBackgroundHandler = new Handler(backgroundLooper);
-        mMainThreadExecutor = new MainThreadExecutor();
         mHighResLoadingState = new HighResLoadingState(context);
 
         Resources res = context.getResources();
@@ -168,7 +169,7 @@
                     // We don't call back to the provided callback in this case
                     return;
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     mCache.put(key, thumbnail);
                     callback.accept(thumbnail);
                     onEnd();
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 2f411ef..8e5ed1a 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,30 +16,33 @@
 
 package com.android.quickstep.logging;
 
-import android.content.Context;
-import android.content.Intent;
-import android.stats.launcher.nano.LauncherExtension;
-import android.stats.launcher.nano.LauncherTarget;
-
 import static android.stats.launcher.nano.Launcher.ALLAPPS;
 import static android.stats.launcher.nano.Launcher.HOME;
 import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
 import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
+import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
 import static android.stats.launcher.nano.Launcher.BACKGROUND;
 import static android.stats.launcher.nano.Launcher.OVERVIEW;
 
+import android.content.Context;
+import android.content.Intent;
+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;
 
-import androidx.annotation.Nullable;
-
 /**
  * This method calls the StatsLog hidden method until they are made available public.
  *
@@ -51,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) { }
 
@@ -60,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);
     }
@@ -74,22 +82,154 @@
                 MessageNano.toByteArray(ext), true);
     }
 
+    @Override
+    public void logTaskDismiss(View v, ComponentKey componentKey) {
+        LauncherExtension ext = new LauncherExtension();
+        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
+        int srcState = OVERVIEW;
+        fillInLauncherExtension(v, ext);
+        StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */,
+                MessageNano.toByteArray(ext), true);
+    }
+
+    @Override
+    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) {
+        LauncherExtension ext = new LauncherExtension();
+        ext.srcTarget = new LauncherTarget[1];
+        int srcState = mStateProvider.getCurrentState();
+        fillInLauncherExtensionWithPageId(ext, pageId);
+        int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
+        StatsLogCompat.write(launcherAction, srcState, srcState,
+                MessageNano.toByteArray(ext), true);
+    }
+
     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/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
index 4a11601..9ca7f23 100644
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -20,10 +20,10 @@
 
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
 import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
 import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
 import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
 
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
new file mode 100644
index 0000000..552db1f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
+
+import android.annotation.TargetApi;
+import android.app.TaskInfo;
+import android.content.Intent;
+import android.os.Build;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskInfoCompat;
+
+/**
+ * Utility class for interacting with the Assistant.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public final class AssistantUtilities {
+
+    /** Returns true if an Assistant activity that is excluded from recents is running. */
+    public static boolean isExcludedAssistantRunning() {
+        return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask());
+    }
+
+    /** Returns true if the given task holds an Assistant activity that is excluded from recents. */
+    public static boolean isExcludedAssistant(TaskInfo info) {
+        return info != null
+            && TaskInfoCompat.getActivityType(info) == ACTIVITY_TYPE_ASSISTANT
+            && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+    }
+
+    private AssistantUtilities() {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
 
@@ -39,12 +39,27 @@
     @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
     private @interface MultiWindowStrategy {}
 
+    /**
+     * The height for the swipe up motion
+     */
+    public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+        float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+        if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+            swipeHeight -= dp.getInsets().bottom;
+        }
+        return swipeHeight;
+    }
+
     public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         float extraSpace;
         if (dp.isVerticalBarLayout()) {
             extraSpace = 0;
         } else {
-            extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+            Resources res = context.getResources();
+
+            extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+                    + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
         }
         calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
     }
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index a7e6d74..4503a43 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -17,6 +17,7 @@
 
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
+import android.content.Context;
 import android.os.Handler;
 
 import com.android.launcher3.LauncherAnimationRunner;
@@ -32,14 +33,14 @@
 
     AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
 
-    default ActivityOptions toActivityOptions(Handler handler, long duration) {
+    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));
+                result.setAnimation(createWindowAnimation(targetCompats), context);
             }
         };
         return ActivityOptionsCompat.makeRemoteAnimation(
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 63c8023..3320dae 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -35,15 +36,18 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
 
 /**
  * Scrim used for all-apps and shelf in Overview
@@ -74,6 +78,9 @@
     private int mMidAlpha;
     private float mMidProgress;
 
+    // The progress at which the drag handle starts moving up with the shelf.
+    private float mDragHandleProgress;
+
     private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
     private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
 
@@ -95,7 +102,7 @@
 
     public ShelfScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mMaxScrimAlpha = Math.round(OVERVIEW.getWorkspaceScrimAlpha(mLauncher) * 255);
+        mMaxScrimAlpha = Math.round(OVERVIEW.getOverviewScrimAlpha(mLauncher) * 255);
 
         mEndAlpha = Color.alpha(mEndScrim);
         mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
@@ -150,15 +157,18 @@
 
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
                 mMidProgress = 1;
+                mDragHandleProgress = 1;
                 mMidAlpha = 0;
             } else {
-                mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha);
+                Context context = getContext();
+                mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
+                mMidProgress =  OVERVIEW.getVerticalProgress(mLauncher);
                 Rect hotseatPadding = dp.getHotseatLayoutPadding();
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
-                        - hotseatPadding.bottom - hotseatPadding.top;
-                float arrowTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp));
-                mMidProgress =  1 - (arrowTop / mShiftRange);
-
+                        + hotseatPadding.bottom + hotseatPadding.top;
+                float dragHandleTop =
+                        Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
+                mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
             mTopOffset = dp.getInsets().top - mShelfOffset;
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
@@ -187,8 +197,12 @@
         if (mProgress >= 1) {
             mRemainingScreenColor = 0;
             mShelfColor = 0;
+            ShelfPeekAnim shelfPeekAnim = ((QuickstepAppTransitionManagerImpl)
+                    mLauncher.getAppTransitionManager()).getShelfPeekAnim();
+            LauncherState state = mLauncher.getStateManager().getState();
             if (mSysUINavigationMode == Mode.NO_BUTTON
-                    && mLauncher.getStateManager().getState() == BACKGROUND_APP) {
+                    && (state == BACKGROUND_APP || state == QUICK_SWITCH)
+                    && shelfPeekAnim.isPeeking()) {
                 // Show the shelf background when peeking during swipe up.
                 mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
             }
@@ -199,8 +213,6 @@
                     mProgress, mMidProgress, 1, mMidAlpha, 0, mBeforeMidProgressColorInterpolator));
             mShelfColor = setColorAlphaBound(mEndScrim, alpha);
         } else {
-            mDragHandleOffset += mShiftRange * (mMidProgress - mProgress);
-
             // Note that these ranges and interpolators are inverted because progress goes 1 to 0.
             int alpha = Math.round(
                     Utilities.mapToRange(mProgress, (float) 0, mMidProgress, (float) mEndAlpha,
@@ -212,6 +224,10 @@
                             (float) 0, LINEAR));
             mRemainingScreenColor = setColorAlphaBound(mScrimColor, remainingScrimAlpha);
         }
+
+        if (mProgress < mDragHandleProgress) {
+            mDragHandleOffset += mShiftRange * (mDragHandleProgress - mProgress);
+        }
     }
 
     @Override
diff --git a/quickstep/tests/OWNERS b/quickstep/tests/OWNERS
index 046d871..02e8ebc 100644
--- a/quickstep/tests/OWNERS
+++ b/quickstep/tests/OWNERS
@@ -1 +1,4 @@
 vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index d9fcf4d..7801775 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -88,7 +88,7 @@
      */
     @Test
     public void testPredictionExistsInAllApps() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         mLauncher.pressHome().switchToAllApps();
 
         // Dispatch an update
@@ -103,7 +103,7 @@
      */
     @Test
     public void testPredictionsDeferredUntilHome() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         sendPredictionUpdate(mSampleApp1, mSampleApp2);
         mLauncher.pressHome().switchToAllApps();
         waitForLauncherCondition("Predictions were not updated in loading state",
@@ -120,7 +120,7 @@
 
     @Test
     public void testPredictionsDisabled() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         sendPredictionUpdate();
         mLauncher.pressHome().switchToAllApps();
 
@@ -150,10 +150,10 @@
             List<AppTarget> targets = new ArrayList<>(activities.length);
             for (LauncherActivityInfo info : activities) {
                 ComponentName cn = info.getComponentName();
-                AppTarget target =
-                        new AppTarget.Builder(new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
-                            .setClassName(cn.getClassName())
-                            .build();
+                AppTarget target = new AppTarget.Builder(
+                        new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
+                        .setClassName(cn.getClassName())
+                        .build();
                 targets.add(target);
             }
             mCallback.onTargetsAvailable(targets);
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 0c5a6f5..a7c33a9 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -51,7 +51,7 @@
             mLauncher.pressHome();
             final DigitalWellBeingToast toast = getToast();
 
-            assertTrue("Toast is not visible", toast.hasLimit());
+            waitForLauncherCondition("Toast is not visible", launcher -> toast.hasLimit());
             assertEquals("Toast text: ", "5 minutes left today", toast.getText());
 
             // Unset time limit for app.
@@ -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..8c11c1c 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -78,7 +79,7 @@
         Context context = instrumentation.getContext();
         mDevice = UiDevice.getInstance(instrumentation);
         mDevice.setOrientationNatural();
-        mLauncher = new LauncherInstrumentation(instrumentation);
+        mLauncher = new LauncherInstrumentation();
 
         mOrderSensitiveRules = RuleChain.
                 outerRule(new NavigationModeSwitchRule(mLauncher)).
@@ -103,6 +104,11 @@
                 }
             }
         };
+        if (TestHelpers.isInLauncherProcess()) {
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
+        }
     }
 
     @NavigationModeSwitch(mode = THREE_BUTTON)
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 3b35c86..c2197ab 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -27,15 +27,16 @@
 import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.util.Log;
 
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 
-import org.junit.Assert;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -78,8 +79,17 @@
                 description.getAnnotation(NavigationModeSwitch.class) != null) {
             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
+                private void assertTrue(String message, boolean condition) {
+                    if(!condition) {
+                        final AssertionError assertionError = new AssertionError(message);
+                        FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
+                        throw assertionError;
+                    }
+                }
+
                 @Override
                 public void evaluate() throws Throwable {
+                    mLauncher.enableDebugTracing();
                     final Context context = getInstrumentation().getContext();
                     final int currentInteractionMode =
                             LauncherInstrumentation.getCurrentInteractionMode(context);
@@ -101,35 +111,55 @@
                         if (mode == THREE_BUTTON || mode == ALL) {
                             evaluateWithThreeButtons();
                         }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Exception", e);
+                        throw e;
                     } finally {
-                        setActiveOverlay(prevOverlayPkg, originalMode);
+                        assertTrue("Couldn't set overlay",
+                                setActiveOverlay(prevOverlayPkg, originalMode));
                     }
                 }
 
-                public void evaluateWithoutChangingSetting(Statement base) throws Throwable {
-                    base.evaluate();
-                }
-
                 private void evaluateWithThreeButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
-                    evaluateWithoutChangingSetting(base);
+                    if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
                 private void evaluateWithTwoButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
-                    base.evaluate();
+                    if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
                 private void evaluateWithZeroButtons() throws Throwable {
-                    setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
-                    base.evaluate();
+                    if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+                        base.evaluate();
+                    }
                 }
 
-                private void setActiveOverlay(String overlayPackage,
+                private boolean packageExists(String packageName) {
+                    try {
+                        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+                        if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
+                            return false;
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                        return false;
+                    }
+                    return true;
+                }
+
+                private boolean setActiveOverlay(String overlayPackage,
                         LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
+                    if (!packageExists(overlayPackage)) {
+                        Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
+                        return false;
+                    }
+
                     setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
                             overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
                     setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
@@ -151,10 +181,10 @@
                                 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));
-                        Assert.assertTrue("Navigation mode didn't change to " + expectedMode,
+                        assertTrue("Navigation mode didn't change to " + expectedMode,
                                 currentSysUiNavigationMode() == expectedMode);
                     }
 
@@ -162,7 +192,7 @@
                         if (mLauncher.getNavigationModel() == expectedMode) break;
                         Thread.sleep(100);
                     }
-                    Assert.assertTrue("Couldn't switch to " + overlayPackage,
+                    assertTrue("Couldn't switch to " + overlayPackage,
                             mLauncher.getNavigationModel() == expectedMode);
 
                     for (int i = 0; i != 100; ++i) {
@@ -170,9 +200,10 @@
                         Thread.sleep(100);
                     }
                     final String error = mLauncher.getNavigationModeMismatchError();
-                    Assert.assertTrue("Switching nav mode: " + error, error == null);
+                    assertTrue("Switching nav mode: " + error, error == null);
 
                     Thread.sleep(5000);
+                    return true;
                 }
 
                 private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
new file mode 100644
index 0000000..34eb7f8
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 junit.framework.TestCase.assertNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.util.LooperExecutor;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.KeyguardManagerCompat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+public class RecentTasksListTest {
+
+    private ActivityManagerWrapper mockActivityManagerWrapper;
+
+    // Class under test
+    private RecentTasksList mRecentTasksList;
+
+    @Before
+    public void setup() {
+        LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
+        KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class);
+        mockActivityManagerWrapper = mock(ActivityManagerWrapper.class);
+        mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat,
+                mockActivityManagerWrapper);
+    }
+
+    @Test
+    public void onTaskRemoved_reloadsAllTasks() {
+        mRecentTasksList.onTaskRemoved(0);
+        verify(mockActivityManagerWrapper, times(1))
+                .getRecentTasks(anyInt(), anyInt());
+    }
+
+    @Test
+    public void onTaskStackChanged_doesNotFetchTasks() {
+        mRecentTasksList.onTaskStackChanged();
+        verify(mockActivityManagerWrapper, times(0))
+                .getRecentTasks(anyInt(), anyInt());
+    }
+
+    @Test
+    public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
+        ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
+        when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(recentTaskInfo));
+
+        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true);
+
+        assertEquals(1, taskList.size());
+        assertNull(taskList.get(0).taskDescription.getLabel());
+    }
+
+    @Test
+    public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() {
+        String taskDescription = "Wheeee!";
+        ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
+        recentTaskInfo.taskDescription = new ActivityManager.TaskDescription(taskDescription);
+        when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(recentTaskInfo));
+
+        List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false);
+
+        assertEquals(1, taskList.size());
+        assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel());
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 9e3bf2f..8cd3bb6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.tapl.AllApps;
 import com.android.launcher3.tapl.AllAppsFromOverview;
 import com.android.launcher3.tapl.Background;
+import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
@@ -208,19 +209,24 @@
 
     @Test
     @NavigationModeSwitch
-//    @PortraitLandscape
+    @PortraitLandscape
     public void testBackground() throws Exception {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        final Background background = getAndAssertBackground();
+
+        assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
+        assertTrue("Launcher internal state didn't switch to Overview",
+                isInState(LauncherState.OVERVIEW));
+    }
+
+    private Background getAndAssertBackground() {
         final Background background = mLauncher.getBackground();
         assertNotNull("Launcher.getBackground() returned null", background);
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
                 isInBackground(launcher)));
-
-        assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(LauncherState.OVERVIEW));
+        return background;
     }
 
     @Test
@@ -238,4 +244,47 @@
         assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
         assertNotNull("getHome returned null", mLauncher.getWorkspace());
     }
+
+    @Test
+    @NavigationModeSwitch
+    @PortraitLandscape
+    public void testQuickSwitchFromApp() throws Exception {
+        startTestActivity(2);
+        startTestActivity(3);
+        startTestActivity(4);
+
+        Background background = getAndAssertBackground();
+        background.quickSwitchToPreviousApp();
+        assertTrue("The first app we should have quick switched to is not running",
+                isTestActivityRunning(3));
+
+        background = getAndAssertBackground();
+        background.quickSwitchToPreviousApp();
+        if (mLauncher.getNavigationModel() == NavigationModel.THREE_BUTTON) {
+            // 3-button mode toggles between 2 apps, rather than going back further.
+            assertTrue("Second quick switch should have returned to the first app.",
+                    isTestActivityRunning(4));
+        } else {
+            assertTrue("The second app we should have quick switched to is not running",
+                    isTestActivityRunning(2));
+        }
+        getAndAssertBackground();
+    }
+
+    private boolean isTestActivityRunning(int activityNumber) {
+        return mDevice.wait(Until.hasObject(By.pkg(getAppPackageName())
+                        .text("TestActivity" + activityNumber)),
+                DEFAULT_UI_TIMEOUT);
+    }
+
+    @Test
+    @NavigationModeSwitch
+    @PortraitLandscape
+    public void testQuickSwitchFromHome() throws Exception {
+        startTestActivity(2);
+        mLauncher.pressHome().quickSwitchToPreviousApp();
+        assertTrue("The most recent task is not running after quick switching from home",
+                isTestActivityRunning(2));
+        getAndAssertBackground();
+    }
 }
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-af/strings.xml b/res/values-af/strings.xml
index ca59afa..ba16dd3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/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">"Werk"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Program is nie beskikbaar nie"</string>
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-ar/strings.xml b/res/values-ar/strings.xml
index a80ecb0..2fbdae1 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/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-as/strings.xml b/res/values-as/strings.xml
index 2984603..221921f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/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-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 883003c..e6fe3bd 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/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">"Aplikacija nije instalirana."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index b4cf913..2ccd34f 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/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-bg/strings.xml b/res/values-bg/strings.xml
index 408f205..f7d1d0f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/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-bn/strings.xml b/res/values-bn/strings.xml
index 775885c..0a72632 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/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>
@@ -93,7 +92,7 @@
     <string name="title_change_settings" msgid="1376365968844349552">"সেটিংস পরিবর্তন করুন"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"বিজ্ঞপ্তির ডট দেখুন"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"হোম স্ক্রিনে আইকন যোগ করুন"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপ্লিকেশানগুলির জন্যে"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"নতুন অ্যাপের জন্য"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"অজানা"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"সরান"</string>
     <string name="abandoned_search" msgid="891119232568284442">"সার্চ"</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 c0859d3..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>
@@ -88,7 +87,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"Punts de notificació"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Activats"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Desactivats"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal que tingui accés a les notificacions"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal accés a les notificacions"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Per veure els punts de notificació, activa les notificacions de l\'aplicació <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Canvia la configuració"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Mostra els punts de notificació"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 692b57d..57c5072 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/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">"Práce"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikace není k dispozici."</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index dc17516..9097ffd 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/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">"Arbejde"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgængelig"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index a345bab..1602a53 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/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">"Arbeit"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App nicht verfügbar"</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-rCA/strings.xml b/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..7adc218
--- /dev/null
+++ b/res/values-en-rCA/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2008 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.
+*/
+ -->
+
+<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="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>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"Downloaded app disabled in Safe mode"</string>
+    <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
+    <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
+    <string name="home_screen" msgid="806512411299847073">"Home screen"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"Customised actions"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"Touch &amp; hold to pick up a widget."</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Double-tap &amp; hold to pick up a widget or use customised actions."</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
+    <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d wide by %2$d high"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Touch &amp; hold to place manually"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"Add automatically"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Search apps"</string>
+    <string name="all_apps_loading_message" msgid="5813968043155271636">"Loading apps…"</string>
+    <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"Search for more apps"</string>
+    <string name="label_application" msgid="8531721983832654978">"App"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
+    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Touch &amp; hold to pick up a shortcut."</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Double-tap &amp; hold to pick up a shortcut or use custom actions."</string>
+    <string name="out_of_space" msgid="4691004494942118364">"No more room on this Home screen."</string>
+    <string name="hotseat_out_of_space" msgid="7448809638125333693">"No more room in the Favourites tray"</string>
+    <string name="all_apps_button_label" msgid="8130441508702294465">"Apps list"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Personal apps list"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"Work apps list"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"Home"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"Remove"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Uninstall"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"App info"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"Install"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"install shortcuts"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"Allows an app to add shortcuts without user intervention."</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"read Home settings and shortcuts"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"Allows the app to read the settings and shortcuts in Home."</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"write Home settings and shortcuts"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"Allows the app to change the settings and shortcuts in Home."</string>
+    <string name="msg_no_phone_permission" msgid="9208659281529857371">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not allowed to make phone calls"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"Problem loading widget"</string>
+    <string name="gadget_setup_text" msgid="8274003207686040488">"Setup"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"This is a system app and can\'t be uninstalled."</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"Unnamed Folder"</string>
+    <string name="disabled_app_label" msgid="6673129024321402780">"Disabled <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
+      <item quantity="other"><xliff:g id="APP_NAME_2">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g> notifications</item>
+      <item quantity="one"><xliff:g id="APP_NAME_0">%1$s</xliff:g>, has <xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g> notification</item>
+    </plurals>
+    <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+    <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"Folder closed"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="widget_button_text" msgid="2880537293434387943">"Widgets"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpapers"</string>
+    <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Styles &amp; wallpapers"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Home settings"</string>
+    <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Disabled by your admin"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"Allow Home screen rotation"</string>
+    <string name="allow_rotation_desc" msgid="8662546029078692509">"When phone is rotated"</string>
+    <string name="notification_dots_title" msgid="9062440428204120317">"Notification dots"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"On"</string>
+    <string name="notification_dots_desc_off" msgid="1760796511504341095">"Off"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Notification access needed"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"To show Notification Dots, turn on app notifications for <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="title_change_settings" msgid="1376365968844349552">"Change settings"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"Show notification dots"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"Add icon to Home screen"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"For new apps"</string>
+    <string name="package_state_unknown" msgid="7592128424511031410">"Unknown"</string>
+    <string name="abandoned_clean_this" msgid="7610119707847920412">"Remove"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"Search"</string>
+    <string name="abandoned_promises_title" msgid="7096178467971716750">"This app is not installed"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually."</string>
+    <string name="app_downloading_title" msgid="8336702962104482644">"<xliff:g id="NAME">%1$s</xliff:g> downloading, <xliff:g id="PROGRESS">%2$s</xliff:g> complete"</string>
+    <string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> waiting to install"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"<xliff:g id="NAME">%1$s</xliff:g> widgets"</string>
+    <string name="widgets_list" msgid="796804551140113767">"Widgets list"</string>
+    <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"Add to Home screen"</string>
+    <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
+    <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
+    <string name="undo" msgid="4151576204245173321">"Undo"</string>
+    <string name="action_move" msgid="4339390619886385032">"Move item"</string>
+    <string name="move_to_empty_cell" msgid="2833711483015685619">"Move to row <xliff:g id="NUMBER_0">%1$s</xliff:g> column <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="move_to_position" msgid="6750008980455459790">"Move to position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+    <string name="move_to_hotseat_position" msgid="6295412897075147808">"Move to favourites position <xliff:g id="NUMBER">%1$s</xliff:g>"</string>
+    <string name="item_moved" msgid="4606538322571412879">"Item moved"</string>
+    <string name="add_to_folder" msgid="9040534766770853243">"Add to folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="add_to_folder_with_app" msgid="4534929978967147231">"Add to folder with <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="added_to_folder" msgid="4793259502305558003">"Item added to folder"</string>
+    <string name="create_folder_with" msgid="4050141361160214248">"Create folder with: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="folder_created" msgid="6409794597405184510">"Folder created"</string>
+    <string name="action_move_to_workspace" msgid="1603837886334246317">"Move to Home screen"</string>
+    <string name="action_resize" msgid="1802976324781771067">"Re-size"</string>
+    <string name="action_increase_width" msgid="8773715375078513326">"Increase width"</string>
+    <string name="action_increase_height" msgid="459390020612501122">"Increase height"</string>
+    <string name="action_decrease_width" msgid="1374549771083094654">"Decrease width"</string>
+    <string name="action_decrease_height" msgid="282377193880900022">"Decrease height"</string>
+    <string name="widget_resized" msgid="9130327887929620">"Widget re-sized to width <xliff:g id="NUMBER_0">%1$s</xliff:g> height <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="action_deep_shortcut" msgid="2864038805849372848">"Short cuts"</string>
+    <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Shortcuts and notifications"</string>
+    <string name="action_dismiss_notification" msgid="5909461085055959187">"Dismiss"</string>
+    <string name="notification_dismissed" msgid="6002233469409822874">"Notification dismissed"</string>
+    <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
+    <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
+    <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Find work apps here"</string>
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Each work app has a badge and is kept secure by your organisation. Move apps to your Home screen for easier access."</string>
+    <string name="work_mode_on_label" msgid="4781128097185272916">"Managed by your organisation"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications and apps are off"</string>
+    <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Close"</string>
+    <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Closed"</string>
+    <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
+</resources>
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-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..c741482
--- /dev/null
+++ b/res/values-en-rXC/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+* Copyright (C) 2008 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.
+*/
+ -->
+
+<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="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>
+    <string name="safemode_shortcut_error" msgid="9160126848219158407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‎Downloaded app disabled in Safe mode‎‏‎‎‏‎"</string>
+    <string name="safemode_widget_error" msgid="4863470563535682004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎Widgets disabled in Safe mode‎‏‎‎‏‎"</string>
+    <string name="shortcut_not_available" msgid="2536503539825726397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎Shortcut isn\'t available‎‏‎‎‏‎"</string>
+    <string name="home_screen" msgid="806512411299847073">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎Home screen‎‏‎‎‏‎"</string>
+    <string name="custom_actions" msgid="3747508247759093328">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎Custom actions‎‏‎‎‏‎"</string>
+    <string name="long_press_widget_to_add" msgid="7699152356777458215">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎Touch &amp; hold to pick up a widget.‎‏‎‎‏‎"</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‎Double-tap &amp; hold to pick up a widget or use custom actions.‎‏‎‎‏‎"</string>
+    <string name="widget_dims_format" msgid="2370757736025621599">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎%1$d × %2$d‎‏‎‎‏‎"</string>
+    <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎%1$d wide by %2$d high‎‏‎‎‏‎"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎Touch &amp; hold to place manually‎‏‎‎‏‎"</string>
+    <string name="place_automatically" msgid="8064208734425456485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎Add automatically‎‏‎‎‏‎"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎Search apps‎‏‎‎‏‎"</string>
+    <string name="all_apps_loading_message" msgid="5813968043155271636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎Loading apps…‎‏‎‎‏‎"</string>
+    <string name="all_apps_no_search_results" msgid="3200346862396363786">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‎‏‎‎No apps found matching \"‎‏‎‎‏‏‎<xliff:g id="QUERY">%1$s</xliff:g>‎‏‎‎‏‏‏‎\"‎‏‎‎‏‎"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎Search for more apps‎‏‎‎‏‎"</string>
+    <string name="label_application" msgid="8531721983832654978">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎App‎‏‎‎‏‎"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎Notifications‎‏‎‎‏‎"</string>
+    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎Touch &amp; hold to pick up a shortcut.‎‏‎‎‏‎"</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‎Double-tap &amp; hold to pick up a shortcut or use custom actions.‎‏‎‎‏‎"</string>
+    <string name="out_of_space" msgid="4691004494942118364">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎No more room on this Home screen.‎‏‎‎‏‎"</string>
+    <string name="hotseat_out_of_space" msgid="7448809638125333693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎No more room in the Favorites tray‎‏‎‎‏‎"</string>
+    <string name="all_apps_button_label" msgid="8130441508702294465">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎Apps list‎‏‎‎‏‎"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎Personal apps list‎‏‎‎‏‎"</string>
+    <string name="all_apps_button_work_label" msgid="7270707118948892488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎Work apps list‎‏‎‎‏‎"</string>
+    <string name="all_apps_home_button_label" msgid="252062713717058851">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‎Home‎‏‎‎‏‎"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎Remove‎‏‎‎‏‎"</string>
+    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎Uninstall‎‏‎‎‏‎"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎App info‎‏‎‎‏‎"</string>
+    <string name="install_drop_target_label" msgid="2539096853673231757">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎Install‎‏‎‎‏‎"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎install shortcuts‎‏‎‎‏‎"</string>
+    <string name="permdesc_install_shortcut" msgid="923466509822011139">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎Allows an app to add shortcuts without user intervention.‎‏‎‎‏‎"</string>
+    <string name="permlab_read_settings" msgid="1941457408239617576">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎read Home settings and shortcuts‎‏‎‎‏‎"</string>
+    <string name="permdesc_read_settings" msgid="5833423719057558387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎Allows the app to read the settings and shortcuts in Home.‎‏‎‎‏‎"</string>
+    <string name="permlab_write_settings" msgid="3574213698004620587">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎write Home settings and shortcuts‎‏‎‎‏‎"</string>
+    <string name="permdesc_write_settings" msgid="5440712911516509985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‎Allows the app to change the settings and shortcuts in Home.‎‏‎‎‏‎"</string>
+    <string name="msg_no_phone_permission" msgid="9208659281529857371">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is not allowed to make phone calls‎‏‎‎‏‎"</string>
+    <string name="gadget_error_text" msgid="6081085226050792095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎Problem loading widget‎‏‎‎‏‎"</string>
+    <string name="gadget_setup_text" msgid="8274003207686040488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎Setup‎‏‎‎‏‎"</string>
+    <string name="uninstall_system_app_text" msgid="4172046090762920660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎This is a system app and can\'t be uninstalled.‎‏‎‎‏‎"</string>
+    <string name="folder_hint_text" msgid="6617836969016293992">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎Unnamed Folder‎‏‎‎‏‎"</string>
+    <string name="disabled_app_label" msgid="6673129024321402780">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎Disabled ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <plurals name="dotted_app_label" formatted="false" msgid="5194538107138265416">
+      <item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME_2">%1$s</xliff:g>‎‏‎‎‏‏‏‎, has ‎‏‎‎‏‏‎<xliff:g id="NOTIFICATION_COUNT_3">%2$d</xliff:g>‎‏‎‎‏‏‏‎ notifications‎‏‎‎‏‎</item>
+      <item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎, has ‎‏‎‎‏‏‎<xliff:g id="NOTIFICATION_COUNT_1">%2$d</xliff:g>‎‏‎‎‏‏‏‎ notification‎‏‎‎‏‎</item>
+    </plurals>
+    <string name="default_scroll_format" msgid="7475544710230993317">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎Page %1$d of %2$d‎‏‎‎‏‎"</string>
+    <string name="workspace_scroll_format" msgid="8458889198184077399">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎Home screen %1$d of %2$d‎‏‎‎‏‎"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎New home screen page‎‏‎‎‏‎"</string>
+    <string name="folder_opened" msgid="94695026776264709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎Folder opened, ‎‏‎‎‏‏‎<xliff:g id="WIDTH">%1$d</xliff:g>‎‏‎‎‏‏‏‎ by ‎‏‎‎‏‏‎<xliff:g id="HEIGHT">%2$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‎Tap to close folder‎‏‎‎‏‎"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎Tap to save rename‎‏‎‎‏‎"</string>
+    <string name="folder_closed" msgid="4100806530910930934">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎Folder closed‎‏‎‎‏‎"</string>
+    <string name="folder_renamed" msgid="1794088362165669656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎Folder renamed to ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="folder_name_format" msgid="6629239338071103179">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎Folder: ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="widget_button_text" msgid="2880537293434387943">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎Widgets‎‏‎‎‏‎"</string>
+    <string name="wallpaper_button_text" msgid="8404103075899945851">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎Wallpapers‎‏‎‎‏‎"</string>
+    <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‏‎Styles &amp; wallpapers‎‏‎‎‏‎"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎Home settings‎‏‎‎‏‎"</string>
+    <string name="msg_disabled_by_admin" msgid="6898038085516271325">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‏‎Disabled by your admin‎‏‎‎‏‎"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎Allow Home screen rotation‎‏‎‎‏‎"</string>
+    <string name="allow_rotation_desc" msgid="8662546029078692509">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎When phone is rotated‎‏‎‎‏‎"</string>
+    <string name="notification_dots_title" msgid="9062440428204120317">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎Notification dots‎‏‎‎‏‎"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎On‎‏‎‎‏‎"</string>
+    <string name="notification_dots_desc_off" msgid="1760796511504341095">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‏‏‎Off‎‏‎‎‏‎"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎Notification access needed‎‏‎‎‏‎"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎To show Notification Dots, turn on app notifications for ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="title_change_settings" msgid="1376365968844349552">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎Change settings‎‏‎‎‏‎"</string>
+    <string name="notification_dots_service_title" msgid="4284221181793592871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‎Show notification dots‎‏‎‎‏‎"</string>
+    <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎Add icon to Home screen‎‏‎‎‏‎"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‎For new apps‎‏‎‎‏‎"</string>
+    <string name="package_state_unknown" msgid="7592128424511031410">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎Unknown‎‏‎‎‏‎"</string>
+    <string name="abandoned_clean_this" msgid="7610119707847920412">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎Remove‎‏‎‎‏‎"</string>
+    <string name="abandoned_search" msgid="891119232568284442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎Search‎‏‎‎‏‎"</string>
+    <string name="abandoned_promises_title" msgid="7096178467971716750">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎This app is not installed‎‏‎‎‏‎"</string>
+    <string name="abandoned_promise_explanation" msgid="3990027586878167529">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‎The app for this icon isn\'t installed. You can remove it, or search for the app and install it manually.‎‏‎‎‏‎"</string>
+    <string name="app_downloading_title" msgid="8336702962104482644">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ downloading, ‎‏‎‎‏‏‎<xliff:g id="PROGRESS">%2$s</xliff:g>‎‏‎‎‏‏‏‎ complete‎‏‎‎‏‎"</string>
+    <string name="app_waiting_download_title" msgid="7053938513995617849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ waiting to install‎‏‎‎‏‎"</string>
+    <string name="widgets_bottom_sheet_title" msgid="2904559530954183366">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ widgets‎‏‎‎‏‎"</string>
+    <string name="widgets_list" msgid="796804551140113767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎Widgets list‎‏‎‎‏‎"</string>
+    <string name="widgets_list_closed" msgid="6141506579418771922">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎Widgets list closed‎‏‎‎‏‎"</string>
+    <string name="action_add_to_workspace" msgid="8902165848117513641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‎Add to Home screen‎‏‎‎‏‎"</string>
+    <string name="action_move_here" msgid="2170188780612570250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‎‎Move item here‎‏‎‎‏‎"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎Item added to home screen‎‏‎‎‏‎"</string>
+    <string name="item_removed" msgid="851119963877842327">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎Item removed‎‏‎‎‏‎"</string>
+    <string name="undo" msgid="4151576204245173321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎Undo‎‏‎‎‏‎"</string>
+    <string name="action_move" msgid="4339390619886385032">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎Move item‎‏‎‎‏‎"</string>
+    <string name="move_to_empty_cell" msgid="2833711483015685619">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎Move to row ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ column ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="move_to_position" msgid="6750008980455459790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎Move to position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="move_to_hotseat_position" msgid="6295412897075147808">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎Move to favorites position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="item_moved" msgid="4606538322571412879">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎Item moved‎‏‎‎‏‎"</string>
+    <string name="add_to_folder" msgid="9040534766770853243">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‏‏‎Add to folder: ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="add_to_folder_with_app" msgid="4534929978967147231">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‏‏‏‎Add to folder with ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="added_to_folder" msgid="4793259502305558003">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎Item added to folder‎‏‎‎‏‎"</string>
+    <string name="create_folder_with" msgid="4050141361160214248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎‎‎‎Create folder with: ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="folder_created" msgid="6409794597405184510">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎Folder created‎‏‎‎‏‎"</string>
+    <string name="action_move_to_workspace" msgid="1603837886334246317">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‎Move to Home screen‎‏‎‎‏‎"</string>
+    <string name="action_resize" msgid="1802976324781771067">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎Resize‎‏‎‎‏‎"</string>
+    <string name="action_increase_width" msgid="8773715375078513326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎Increase width‎‏‎‎‏‎"</string>
+    <string name="action_increase_height" msgid="459390020612501122">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎Increase height‎‏‎‎‏‎"</string>
+    <string name="action_decrease_width" msgid="1374549771083094654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎Decrease width‎‏‎‎‏‎"</string>
+    <string name="action_decrease_height" msgid="282377193880900022">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎Decrease height‎‏‎‎‏‎"</string>
+    <string name="widget_resized" msgid="9130327887929620">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎Widget resized to width ‎‏‎‎‏‏‎<xliff:g id="NUMBER_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ height ‎‏‎‎‏‏‎<xliff:g id="NUMBER_1">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="action_deep_shortcut" msgid="2864038805849372848">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎Shortcuts‎‏‎‎‏‎"</string>
+    <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎Shortcuts and notifications‎‏‎‎‏‎"</string>
+    <string name="action_dismiss_notification" msgid="5909461085055959187">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎Dismiss‎‏‎‎‏‎"</string>
+    <string name="notification_dismissed" msgid="6002233469409822874">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎Notification dismissed‎‏‎‎‏‎"</string>
+    <string name="all_apps_personal_tab" msgid="4190252696685155002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎Personal‎‏‎‎‏‎"</string>
+    <string name="all_apps_work_tab" msgid="4884822796154055118">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎Work‎‏‎‎‏‎"</string>
+    <string name="work_profile_toggle_label" msgid="3081029915775481146">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎Work profile‎‏‎‎‏‎"</string>
+    <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎Find work apps here‎‏‎‎‏‎"</string>
+    <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access.‎‏‎‎‏‎"</string>
+    <string name="work_mode_on_label" msgid="4781128097185272916">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎Managed by your organization‎‏‎‎‏‎"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎Notifications and apps are off‎‏‎‎‏‎"</string>
+    <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‎Close‎‏‎‎‏‎"</string>
+    <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‎Closed‎‏‎‎‏‎"</string>
+    <string name="remote_action_failed" msgid="1383965239183576790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎Failed: ‎‏‎‎‏‏‎<xliff:g id="WHAT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d10c84b..960ef93 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/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">"Trabajo"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 09b1239..01f60e4 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/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">"Trabajo"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 1e470e1..ff121fa 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/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">"Töö"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Rakendus ei ole saadaval"</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-fa/strings.xml b/res/values-fa/strings.xml
index 926cdb9..913ff48 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/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-fi/strings.xml b/res/values-fi/strings.xml
index f87441f..e0930b6 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/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">"Työ"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Sovellus ei ole käytettävissä"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 5ac514d..50d6a06 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/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">"Lanceur3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Travail"</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>
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..da9da38 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>
@@ -81,7 +80,7 @@
     <string name="widget_button_text" msgid="2880537293434387943">"વિજેટ્સ"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"વૉલપેપર્સ"</string>
     <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"શૈલીઓ અને વૉલપેપર"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ્સ"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"હોમ સેટિંગ"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"તમારા વ્યવસ્થાપક દ્વારા અક્ષમ કરેલ"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"હોમ સ્ક્રીનને ફેરવવાની મંજૂરી આપો"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"જ્યારે ફોન ફેરવવામાં આવે ત્યારે"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index b796140e..8fa02c0 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/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>
@@ -88,7 +87,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"नई सूचनाएं बताने वाला गोल निशान"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"चालू"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"चालू"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के एक्सेस की ज़रूरत है"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचना के ऐक्सेस की ज़रूरत है"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"सूचना बिंदु दिखाने के लिए, <xliff:g id="NAME">%1$s</xliff:g> के ऐप्लिकेशन सूचना चालू करें"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"सेटिंग बदलें"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"नई सूचनाएं बताने वाला गोल निशान दिखाएं"</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-hu/strings.xml b/res/values-hu/strings.xml
index 3327590..7e26ca4 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/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">"Munka"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Az alkalmazás nem érhető el"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index fbb657e..93404e6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/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>
@@ -87,7 +86,7 @@
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"Ծանուցումների կետիկներ"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Միացված է"</string>
-    <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատած է"</string>
+    <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատված է"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Անհրաժեշտ է ծանուցման թույլտվություն"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Փոխել կարգավորումները"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 3054214..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>
@@ -30,10 +29,10 @@
     <string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
     <string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
-    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tap dua kalip &amp; tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kali &amp; tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tap lama untuk menempatkan secara manual"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sentuh lama untuk menempatkan secara manual"</string>
     <string name="place_automatically" msgid="8064208734425456485">"Tambahkan otomatis"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Telusuri aplikasi"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Memuat aplikasi…"</string>
@@ -41,8 +40,8 @@
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikasi"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
-    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Tap lama untuk memilih pintasan."</string>
-    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Tap dua kali &amp; tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
+    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Sentuh lama untuk memilih pintasan."</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Ketuk dua kali &amp; tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
@@ -73,8 +72,8 @@
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Halaman layar utama baru"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
-    <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap untuk menutup folder"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap untuk menyimpan ganti nama"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Ketuk untuk menutup folder"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ketuk untuk menyimpan ganti nama"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index d66ca32..9d26c8c 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/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">"Vinna"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Forritið er ekki uppsett."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Forritið er ekki í boði"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8c4e3c5..aaceb53 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/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">"Lavoro"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App non disponibile"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index c25e879..3525699 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/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-ja/strings.xml b/res/values-ja/strings.xml
index 354f020..dad4879 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/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-ka/strings.xml b/res/values-ka/strings.xml
index e9afef9..b348afb 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/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-kk/strings.xml b/res/values-kk/strings.xml
index 1cd9045..bd25ac9 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/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-km/strings.xml b/res/values-km/strings.xml
index f4a328d..9738042 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/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-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-ko/strings.xml b/res/values-ko/strings.xml
index 58b65da..e2767f9 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/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-ky/strings.xml b/res/values-ky/strings.xml
index 046a662..d52446c 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/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-lo/strings.xml b/res/values-lo/strings.xml
index afe7664..e764347 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/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-lt/strings.xml b/res/values-lt/strings.xml
index 6571582..7f93ac8 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/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">"Darbas"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Programa nepasiekiama"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 3da0fbb..13d88fe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/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">"Darbs"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Lietotne nav pieejama."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 9550a5f..458fb73 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>
@@ -81,9 +80,9 @@
     <string name="widget_button_text" msgid="2880537293434387943">"Виџети"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Тапети"</string>
     <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"Стилови и тапети"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Поставки за Home"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Поставки за почетен екран"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Оневозможено од администраторот"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на Почетниот екран"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"Дозволете ротација на почетниот екран"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Кога телефонот се ротира"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"Точки за известување"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Вклучено"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 4362e7c..a118368 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/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-mn/strings.xml b/res/values-mn/strings.xml
index ab02ac6..1b0e753 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/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-mr/strings.xml b/res/values-mr/strings.xml
index 19c0697..69eb5ee 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/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>
@@ -35,19 +34,19 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
     <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_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="all_apps_search_bar_hint" msgid="1390553134053255246">"अ‍ॅप्स शोधा"</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>
     <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
     <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"शॉर्टकट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <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_personal_label" msgid="1315764287305224468">"वैयक्तिक अॅप्स सूची"</string>
-    <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची"</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>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"काढा"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"अनइंस्टॉल करा"</string>
@@ -83,7 +82,7 @@
     <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"शैली आणि वॉलपेपर"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग्‍ज"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"आपल्या प्रशासकाने अक्षम केले"</string>
-    <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्‍यास अनुमती द्या"</string>
+    <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्य स्क्रीन फिरविण्‍यास अनुमती द्या"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"सूचना बिंदू"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"सुरू"</string>
@@ -93,7 +92,7 @@
     <string name="title_change_settings" msgid="1376365968844349552">"सेटिंग्ज बदला"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"सूचना बिंदू दाखवा"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"होम स्क्रीनवर आयकन जोडा"</string>
-    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन अॅप्ससाठी"</string>
+    <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"नवीन ॲप्ससाठी"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"अज्ञात"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"काढा"</string>
     <string name="abandoned_search" msgid="891119232568284442">"शोधा"</string>
@@ -136,7 +135,7 @@
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"कामाची अ‍ॅप्स येथे मिळवा"</string>
     <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"प्रत्येक कार्य अ‍ॅपला एक बॅज असतो आणि तो तुमच्या संस्थेकडून सुरक्षित ठेवला जातो. अधिक सहज अ‍ॅक्सेससाठी अ‍ॅप्स तुमच्या होम स्क्रीनवर हलवा."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"तुमच्या संस्थेकडून व्यवस्थापित"</string>
-    <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अॅप्स बंद आहेत"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अ‍ॅप्स बंद आहेत"</string>
     <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"बंद करा"</string>
     <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"बंद केले"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"हे करता आले नाही: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 7d05412..492e6b6 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/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">"Kerja"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Apl tidak tersedia"</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-nb/strings.xml b/res/values-nb/strings.xml
index 2257367..356ed03 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/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">"Jobb"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgjengelig"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 1e7aee9..58d61f9 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/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-nl/strings.xml b/res/values-nl/strings.xml
index ec30d8c..c2dfb71 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/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">"Werk"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"App is niet beschikbaar"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 4ddc903..2479d1b 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/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>
@@ -85,8 +84,8 @@
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"ଆପଣଙ୍କ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"ହୋମ୍‌ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ"</string>
-    <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁଗୁଡ଼ିକ"</string>
-    <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ କରନ୍ତୁ"</string>
+    <string name="notification_dots_title" msgid="9062440428204120317">"ବିଜ୍ଞପ୍ତି ଡଟ୍ସ"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"ଚାଲୁ"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍‌ ଆବଶ୍ୟକ ଅଟେ"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, <xliff:g id="NAME">%1$s</xliff:g> ପାଇଁ ଆପ୍‌ ବିଜ୍ଞପ୍ତି ଅନ୍‌ କରନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index d4cd5be..24686c4 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/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-pl/strings.xml b/res/values-pl/strings.xml
index 6885960..48c8baf 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/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">"Praca"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacja niedostępna"</string>
@@ -130,7 +129,7 @@
     <string name="widget_resized" msgid="9130327887929620">"Szerokość i wysokość widżetu zmieniła się na <xliff:g id="NUMBER_0">%1$s</xliff:g> x <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="action_deep_shortcut" msgid="2864038805849372848">"Skróty"</string>
     <string name="shortcuts_menu_with_notifications_description" msgid="2676582286544232849">"Skróty i powiadomienia"</string>
-    <string name="action_dismiss_notification" msgid="5909461085055959187">"Odrzuć"</string>
+    <string name="action_dismiss_notification" msgid="5909461085055959187">"Zamknij"</string>
     <string name="notification_dismissed" msgid="6002233469409822874">"Powiadomienie odrzucone"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobiste"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Praca"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 071b71e..e0d2c74 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/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">"Trabalho"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"A aplicação não está instalada."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"A aplicação não está disponível"</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-rm/strings.xml b/res/values-rm/strings.xml
deleted file mode 100644
index 0758148..0000000
--- a/res/values-rm/strings.xml
+++ /dev/null
@@ -1,203 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2008 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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for application_name (5181331383435256801) -->
-    <skip />
-    <!-- no translation found for home (7658288663002113681) -->
-    <skip />
-    <!-- no translation found for uid_name (7820867637514617527) -->
-    <skip />
-    <string name="folder_name" msgid="7371454440695724752"></string>
-    <!-- no translation found for activity_not_found (8071924732094499514) -->
-    <skip />
-    <!-- no translation found for widgets_tab_label (2921133187116603919) -->
-    <skip />
-    <!-- no translation found for widget_adder (3201040140710381657) -->
-    <skip />
-    <!-- no translation found for toggle_weight_watcher (5645299835184636119) -->
-    <skip />
-    <!-- no translation found for long_press_widget_to_add (7699152356777458215) -->
-    <skip />
-    <!-- no translation found for market (2619650989819296998) -->
-    <skip />
-    <!-- no translation found for widget_dims_format (2370757736025621599) -->
-    <skip />
-    <!-- no translation found for external_drop_widget_error (3165821058322217155) -->
-    <skip />
-    <!-- no translation found for external_drop_widget_pick_title (3486317258037690630) -->
-    <skip />
-    <!-- no translation found for rename_folder_label (3727762225964550653) -->
-    <skip />
-    <!-- no translation found for rename_folder_title (3771389277707820891) -->
-    <skip />
-    <!-- no translation found for rename_action (5559600076028658757) -->
-    <skip />
-    <!-- no translation found for cancel_action (7009134900002915310) -->
-    <skip />
-    <!-- no translation found for menu_item_add_item (1264911265836810421) -->
-    <skip />
-    <!-- no translation found for group_applications (3797214114206693605) -->
-    <skip />
-    <!-- no translation found for group_shortcuts (6012256992764410535) -->
-    <skip />
-    <!-- no translation found for group_widgets (1569030723286851002) -->
-    <skip />
-    <!-- no translation found for completely_out_of_space (6106288382070760318) -->
-    <skip />
-    <!-- no translation found for out_of_space (4691004494942118364) -->
-    <skip />
-    <!-- no translation found for hotseat_out_of_space (7448809638125333693) -->
-    <skip />
-    <!-- no translation found for invalid_hotseat_item (5779907847267573691) -->
-    <skip />
-    <!-- no translation found for shortcut_installed (1701742129426969556) -->
-    <skip />
-    <!-- no translation found for shortcut_uninstalled (8176767991305701821) -->
-    <skip />
-    <!-- no translation found for shortcut_duplicate (9167217446062498127) -->
-    <skip />
-    <!-- no translation found for title_select_shortcut (6680642571148153868) -->
-    <skip />
-    <!-- no translation found for title_select_application (3280812711670683644) -->
-    <skip />
-    <!-- no translation found for all_apps_button_label (9110807029020582876) -->
-    <skip />
-    <!-- no translation found for all_apps_home_button_label (252062713717058851) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_workspace (4009607676751398685) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_all_apps (8083826390278958980) -->
-    <skip />
-    <!-- no translation found for delete_target_label (1822697352535677073) -->
-    <skip />
-    <!-- no translation found for delete_target_uninstall_label (5100785476250872595) -->
-    <skip />
-    <!-- no translation found for info_target_label (8053346143994679532) -->
-    <skip />
-    <!-- no translation found for accessibility_search_button (1628520399424565142) -->
-    <skip />
-    <!-- no translation found for accessibility_voice_search_button (4637324840434406584) -->
-    <skip />
-    <!-- no translation found for accessibility_all_apps_button (2603132375383800483) -->
-    <skip />
-    <!-- no translation found for accessibility_delete_button (6466114477993744621) -->
-    <skip />
-    <!-- no translation found for delete_zone_label_all_apps_system_app (449755632749610895) -->
-    <skip />
-    <!-- no translation found for cab_menu_delete_app (7435191475867183689) -->
-    <skip />
-    <!-- no translation found for cab_menu_app_info (8593722221450362342) -->
-    <skip />
-    <!-- no translation found for cab_app_selection_text (374688303047985416) -->
-    <skip />
-    <!-- no translation found for cab_widget_selection_text (1833458597831541241) -->
-    <skip />
-    <!-- no translation found for cab_folder_selection_text (7999992513806132118) -->
-    <skip />
-    <!-- no translation found for cab_shortcut_selection_text (2103811025667946450) -->
-    <skip />
-    <!-- no translation found for permlab_install_shortcut (5632423390354674437) -->
-    <skip />
-    <!-- no translation found for permdesc_install_shortcut (923466509822011139) -->
-    <skip />
-    <!-- no translation found for permlab_uninstall_shortcut (864595034498083837) -->
-    <skip />
-    <!-- no translation found for permdesc_uninstall_shortcut (5134129545001836849) -->
-    <skip />
-    <!-- no translation found for permlab_read_settings (1941457408239617576) -->
-    <skip />
-    <!-- no translation found for permdesc_read_settings (5833423719057558387) -->
-    <skip />
-    <!-- no translation found for permlab_write_settings (3574213698004620587) -->
-    <skip />
-    <!-- no translation found for permdesc_write_settings (5440712911516509985) -->
-    <skip />
-    <!-- no translation found for gadget_error_text (6081085226050792095) -->
-    <skip />
-    <!-- no translation found for uninstall_system_app_text (4172046090762920660) -->
-    <skip />
-    <!-- no translation found for dream_name (1530253749244328964) -->
-    <skip />
-    <!-- no translation found for folder_hint_text (6617836969016293992) -->
-    <skip />
-    <!-- no translation found for workspace_description_format (2950174241104043327) -->
-    <skip />
-    <!-- no translation found for default_scroll_format (7475544710230993317) -->
-    <skip />
-    <!-- no translation found for workspace_scroll_format (8458889198184077399) -->
-    <skip />
-    <!-- no translation found for apps_customize_apps_scroll_format (370005296147130238) -->
-    <skip />
-    <!-- no translation found for apps_customize_widgets_scroll_format (3106209519974971521) -->
-    <skip />
-    <!-- no translation found for first_run_cling_title (2459738000155917941) -->
-    <skip />
-    <!-- no translation found for first_run_cling_description (6447072552696253358) -->
-    <skip />
-    <!-- no translation found for first_run_cling_create_screens_hint (6950729526680114157) -->
-    <skip />
-    <!-- no translation found for migration_cling_title (9181776667882933767) -->
-    <skip />
-    <!-- no translation found for migration_cling_description (2752413805582227644) -->
-    <skip />
-    <!-- no translation found for migration_cling_copy_apps (946331230090919440) -->
-    <skip />
-    <!-- no translation found for migration_cling_use_default (2626475813981258626) -->
-    <skip />
-    <!-- no translation found for workspace_cling_title (5626202359865825661) -->
-    <skip />
-    <!-- no translation found for workspace_cling_move_item (528201129978005352) -->
-    <skip />
-    <!-- no translation found for folder_cling_title (3894908818693254164) -->
-    <skip />
-    <!-- no translation found for folder_cling_create_folder (6158215559475836131) -->
-    <skip />
-    <!-- no translation found for cling_dismiss (8962359497601507581) -->
-    <skip />
-    <!-- no translation found for folder_opened (94695026776264709) -->
-    <skip />
-    <!-- no translation found for folder_tap_to_close (1884479294466410023) -->
-    <skip />
-    <!-- no translation found for folder_tap_to_rename (9191075570492871147) -->
-    <skip />
-    <!-- no translation found for folder_closed (4100806530910930934) -->
-    <skip />
-    <!-- no translation found for folder_renamed (1794088362165669656) -->
-    <skip />
-    <!-- no translation found for folder_name_format (6629239338071103179) -->
-    <skip />
-    <!-- no translation found for widget_button_text (2880537293434387943) -->
-    <skip />
-    <!-- no translation found for wallpaper_button_text (8404103075899945851) -->
-    <skip />
-    <!-- no translation found for settings_button_text (8119458837558863227) -->
-    <skip />
-    <!-- no translation found for package_state_enqueued (6227252464303085641) -->
-    <skip />
-    <!-- no translation found for package_state_downloading (4088770468458724721) -->
-    <skip />
-    <!-- no translation found for package_state_installing (7588193972189849870) -->
-    <skip />
-    <!-- no translation found for package_state_unknown (7592128424511031410) -->
-    <skip />
-    <!-- no translation found for package_state_error (7672093962724223588) -->
-    <skip />
-</resources>
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-ru/strings.xml b/res/values-ru/strings.xml
index 34e267d..1cc699f 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/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>
@@ -90,7 +89,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"Значки уведомлений"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Включены"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Отключены"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"Нет доступа к уведомлениям"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Нужен доступ к уведомлениям"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Чтобы показывать значки уведомлений, включите уведомления в приложении \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Изменить настройки"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Показывать значки уведомлений"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ef99a59..2f9dc42 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/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-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-sl/strings.xml b/res/values-sl/strings.xml
index 044a4b4..417151b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/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">"Služba"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija ni na voljo"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 48ae26e..7f2567c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/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">"Nisësi3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Puna"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Aplikacioni nuk është i instaluar."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Aplikacioni nuk mundësohet"</string>
@@ -41,8 +40,8 @@
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacioni"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
-    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj prekur për të zgjedhur një shkurtore."</string>
-    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj prekur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string>
+    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj të shtypur për të zgjedhur një shkurtore."</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj të shtypur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string>
     <string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Lista e aplikacioneve"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 4e52592..5e2c954 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/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">"Апликација није инсталирана."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Апликација није доступна"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index e7400a6..7df507b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/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">"Arbete"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Appen är inte tillgänglig"</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 672665e..799b6b8 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,8 +35,8 @@
     <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_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</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>
     <string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</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>
@@ -89,7 +88,7 @@
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"ஆன்"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"ஆஃப்"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"அறிவிப்பிற்கான அணுகல் தேவை"</string>
-    <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் பயன்பாட்டு அறிவிப்புகளை இயக்கவும்"</string>
+    <string name="msg_missing_notification_access" msgid="281113995110910548">"அறிவிப்புப் புள்ளிகளைக் காட்ட, <xliff:g id="NAME">%1$s</xliff:g> இன் ஆப்ஸ் அறிவிப்புகளை இயக்கவும்"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"அமைப்புகளை மாற்று"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"அறிவிப்புப் புள்ளிகளைக் காட்டு"</string>
     <string name="auto_add_shortcuts_label" msgid="8222286205987725611">"முகப்புத் திரையில் ஐகானைச் சேர்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index c8f70fe..1f8d3b8 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/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>
@@ -53,7 +52,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్‌స్టాల్ చేయి"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్‌స్టాల్ చేయండి"</string>
-    <string name="permlab_install_shortcut" msgid="5632423390354674437">"సత్వరమార్గాలను ఇన్‌స్టాల్ చేయడం"</string>
+    <string name="permlab_install_shortcut" msgid="5632423390354674437">"షార్ట్‌కట్‌లను ఇన్‌స్టాల్ చేయడం"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string>
     <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్‌లు మరియు సత్వరమార్గాలను చదవడం"</string>
     <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్‌లో సెట్టింగ్‌లు మరియు సత్వరమార్గాలను చదవడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</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-tl/strings.xml b/res/values-tl/strings.xml
index 0df94c7..de4863a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/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">"Trabaho"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Hindi available ang app"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 56f8447..1b2b3d1 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/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">"Uygulama yüklü değil."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Uygulama kullanılamıyor"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 13ba701..622cdcc 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/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-ur/strings.xml b/res/values-ur/strings.xml
index 4f77670..74b5eaa 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/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-uz/strings.xml b/res/values-uz/strings.xml
index 69084d7..dac1ac9 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/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">"Ishga oid"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Ilova o‘rnatilmadi."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Ilova mavjud emas"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 71decfc..4dc58b9 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/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">"Trình chạy 3"</string>
-    <string name="folder_name" msgid="7371454440695724752"></string>
     <string name="work_folder_name" msgid="3753320833950115786">"Work"</string>
     <string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"Ứng dụng không có sẵn"</string>
@@ -86,7 +85,7 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"Dấu chấm thông báo"</string>
-    <string name="notification_dots_desc_on" msgid="1679848116452218908">"Bật"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"Đang bật"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Tắt"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"Cần quyền truy cập thông báo"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 9804af1..0478627 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/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">"未安装该应用。"</string>
     <string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e737744..ed53c32 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/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-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index e971b69..14f2e06 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/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 69b8c8a..de17eb7 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -115,7 +115,6 @@
         <attr name="numFolderColumns" format="integer" />
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
-
         <attr name="defaultLayoutId" format="reference" />
         <attr name="demoModeLayoutId" format="reference" />
     </declare-styleable>
@@ -152,21 +151,6 @@
         <attr name="canThumbDetach" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="CustomAppWidgetProviderInfo">
-        <attr name="providerId" format="integer" />
-
-        <attr name="android:label" />
-        <attr name="android:initialLayout" />
-        <attr name="android:icon" />
-        <attr name="android:previewImage" />
-        <attr name="android:resizeMode" />
-
-        <attr name="numRows" />
-        <attr name="numColumns" />
-        <attr name="numMinRows" format="integer" />
-        <attr name="numMinColumns" format="integer" />
-    </declare-styleable>
-
     <declare-styleable name="PreviewFragment">
         <attr name="android:name" />
         <attr name="android:id" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 638a411..9d36ee4 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -72,6 +72,7 @@
     <string name="system_shortcut_factory_class" translatable="false"></string>
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
+    <string name="launcher_activity_logic_class" translatable="false"></string>
 
     <!-- Package name of the default wallpaper picker. -->
     <string name="wallpaper_picker_package" translatable="false"></string>
@@ -117,4 +118,7 @@
 
 <!-- Recents -->
     <item type="id" name="overview_panel"/>
+
+    <string-array name="live_wallpapers_remove_sysui_scrims">
+    </string-array>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 13e096c..9d9c2e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -23,8 +23,6 @@
 
     <!-- Application name -->
     <string name="app_name">Launcher3</string>
-    <!-- Default folder name -->
-    <string name="folder_name"></string>
     <!-- Work folder name -->
     <string name="work_folder_name">Work</string>
     <!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]-->
@@ -105,6 +103,9 @@
     <!-- Label for install drop target. [CHAR_LIMIT=20] -->
     <string name="install_drop_target_label">Install</string>
 
+    <!-- Label for install dismiss prediction. -->
+    <string translatable="false" name="dismiss_prediction_label">Dismiss prediction</string>
+
     <!-- Permissions: -->
     <skip />
     <!-- Permission short label -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 881f65d..80c791c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -104,6 +104,7 @@
     </style>
 
     <style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
+        <item name="android:colorControlHighlight">#75212121</item>
         <item name="allAppsInterimScrimAlpha">25</item>
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderTextColor">?attr/workspaceTextColor</item>
@@ -129,6 +130,10 @@
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
+    <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+    </style>
+
     <!--
     Theme overrides to element on homescreen, i.e., which are drawn on top on wallpaper.
     Various foreground colors are overridden to be workspaceTextColor so that they are properly
diff --git a/res/xml/custom_widgets.xml b/res/xml/custom_widgets.xml
deleted file mode 100644
index 4b54386..0000000
--- a/res/xml/custom_widgets.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-
-<widgets>
-    <!-- Sample widget definition
-        <widget
-            android:label="My custom widget"
-            android:initialLayout="@layout/sample_widget_layout"
-            android:icon="@drawable/ic_launcher_home"
-            android:resizeMode="horizontal|vertical"
-            launcher:numRows="2"
-            launcher:numColumns="3"
-            launcher:numMinRows="1"
-            launcher:numMinColumns="2"
-            launcher:providerId="1" />
-    -->
-</widgets>
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
index 92bcc64..a3d1216 100644
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
+++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java
@@ -1,6 +1,8 @@
 package com.android.launcher3.config;
 
 
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+import com.android.launcher3.uioverrides.TogglableFlag;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -70,7 +72,7 @@
         };
     }
 
-    private void override(BaseFlags.TogglableFlag flag, boolean newValue) {
+    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?");
@@ -93,7 +95,7 @@
 
     private void applyAnnotation(FlagOverride flagOverride) {
         boolean found = false;
-        for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
             if (flag.getKey().equals(flagOverride.key())) {
                 override(flag, flagOverride.value());
                 found = true;
@@ -109,7 +111,7 @@
      * Resets all flags to their default values.
      */
     private void clearOverrides() {
-        for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+        for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
             flag.setForTests(flag.getDefaultValue());
         }
     }
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
index ab39274..bc936b7 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -15,14 +15,13 @@
 import android.os.Process;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 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.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.icons.IconCache;
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index e9324f9..42a4f5c 100644
--- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -29,7 +29,8 @@
 
     private PackageInstallStateChangedTask newTask(String pkg, int progress) {
         int state = PackageInstallerCompat.STATUS_INSTALLING;
-        PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress);
+        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/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
new file mode 100644
index 0000000..c08e198
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Robolectric unit tests for {@link IntArray}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class IntArrayTest {
+
+    @Test
+    public void concatAndParseString() {
+        int[] array = new int[] {0, 2, 3, 9};
+        String concat = IntArray.wrap(array).toConcatString();
+
+        int[] parsed = IntArray.fromConcatString(concat).toArray();
+        assertThat(array).isEqualTo(parsed);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
index f846de5..8513353 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -21,7 +21,6 @@
 import org.junit.runner.RunWith;
 
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 65f9d6b..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;
@@ -86,7 +86,7 @@
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
-            | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+            | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
 
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
@@ -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/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index d884049..c8e7619 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -29,11 +29,19 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 
+import java.util.Comparator;
+
 /**
  * Represents an app in AllAppsView.
  */
 public class AppInfo extends ItemInfoWithIcon {
 
+    public static AppInfo[] EMPTY_ARRAY = new AppInfo[0];
+    public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> {
+        int uc = a.user.hashCode() - b.user.hashCode();
+        return uc != 0 ? uc : a.componentName.compareTo(b.componentName);
+    };
+
     /**
      * The intent used to start the application.
      */
@@ -41,6 +49,9 @@
 
     public ComponentName componentName;
 
+    // Section name used for indexing.
+    public String sectionName = "";
+
     public AppInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     }
@@ -74,6 +85,8 @@
         componentName = info.componentName;
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
+        user = info.user;
+        runtimeStatusFlags = info.runtimeStatusFlags;
     }
 
     @Override
@@ -116,4 +129,9 @@
             info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
         }
     }
+
+    @Override
+    public AppInfo clone() {
+        return new AppInfo(this);
+    }
 }
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index d949141..e3ef5d6 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -11,6 +11,7 @@
 import android.util.Log;
 
 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.provider.RestoreDbTask;
@@ -18,6 +19,8 @@
 
 import androidx.annotation.WorkerThread;
 
+import static android.os.Process.myUserHandle;
+
 public class AppWidgetsRestoredReceiver extends BroadcastReceiver {
 
     private static final String TAG = "AWRestoredReceiver";
@@ -77,9 +80,14 @@
                 state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
             }
 
-            String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) };
+            // b/135926478: Work profile widget restore is broken in platform. This forces us to
+            // recreate the widget during loading with the correct host provider.
+            long mainProfileId = UserManagerCompat.getInstance(context)
+                    .getSerialNumberForUser(myUserHandle());
+            String oldWidgetId = Integer.toString(oldWidgetIds[i]);
             int result = new ContentWriter(context, new ContentWriter.CommitParams(
-                    "appWidgetId=? and (restored & 1) = 1", widgetIdParams))
+                    "appWidgetId=? and (restored & 1) = 1 and profileId=?",
+                    new String[] { oldWidgetId, Long.toString(mainProfileId) }))
                     .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
                     .put(LauncherSettings.Favorites.RESTORED, state)
                     .commit();
@@ -87,7 +95,7 @@
             if (result == 0) {
                 Cursor cursor = cr.query(Favorites.CONTENT_URI,
                         new String[] {Favorites.APPWIDGET_ID},
-                        "appWidgetId=?", widgetIdParams, null);
+                        "appWidgetId=?", new String[] { oldWidgetId }, null);
                 try {
                     if (!cursor.moveToFirst()) {
                         // The widget no long exists.
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 9724869..ac43967 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -38,11 +38,15 @@
 import android.util.Patterns;
 import android.util.Xml;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherProvider.SqlArguments;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Thunk;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -72,7 +76,7 @@
 
     static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
             LayoutParserCallback callback) {
-        Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk(
+        Pair<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk(
                 ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
         if (customizationApkInfo == null) {
             return null;
@@ -83,7 +87,7 @@
 
         // Try with grid size and hotseat count
         String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
-            grid.numColumns, grid.numRows, grid.numHotseatIcons);
+                grid.numColumns, grid.numRows, grid.numHotseatIcons);
         int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
 
         // Try with only grid size
@@ -91,7 +95,7 @@
             Log.d(TAG, "Formatted layout: " + layoutName
                     + " not found. Trying layout without hosteat");
             layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
-                grid.numColumns, grid.numRows);
+                    grid.numColumns, grid.numRows);
             layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
         }
 
@@ -116,6 +120,7 @@
     private static final String TAG_AUTO_INSTALL = "autoinstall";
     private static final String TAG_FOLDER = "folder";
     private static final String TAG_APPWIDGET = "appwidget";
+    protected static final String TAG_SEARCH_WIDGET = "searchwidget";
     private static final String TAG_SHORTCUT = "shortcut";
     private static final String TAG_EXTRA = "extra";
 
@@ -147,8 +152,10 @@
     private static final String HOTSEAT_CONTAINER_NAME =
             Favorites.containerToString(Favorites.CONTAINER_HOTSEAT);
 
-    @Thunk final Context mContext;
-    @Thunk final AppWidgetHost mAppWidgetHost;
+    @Thunk
+    final Context mContext;
+    @Thunk
+    final AppWidgetHost mAppWidgetHost;
     protected final LayoutParserCallback mCallback;
 
     protected final PackageManager mPackageManager;
@@ -160,7 +167,8 @@
     private final int mColumnCount;
 
     private final int[] mTemp = new int[2];
-    @Thunk final ContentValues mValues;
+    @Thunk
+    final ContentValues mValues;
     protected final String mRootTag;
 
     protected SQLiteDatabase mDb;
@@ -244,7 +252,7 @@
      */
     protected int parseAndAddNode(
             XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds)
-        throws XmlPullParserException, IOException {
+            throws XmlPullParserException, IOException {
 
         if (TAG_INCLUDE.equals(parser.getName())) {
             final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0);
@@ -315,6 +323,7 @@
         parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
         parsers.put(TAG_FOLDER, new FolderParser());
         parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
+        parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
         parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
         return parsers;
     }
@@ -347,15 +356,15 @@
                         info = mPackageManager.getActivityInfo(cn, 0);
                     } catch (PackageManager.NameNotFoundException nnfe) {
                         String[] packages = mPackageManager.currentToCanonicalPackageNames(
-                                new String[] { packageName });
+                                new String[]{packageName});
                         cn = new ComponentName(packages[0], className);
                         info = mPackageManager.getActivityInfo(cn, 0);
                     }
                     final Intent intent = new Intent(Intent.ACTION_MAIN, null)
-                        .addCategory(Intent.CATEGORY_LAUNCHER)
-                        .setComponent(cn)
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                            .addCategory(Intent.CATEGORY_LAUNCHER)
+                            .setComponent(cn)
+                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
                     return addShortcut(info.loadLabel(mPackageManager).toString(),
                             intent, Favorites.ITEM_TYPE_APPLICATION);
@@ -393,10 +402,10 @@
 
             mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON);
             final Intent intent = new Intent(Intent.ACTION_MAIN, null)
-                .addCategory(Intent.CATEGORY_LAUNCHER)
-                .setComponent(new ComponentName(packageName, className))
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setComponent(new ComponentName(packageName, className))
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
             return addShortcut(mContext.getString(R.string.package_state_unknown), intent,
                     Favorites.ITEM_TYPE_APPLICATION);
         }
@@ -444,7 +453,7 @@
             mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
 
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
-                        Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
             return addShortcut(mSourceRes.getString(titleResId),
                     intent, Favorites.ITEM_TYPE_SHORTCUT);
         }
@@ -469,12 +478,22 @@
      */
     protected class PendingWidgetParser implements TagParser {
 
-        @Override
-        public int parseAndAdd(XmlPullParser parser)
-                throws XmlPullParserException, IOException {
+        @Nullable
+        public ComponentName getComponentName(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
+                return null;
+            }
+            return new ComponentName(packageName, className);
+        }
+
+
+        @Override
+        public int parseAndAdd(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            ComponentName cn = getComponentName(parser);
+            if (cn == null) {
                 if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component");
                 return -1;
             }
@@ -505,16 +524,15 @@
                     throw new RuntimeException("Widgets can contain only extras");
                 }
             }
-
-            return verifyAndInsert(new ComponentName(packageName, className), extras);
+            return verifyAndInsert(cn, extras);
         }
 
         protected int verifyAndInsert(ComponentName cn, Bundle extras) {
             mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString());
             mValues.put(Favorites.RESTORED,
-                    LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
-                            LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
-                            LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
+                    LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
+                            | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
+                            | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
             mValues.put(Favorites._ID, mCallback.generateNewItemId());
             if (!extras.isEmpty()) {
                 mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0));
@@ -529,6 +547,23 @@
         }
     }
 
+    protected class SearchWidgetParser extends PendingWidgetParser {
+        @Override
+        @Nullable
+        public ComponentName getComponentName(XmlPullParser parser) {
+            return QsbContainerView.getSearchComponentName(mContext);
+        }
+
+        @Override
+        protected int verifyAndInsert(ComponentName cn, Bundle extras) {
+            mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET);
+            int flags = mValues.getAsInteger(Favorites.RESTORED)
+                    | WorkspaceItemInfo.FLAG_RESTORE_STARTED;
+            mValues.put(Favorites.RESTORED, flags);
+            return super.verifyAndInsert(cn, extras);
+        }
+    }
+
     protected class FolderParser implements TagParser {
         private final ArrayMap<String, TagParser> mFolderElements;
 
@@ -548,7 +583,7 @@
             if (titleResId != 0) {
                 title = mSourceRes.getString(titleResId);
             } else {
-                title = mContext.getResources().getString(R.string.folder_name);
+                title = "";
             }
 
             mValues.put(Favorites.TITLE, title);
@@ -680,7 +715,8 @@
         int insertAndCheck(SQLiteDatabase db, ContentValues values);
     }
 
-    @Thunk static void copyInteger(ContentValues from, ContentValues to, String key) {
+    @Thunk
+    static void copyInteger(ContentValues from, ContentValues to, String key) {
         to.put(key, from.getAsInteger(key));
     }
 }
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 f69b172..994ba65 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -30,17 +30,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.testing.TestProtocol;
 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
  */
@@ -120,6 +120,10 @@
 
     public abstract View getRootView();
 
+    public void returnToHomescreen() {
+        // no-op
+    }
+
     public Rect getViewBounds(View v) {
         int[] pos = new int[2];
         v.getLocationOnScreen(pos);
@@ -135,11 +139,7 @@
 
     public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startActivitySafely 1");
-        }
-        if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
+        if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
         }
@@ -162,10 +162,6 @@
                 startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
-                if (TestProtocol.sDebugTracing) {
-                    android.util.Log.d(TestProtocol.NO_START_TAG,
-                            "startActivitySafely 2");
-                }
                 startActivity(intent, optsBundle);
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                         Process.myUserHandle(), sourceContainer);
@@ -178,7 +174,7 @@
             getUserEventDispatcher().logAppLaunch(v, intent);
             getStatsLogManager().logAppLaunch(v, intent);
             return true;
-        } catch (ActivityNotFoundException|SecurityException e) {
+        } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
             Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
         }
@@ -245,14 +241,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 1619e36..7adb6a4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -32,7 +32,6 @@
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
 import android.view.KeyEvent;
@@ -54,8 +53,8 @@
 import com.android.launcher3.icons.IconCache.IconLoadRequest;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.IconLabelDotView;
 
 import java.text.NumberFormat;
 
@@ -64,7 +63,8 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback {
+public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
+        IconLabelDotView {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -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,9 +330,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "BubbleTextView.onTouchEvent " + 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);
@@ -416,7 +433,8 @@
         }
     }
 
-    public void forceHideDot(boolean forceHideDot) {
+    @Override
+    public void setForceHideDot(boolean forceHideDot) {
         if (mForceHideDot == forceHideDot) {
             return;
         }
@@ -566,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()) {
@@ -605,6 +627,7 @@
         }
     }
 
+    @Override
     public void setIconVisible(boolean visible) {
         mIsIconVisible = visible;
         Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 09fb244..976ccd5 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2702,6 +2702,14 @@
             }
         }
 
+        /**
+         * Sets the position to the provided point
+         */
+        public void setXY(Point point) {
+            cellX = point.x;
+            cellY = point.y;
+        }
+
         public String toString() {
             return "(" + this.cellX + ", " + this.cellY + ")";
         }
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index 75297f6..af85594 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -14,13 +14,16 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.util.Thunk;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.List;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
 
 /**
  * Implements the layout parser with rules for internal layouts and partner layouts.
@@ -55,7 +58,8 @@
         return getFolderElementsMap(mSourceRes);
     }
 
-    @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
+    @Thunk
+    ArrayMap<String, TagParser> getFolderElementsMap(Resources res) {
         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
         parsers.put(TAG_SHORTCUT, new UriShortcutParser(res));
@@ -67,6 +71,7 @@
         ArrayMap<String, TagParser> parsers = new ArrayMap<>();
         parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
         parsers.put(TAG_APPWIDGET, new AppWidgetParser());
+        parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
         parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
         parsers.put(TAG_RESOLVE, new ResolveParser());
         parsers.put(TAG_FOLDER, new MyFolderParser());
@@ -229,7 +234,8 @@
     /**
      * A parser which adds a folder whose contents come from partner apk.
      */
-    @Thunk class PartnerFolderParser implements TagParser {
+    @Thunk
+    class PartnerFolderParser implements TagParser {
 
         @Override
         public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
@@ -255,7 +261,8 @@
     /**
      * An extension of FolderParser which allows adding items from a different xml.
      */
-    @Thunk class MyFolderParser extends FolderParser {
+    @Thunk
+    class MyFolderParser extends FolderParser {
 
         @Override
         public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException,
@@ -281,7 +288,7 @@
                 mPackageManager.getReceiverInfo(cn, 0);
             } catch (Exception e) {
                 String[] packages = mPackageManager.currentToCanonicalPackageNames(
-                        new String[] { cn.getPackageName() });
+                        new String[]{cn.getPackageName()});
                 cn = new ComponentName(packages[0], cn.getClassName());
                 try {
                     mPackageManager.getReceiverInfo(cn, 0);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 883e8c6..bc6fa6e 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,16 +24,20 @@
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.Surface;
-import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
 
 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 {
 
     public final InvariantDeviceProfile inv;
+    // IDP with no grid override values.
+    @Nullable private final InvariantDeviceProfile originalIdp;
 
     // Device properties
     public final boolean isTablet;
@@ -111,6 +115,7 @@
 
     // All apps
     public int allAppsCellHeightPx;
+    public int allAppsCellWidthPx;
     public int allAppsIconSizePx;
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
@@ -129,13 +134,15 @@
     private boolean mIsSeascape;
 
     // Notification dots
-    public DotRenderer mDotRenderer;
+    public DotRenderer mDotRendererWorkSpace;
+    public DotRenderer mDotRendererAllApps;
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
-            Point minSize, Point maxSize,
+            InvariantDeviceProfile originalIDP, Point minSize, Point maxSize,
             int width, int height, boolean isLandscape, boolean isMultiWindowMode) {
 
         this.inv = inv;
+        this.originalIdp = inv;
         this.isLandscape = isLandscape;
         this.isMultiWindowMode = isMultiWindowMode;
 
@@ -227,17 +234,33 @@
             // Recalculate the available dimensions using the new hotseat size.
             updateAvailableDimensions(dm, res);
         }
+
+        if (originalIDP != null) {
+            // Grid size change should not affect All Apps UI, so we use the original profile
+            // measurements here.
+            DeviceProfile originalProfile = isLandscape
+                    ? originalIDP.landscapeProfile
+                    : originalIDP.portraitProfile;
+            allAppsIconSizePx = originalProfile.iconSizePx;
+            allAppsIconTextSizePx = originalProfile.iconTextSizePx;
+            allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
+            allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
+            allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+        }
         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) {
         Point size = new Point(availableWidthPx, availableHeightPx);
-        return new DeviceProfile(context, inv, size, size, widthPx, heightPx, isLandscape,
-                isMultiWindowMode);
+        return new DeviceProfile(context, inv, originalIdp, size, size, widthPx, heightPx,
+                isLandscape, isMultiWindowMode);
     }
 
     public DeviceProfile getMultiWindowProfile(Context context, Point mwSize) {
@@ -248,8 +271,8 @@
         // In multi-window mode, we can have widthPx = availableWidthPx
         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
         // widthPx and heightPx values where it's needed.
-        DeviceProfile profile = new DeviceProfile(context, inv, mwSize, mwSize, mwSize.x, mwSize.y,
-                isLandscape, true);
+        DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+                mwSize.x, mwSize.y, isLandscape, true);
 
         // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
         float workspaceCellPaddingY = profile.getCellSize().y - profile.iconSizePx
@@ -307,11 +330,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);
 
@@ -328,13 +356,13 @@
         }
         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
 
-        // All apps
-        allAppsIconTextSizePx = iconTextSizePx;
         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();
         }
@@ -375,14 +403,15 @@
         Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the icons fit within the available height.
-        float usedHeight = folderCellHeightPx * inv.numFolderRows + folderBottomPanelSize;
-        int maxHeight = availableHeightPx - totalWorkspacePadding.y - folderMargin;
-        float scaleY = maxHeight / usedHeight;
+        float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+        int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
+                - folderMargin;
+        float scaleY = contentMaxHeight / contentUsedHeight;
 
         // Check if the icons fit within the available width.
-        float usedWidth = folderCellWidthPx * inv.numFolderColumns;
-        int maxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
-        float scaleX = maxWidth / usedWidth;
+        float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+        int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
+        float scaleX = contentMaxWidth / contentUsedWidth;
 
         float scale = Math.min(scaleX, scaleY);
         if (scale < 1f) {
@@ -419,14 +448,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 +575,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/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 7ab88a0..a90025e 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -142,8 +142,11 @@
 
     @Override
     public void setAlpha(int alpha) {
-        mAlpha = alpha;
-        mPaint.setAlpha(alpha);
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            mPaint.setAlpha(alpha);
+            invalidateSelf();
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index c967a96..6c5bc40 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -108,17 +108,20 @@
                     // For the second frame, if the first frame took more than 16ms,
                     // adjust the start time and pretend it took only 16ms anyway. This
                     // prevents a large jump in the animation due to an expensive first frame
-                } else if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
-                        !mAdjustedSecondFrameTime &&
-                        currentTime > mStartTime + SINGLE_FRAME_MS &&
-                        currentPlayTime > SINGLE_FRAME_MS) {
-                    animation.setCurrentPlayTime(SINGLE_FRAME_MS);
-                    mAdjustedSecondFrameTime = true;
                 } else {
-                    if (frameNum > 1) {
-                        mRootView.post(() -> animation.removeUpdateListener(this));
+                    int singleFrameMS = getSingleFrameMs(mRootView.getContext());
+                    if (frameNum == 1 && currentTime < mStartTime + MAX_DELAY &&
+                            !mAdjustedSecondFrameTime &&
+                            currentTime > mStartTime + singleFrameMS &&
+                            currentPlayTime > singleFrameMS) {
+                        animation.setCurrentPlayTime(singleFrameMS);
+                        mAdjustedSecondFrameTime = true;
+                    } else {
+                        if (frameNum > 1) {
+                            mRootView.post(() -> animation.removeUpdateListener(this));
+                        }
+                        if (DEBUG) print(animation);
                     }
-                    if (DEBUG) print(animation);
                 }
                 mHandlingOnAnimationUpdate = false;
             } else {
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index b747d62..e2b7b68 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -50,9 +50,9 @@
     /**
      * The apps and shortcuts
      */
-    public ArrayList<WorkspaceItemInfo> contents = new ArrayList<WorkspaceItemInfo>();
+    public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>();
 
-    ArrayList<FolderListener> listeners = new ArrayList<FolderListener>();
+    private ArrayList<FolderListener> mListeners = new ArrayList<>();
 
     public FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
@@ -72,10 +72,10 @@
      * Add an app or shortcut for a specified rank.
      */
     public void add(WorkspaceItemInfo item, int rank, boolean animate) {
-        rank = Utilities.boundToRange(rank, 0, contents.size());
+        rank = Utilities.boundToRange(rank, 0, contents.size() + 1);
         contents.add(rank, item);
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onAdd(item, rank);
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onAdd(item, rank);
         }
         itemsChanged(animate);
     }
@@ -87,53 +87,37 @@
      */
     public void remove(WorkspaceItemInfo item, boolean animate) {
         contents.remove(item);
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onRemove(item);
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onRemove(item);
         }
         itemsChanged(animate);
     }
 
-    public void setTitle(CharSequence title) {
-        this.title = title;
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onTitleChanged(title);
-        }
-    }
-
     @Override
     public void onAddToDatabase(ContentWriter writer) {
         super.onAddToDatabase(writer);
         writer.put(LauncherSettings.Favorites.TITLE, title)
                 .put(LauncherSettings.Favorites.OPTIONS, options);
-
     }
 
     public void addListener(FolderListener listener) {
-        listeners.add(listener);
+        mListeners.add(listener);
     }
 
     public void removeListener(FolderListener listener) {
-        listeners.remove(listener);
+        mListeners.remove(listener);
     }
 
     public void itemsChanged(boolean animate) {
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).onItemsChanged(animate);
-        }
-    }
-
-    public void prepareAutoUpdate() {
-        for (int i = 0; i < listeners.size(); i++) {
-            listeners.get(i).prepareAutoUpdate();
+        for (int i = 0; i < mListeners.size(); i++) {
+            mListeners.get(i).onItemsChanged(animate);
         }
     }
 
     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/IconProvider.java b/src/com/android/launcher3/IconProvider.java
index e1ef954..0f006f7 100644
--- a/src/com/android/launcher3/IconProvider.java
+++ b/src/com/android/launcher3/IconProvider.java
@@ -1,16 +1,17 @@
 package com.android.launcher3;
 
-import android.content.Context;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
 import android.content.pm.LauncherActivityInfo;
 import android.graphics.drawable.Drawable;
 
+import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 public class IconProvider implements ResourceBasedOverride {
 
-    public static IconProvider newInstance(Context context) {
-        return Overrides.getObject(IconProvider.class, context, R.string.icon_provider_class);
-    }
+    public static MainThreadInitializedObject<IconProvider> INSTANCE =
+            forOverride(IconProvider.class, R.string.icon_provider_class);
 
     public IconProvider() { }
 
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index e9b932a..fe91602 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,9 +16,12 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -28,9 +31,6 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.UserHandle;
@@ -39,6 +39,9 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.icons.BitmapInfo;
@@ -65,9 +68,6 @@
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
 
-    private static final int MSG_ADD_TO_QUEUE = 1;
-    private static final int MSG_FLUSH_QUEUE = 2;
-
     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;
@@ -100,65 +100,57 @@
     public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
     public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
 
-    private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) {
+    @WorkerThread
+    private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
+        String encoded = info.encodeToString();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
+        strings.add(encoded);
+        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+    }
 
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ADD_TO_QUEUE: {
-                    Pair<Context, PendingInstallShortcutInfo> pair =
-                            (Pair<Context, PendingInstallShortcutInfo>) msg.obj;
-                    String encoded = pair.second.encodeToString();
-                    SharedPreferences prefs = Utilities.getPrefs(pair.first);
-                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-                    strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1);
-                    strings.add(encoded);
-                    prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
-                    return;
-                }
-                case MSG_FLUSH_QUEUE: {
-                    Context context = (Context) msg.obj;
-                    LauncherModel model = LauncherAppState.getInstance(context).getModel();
-                    if (model.getCallback() == null) {
-                        // Launcher not loaded
-                        return;
-                    }
-
-                    ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-                    SharedPreferences prefs = Utilities.getPrefs(context);
-                    Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-                    if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-                    if (strings == null) {
-                        return;
-                    }
-
-                    LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
-                    for (String encoded : strings) {
-                        PendingInstallShortcutInfo info = decode(encoded, context);
-                        if (info == null) {
-                            continue;
-                        }
-
-                        String pkg = getIntentPackage(info.launchIntent);
-                        if (!TextUtils.isEmpty(pkg)
-                                && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) {
-                            if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: "
-                                    + info.launchIntent);
-                            continue;
-                        }
-
-                        // Generate a shortcut info to add into the model
-                        installQueue.add(info.getItemInfo());
-                    }
-                    prefs.edit().remove(APPS_PENDING_INSTALL).apply();
-                    if (!installQueue.isEmpty()) {
-                        model.addAndBindAddedWorkspaceItems(installQueue);
-                    }
-                    return;
-                }
-            }
+    @WorkerThread
+    private static void flushQueueInBackground(Context context) {
+        LauncherModel model = LauncherAppState.getInstance(context).getModel();
+        if (model.getCallback() == null) {
+            // Launcher not loaded
+            return;
         }
-    };
+
+        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
+        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
+        if (strings == null) {
+            return;
+        }
+
+        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        for (String encoded : strings) {
+            PendingInstallShortcutInfo info = decode(encoded, context);
+            if (info == null) {
+                continue;
+            }
+
+            String pkg = getIntentPackage(info.launchIntent);
+            if (!TextUtils.isEmpty(pkg)
+                    && !launcherApps.isPackageEnabledForProfile(pkg, info.user)
+                    && !info.isActivity) {
+                if (DBG) {
+                    Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
+                }
+                continue;
+            }
+
+            // Generate a shortcut info to add into the model
+            installQueue.add(info.getItemInfo());
+        }
+        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+        if (!installQueue.isEmpty()) {
+            model.addAndBindAddedWorkspaceItems(installQueue);
+        }
+    }
 
     public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
             UserHandle user) {
@@ -249,10 +241,6 @@
         return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
     }
 
-    public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) {
-        return (WorkspaceItemInfo) (new PendingInstallShortcutInfo(info, context).getItemInfo().first);
-    }
-
     public static void queueShortcut(ShortcutInfo info, Context context) {
         queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
     }
@@ -261,8 +249,9 @@
         queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
     }
 
-    public static void queueActivityInfo(LauncherActivityInfo activity, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context);
+    public static void queueApplication(Intent data, UserHandle user, Context context) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user),
+                context);
     }
 
     public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
@@ -288,7 +277,7 @@
 
     private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
         // Queue the item up for adding if launcher has not loaded properly yet
-        Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget();
+        MODEL_EXECUTOR.post(() -> addToQueue(context, info));
         flushInstallQueue(context);
     }
 
@@ -304,7 +293,7 @@
         if (sInstallQueueDisabledFlags != 0) {
             return;
         }
-        Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget();
+        MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
     }
 
     /**
@@ -326,11 +315,11 @@
 
     private static class PendingInstallShortcutInfo {
 
-        final LauncherActivityInfo activityInfo;
-        final ShortcutInfo shortcutInfo;
-        final AppWidgetProviderInfo providerInfo;
+        final boolean isActivity;
+        @Nullable final ShortcutInfo shortcutInfo;
+        @Nullable final AppWidgetProviderInfo providerInfo;
 
-        final Intent data;
+        @Nullable final Intent data;
         final Context mContext;
         final Intent launchIntent;
         final String label;
@@ -340,7 +329,7 @@
          * Initializes a PendingInstallShortcutInfo received from a different app.
          */
         public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
-            activityInfo = null;
+            isActivity = false;
             shortcutInfo = null;
             providerInfo = null;
 
@@ -350,18 +339,22 @@
 
             launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
             label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
         }
 
         /**
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
         public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
-            activityInfo = info;
+            isActivity = true;
             shortcutInfo = null;
             providerInfo = null;
 
-            data = null;
+            String packageName = info.getComponentName().getPackageName();
+            data = new Intent();
+            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+                    new ComponentName(packageName, "")).setPackage(packageName));
+            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
+
             user = info.getUser();
             mContext = context;
 
@@ -372,8 +365,24 @@
         /**
          * Initializes a PendingInstallShortcutInfo to represent a launcher target.
          */
+        public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) {
+            isActivity = true;
+            shortcutInfo = null;
+            providerInfo = null;
+
+            this.data = data;
+            this.user = user;
+            mContext = context;
+
+            launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+            label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+         */
         public PendingInstallShortcutInfo(ShortcutInfo info, Context context) {
-            activityInfo = null;
+            isActivity = false;
             shortcutInfo = info;
             providerInfo = null;
 
@@ -390,7 +399,7 @@
          */
         public PendingInstallShortcutInfo(
                 AppWidgetProviderInfo info, int widgetId, Context context) {
-            activityInfo = null;
+            isActivity = false;
             shortcutInfo = null;
             providerInfo = info;
 
@@ -405,17 +414,7 @@
 
         public String encodeToString() {
             try {
-                if (activityInfo != null) {
-                    // If it a launcher target, we only need component name, and user to
-                    // recreate this.
-                    return new JSONStringer()
-                        .object()
-                        .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                        .key(APP_SHORTCUT_TYPE_KEY).value(true)
-                        .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext)
-                                .getSerialNumberForUser(user))
-                        .endObject().toString();
-                } else if (shortcutInfo != null) {
+                if (shortcutInfo != null) {
                     // If it a launcher target, we only need component name, and user to
                     // recreate this.
                     return new JSONStringer()
@@ -449,20 +448,26 @@
                 // This name is only used for comparisons and notifications, so fall back to activity
                 // name if not supplied
                 String name = ensureValidName(mContext, launchIntent, label).toString();
-                Bitmap icon = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-                Intent.ShortcutIconResource iconResource =
-                    data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                Bitmap icon = data == null ? null
+                        : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+                Intent.ShortcutIconResource iconResource = data == null ? null
+                    : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
 
                 // Only encode the parameters which are supported by the API.
                 JSONStringer json = new JSONStringer()
                     .object()
                     .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                    .key(NAME_KEY).value(name);
+                    .key(NAME_KEY).value(name)
+                    .key(USER_HANDLE_KEY).value(
+                            UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user))
+                    .key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
                 if (icon != null) {
                     byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
-                    json = json.key(ICON_KEY).value(
-                            Base64.encodeToString(
-                                    iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+                    if (iconByteArray != null) {
+                        json = json.key(ICON_KEY).value(
+                                Base64.encodeToString(
+                                        iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
+                    }
                 }
                 if (iconResource != null) {
                     json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
@@ -477,29 +482,18 @@
         }
 
         public Pair<ItemInfo, Object> getItemInfo() {
-            if (activityInfo != null) {
-                AppInfo appInfo = new AppInfo(mContext, activityInfo, user);
-                final LauncherAppState app = LauncherAppState.getInstance(mContext);
-                // Set default values until proper values is loaded.
-                appInfo.title = "";
-                appInfo.applyFrom(app.getIconCache().getDefaultIcon(user));
-                final WorkspaceItemInfo si = appInfo.makeWorkspaceItem();
-                if (Looper.myLooper() == LauncherModel.getWorkerLooper()) {
-                    app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */);
-                } else {
-                    app.getModel().updateAndBindWorkspaceItem(() -> {
-                        app.getIconCache().getTitleAndIcon(
-                                si, activityInfo, false /* useLowResIcon */);
-                        return si;
-                    });
-                }
-                return Pair.create((ItemInfo) si, (Object) activityInfo);
+            if (isActivity) {
+                WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
+                        LauncherAppState.getInstance(mContext));
+                si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+                si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+                return Pair.create(si, null);
             } else if (shortcutInfo != null) {
-                WorkspaceItemInfo si = new WorkspaceItemInfo(shortcutInfo, mContext);
+                WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
                 LauncherIcons li = LauncherIcons.obtain(mContext);
-                si.applyFrom(li.createShortcutIcon(shortcutInfo));
+                itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo));
                 li.recycle();
-                return Pair.create((ItemInfo) si, (Object) shortcutInfo);
+                return Pair.create(itemInfo, shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
                         .fromProviderInfo(mContext, providerInfo);
@@ -511,15 +505,16 @@
                 widgetInfo.minSpanY = info.minSpanY;
                 widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
                 widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
-                return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo);
+                return Pair.create(widgetInfo, providerInfo);
             } else {
-                WorkspaceItemInfo si = createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext));
-                return Pair.create((ItemInfo) si, null);
+                WorkspaceItemInfo itemInfo =
+                        createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
+                return Pair.create(itemInfo, null);
             }
         }
 
         public boolean isLauncherActivity() {
-            return activityInfo != null;
+            return isActivity;
         }
     }
 
@@ -534,7 +529,9 @@
             if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
                 LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
                         .resolveActivity(decoder.launcherIntent, decoder.user);
-                return info == null ? null : new PendingInstallShortcutInfo(info, context);
+                if (info != null) {
+                    return new PendingInstallShortcutInfo(info, context);
+                }
             } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
                 DeepShortcutManager sm = DeepShortcutManager.getInstance(context);
                 List<ShortcutInfo> si = sm.queryForFullDetails(
@@ -578,7 +575,11 @@
                 data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
             }
 
-            return new PendingInstallShortcutInfo(data, decoder.user, context);
+            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
+                return new PendingInstallShortcutInfo(data, context, decoder.user);
+            } else {
+                return new PendingInstallShortcutInfo(data, decoder.user, context);
+            }
         } catch (JSONException | URISyntaxException e) {
             Log.d(TAG, "Exception reading shortcut to add: " + e);
         }
@@ -612,7 +613,7 @@
             // Already an activity target
             return original;
         }
-        if (!Utilities.isLauncherAppTarget(original.launchIntent)) {
+        if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) {
             return original;
         }
 
@@ -625,7 +626,13 @@
         return new PendingInstallShortcutInfo(info, original.mContext);
     }
 
-    private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+    private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
+            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);
@@ -637,10 +644,7 @@
         }
 
         final WorkspaceItemInfo info = new WorkspaceItemInfo();
-
-        // Only support intents for current user for now. Intents sent from other
-        // users wouldn't get here without intent forwarding anyway.
-        info.user = Process.myUserHandle();
+        info.user = user;
 
         BitmapInfo iconInfo = null;
         LauncherIcons li = LauncherIcons.obtain(app.getContext());
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index bde87cb..d66ba73 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,6 +18,8 @@
 
 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;
@@ -39,11 +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;
@@ -54,9 +58,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
+import java.util.Comparator;
 
 public class InvariantDeviceProfile {
 
@@ -102,6 +104,8 @@
     public int iconBitmapSize;
     public int fillResIconDpi;
     public float iconTextSize;
+    public float allAppsIconSize;
+    public float allAppsIconTextSize;
 
     private SparseArray<TypedValue> mExtraAttrs;
 
@@ -110,6 +114,11 @@
      */
     public int numHotseatIcons;
 
+    /**
+     * Number of columns in the all apps list.
+     */
+    public int numAllAppsColumns;
+
     public int defaultLayoutId;
     int demoModeLayoutId;
 
@@ -136,6 +145,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;
@@ -144,7 +156,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);
@@ -172,63 +187,81 @@
     }
 
     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);
-        // 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),
-                        dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps)));
-        DisplayOption interpolatedDisplayOption =
-                invDistWeightedInterpolate(minWidthDps,  minHeightDps, allOptions);
+        float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y),
+                displayInfo.metrics);
+        float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y),
+                displayInfo.metrics);
 
-        GridOption closestProfile = allOptions.get(0).grid;
-        numRows = closestProfile.numRows;
-        numColumns = closestProfile.numColumns;
-        numHotseatIcons = closestProfile.numHotseatIcons;
-        defaultLayoutId = closestProfile.defaultLayoutId;
-        demoModeLayoutId = closestProfile.demoModeLayoutId;
-        numFolderRows = closestProfile.numFolderRows;
-        numFolderColumns = closestProfile.numFolderColumns;
-        mExtraAttrs = closestProfile.extraAttrs;
-
-        if (!closestProfile.name.equals(gridName)) {
-            Utilities.getPrefs(context).edit()
-                    .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
-        }
-
-        iconSize = interpolatedDisplayOption.iconSize;
-        iconShapePath = getIconShapePath(context);
-        landscapeIconSize = interpolatedDisplayOption.landscapeIconSize;
-        iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm);
-        iconTextSize = interpolatedDisplayOption.iconTextSize;
-        fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
-
-        // If the partner customization apk contains any grid overrides, apply them
-        // Supported overrides: numRows, numColumns, iconSize
-        applyPartnerDeviceProfileOverrides(context, dm);
-
-        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);
         int largeSide = Math.max(realSize.x, realSize.y);
 
-        landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
-                largeSide, smallSide, true /* isLandscape */, false /* isMultiWindowMode */);
-        portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
-                smallSide, largeSide, false /* isLandscape */, false /* isMultiWindowMode */);
+        // We want a list of all options as well as the list of filtered options. This allows us
+        // to have a consistent UI for areas that the grid size change should not affect
+        // ie. All Apps should be consistent between grid sizes.
+        ArrayList<DisplayOption> allOptions = new ArrayList<>();
+        ArrayList<DisplayOption> filteredOptions = new ArrayList<>();
+        getPredefinedDeviceProfiles(context, gridName, filteredOptions, allOptions);
+
+        if (allOptions.isEmpty() && filteredOptions.isEmpty()) {
+            throw new RuntimeException("No display option with canBeDefault=true");
+        }
+
+        // Sort the profiles based on the closeness to the device size
+        Comparator<DisplayOption> comparator = (a, b) -> Float.compare(dist(minWidthDps,
+                minHeightDps, a.minWidthDps, a.minHeightDps),
+                dist(minWidthDps, minHeightDps, b.minWidthDps, b.minHeightDps));
+
+        // Calculate the device profiles as if there is no grid override.
+        Collections.sort(allOptions, comparator);
+        DisplayOption interpolatedDisplayOption =
+                invDistWeightedInterpolate(minWidthDps,  minHeightDps, allOptions);
+        initGridOption(context, allOptions, interpolatedDisplayOption, displayInfo.metrics);
+
+        // Create IDP with no grid override values.
+        InvariantDeviceProfile originalIDP = new InvariantDeviceProfile(this);
+        originalIDP.landscapeProfile = new DeviceProfile(context, this, null, smallestSize,
+                largestSize, largeSide, smallSide, true /* isLandscape */,
+                false /* isMultiWindowMode */);
+        originalIDP.portraitProfile = new DeviceProfile(context, this, null, smallestSize,
+                largestSize, smallSide, largeSide, false /* isLandscape */,
+                false /* isMultiWindowMode */);
+
+        if (filteredOptions.isEmpty()) {
+            filteredOptions = allOptions;
+
+            landscapeProfile = originalIDP.landscapeProfile;
+            portraitProfile = originalIDP.portraitProfile;
+        } else {
+            Collections.sort(filteredOptions, comparator);
+            interpolatedDisplayOption =
+                    invDistWeightedInterpolate(minWidthDps, minHeightDps, filteredOptions);
+
+            initGridOption(context, filteredOptions, interpolatedDisplayOption,
+                    displayInfo.metrics);
+            numAllAppsColumns = originalIDP.numAllAppsColumns;
+
+            landscapeProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+                    largestSize, largeSide, smallSide, true /* isLandscape */,
+                    false /* isMultiWindowMode */);
+            portraitProfile = new DeviceProfile(context, this, originalIDP, smallestSize,
+                    largestSize, smallSide, largeSide, false /* isLandscape */,
+                    false /* isMultiWindowMode */);
+        }
+
+        GridOption closestProfile = filteredOptions.get(0).grid;
+        if (!closestProfile.name.equals(gridName)) {
+            Utilities.getPrefs(context).edit()
+                    .putString(KEY_IDP_GRID_NAME, closestProfile.name).apply();
+        }
 
         // We need to ensure that there is enough extra space in the wallpaper
         // for the intended parallax effects
@@ -246,6 +279,33 @@
         return closestProfile.name;
     }
 
+    private void initGridOption(Context context, ArrayList<DisplayOption> options,
+            DisplayOption displayOption, DisplayMetrics metrics) {
+        GridOption closestProfile = options.get(0).grid;
+        numRows = closestProfile.numRows;
+        numColumns = closestProfile.numColumns;
+        numHotseatIcons = closestProfile.numHotseatIcons;
+        defaultLayoutId = closestProfile.defaultLayoutId;
+        demoModeLayoutId = closestProfile.demoModeLayoutId;
+        numFolderRows = closestProfile.numFolderRows;
+        numFolderColumns = closestProfile.numFolderColumns;
+        numAllAppsColumns = numColumns;
+
+        mExtraAttrs = closestProfile.extraAttrs;
+
+        iconSize = displayOption.iconSize;
+        iconShapePath = getIconShapePath(context);
+        landscapeIconSize = displayOption.landscapeIconSize;
+        iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
+        iconTextSize = displayOption.iconTextSize;
+        fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+
+        // If the partner customization apk contains any grid overrides, apply them
+        // Supported overrides: numRows, numColumns, iconSize
+        applyPartnerDeviceProfileOverrides(context, metrics);
+    }
+
+
     @Nullable
     public TypedValue getAttrValue(int attr) {
         return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
@@ -280,7 +340,7 @@
     public void setCurrentGrid(Context context, String gridName) {
         Context appContext = context.getApplicationContext();
         Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
-        new MainThreadExecutor().execute(() -> onConfigChanged(appContext));
+        MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
     }
 
     private void onConfigChanged(Context context) {
@@ -288,9 +348,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 ||
@@ -322,7 +383,13 @@
         }
     }
 
-    static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context, String gridName) {
+    /**
+     * @param gridName The current grid name.
+     * @param filteredOptionsOut List filled with all the filtered options based on gridName.
+     * @param allOptionsOut List filled with all the options that can be the default option.
+     */
+    static void getPredefinedDeviceProfiles(Context context, String gridName,
+            ArrayList<DisplayOption> filteredOptionsOut, ArrayList<DisplayOption> allOptionsOut) {
         ArrayList<DisplayOption> profiles = new ArrayList<>();
         try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
             final int depth = parser.getDepth();
@@ -349,26 +416,19 @@
             throw new RuntimeException(e);
         }
 
-        ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
         if (!TextUtils.isEmpty(gridName)) {
             for (DisplayOption option : profiles) {
                 if (gridName.equals(option.grid.name)) {
-                    filteredProfiles.add(option);
+                    filteredOptionsOut.add(option);
                 }
             }
         }
-        if (filteredProfiles.isEmpty()) {
-            // No grid found, use the default options
-            for (DisplayOption option : profiles) {
-                if (option.canBeDefault) {
-                    filteredProfiles.add(option);
-                }
+
+        for (DisplayOption option : profiles) {
+            if (option.canBeDefault) {
+                allOptionsOut.add(option);
             }
         }
-        if (filteredProfiles.isEmpty()) {
-            throw new RuntimeException("No display option with canBeDefault=true");
-        }
-        return filteredProfiles;
     }
 
     private int getLauncherIconDensity(int requiredSize) {
@@ -514,6 +574,7 @@
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
             numFolderColumns = a.getInt(
                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
+
             a.recycle();
 
             extraAttrs = Themes.createValueMap(context, attrs,
@@ -530,8 +591,8 @@
         private final boolean canBeDefault;
 
         private float iconSize;
-        private float landscapeIconSize;
         private float iconTextSize;
+        private float landscapeIconSize;
 
         DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
             this.grid = grid;
@@ -549,6 +610,7 @@
             landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
                     iconSize);
             iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+
             a.recycle();
         }
 
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 134e116..3f723d1 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -22,6 +22,8 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.util.ContentWriter;
 
 /**
@@ -134,6 +136,7 @@
         return null;
     }
 
+    @Nullable
     public ComponentName getTargetComponent() {
         Intent intent = getIntent();
         if (intent != null) {
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index e29f927..1550bb0 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -27,6 +27,8 @@
  */
 public abstract class ItemInfoWithIcon extends ItemInfo {
 
+    public static final String TAG = "ItemInfoDebug";
+
     /**
      * A bitmap version of the application icon.
      */
@@ -126,4 +128,8 @@
         iconColor = info.color;
     }
 
+    /**
+     * @return a copy of this
+     */
+    public abstract ItemInfoWithIcon clone();
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bc3aa7e..1338b30 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -16,10 +16,11 @@
 
 package com.android.launcher3;
 
-import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -77,6 +78,9 @@
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsContainerView;
@@ -91,9 +95,9 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -103,14 +107,14 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.model.AppLaunchTracker;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -127,6 +131,7 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.RaceConditionTracker;
+import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
@@ -144,7 +149,7 @@
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetListRowEntry;
 import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -155,13 +160,11 @@
 import java.util.List;
 import java.util.function.Predicate;
 
-import androidx.annotation.Nullable;
-
 /**
  * Default launcher application.
  */
 public class Launcher extends BaseDraggingActivity implements LauncherExterns,
-        LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate,
+        Callbacks, LauncherProviderChangeListener, UserEventDelegate,
         InvariantDeviceProfile.OnIDPChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
@@ -208,9 +211,9 @@
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
     // How long to wait before the new-shortcut animation automatically pans the workspace
-    private static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
+    @VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
-    @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
+    @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
 
     private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
     private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
@@ -218,9 +221,11 @@
     private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
-    @Thunk Workspace mWorkspace;
+    @Thunk
+    Workspace mWorkspace;
     private View mLauncherView;
-    @Thunk DragLayer mDragLayer;
+    @Thunk
+    DragLayer mDragLayer;
     private DragController mDragController;
 
     private AppWidgetManagerCompat mAppWidgetManager;
@@ -228,21 +233,25 @@
 
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
-    @Thunk Hotseat mHotseat;
+    @Thunk
+    Hotseat mHotseat;
 
     private DropTargetBar mDropTargetBar;
 
     // Main container view for the all apps screen.
-    @Thunk AllAppsContainerView mAppsView;
+    @Thunk
+    AllAppsContainerView mAppsView;
     AllAppsTransitionController mAllAppsController;
 
     // Scrim view for the all apps and overview state.
-    @Thunk ScrimView mScrimView;
+    @Thunk
+    ScrimView mScrimView;
 
     // UI and state for the overview panel
     private View mOverviewPanel;
 
-    @Thunk boolean mWorkspaceLoading = true;
+    @Thunk
+    boolean mWorkspaceLoading = true;
 
     private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
 
@@ -324,8 +333,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);
@@ -387,7 +396,8 @@
         RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT);
         mStateManager.addStateListener(new LauncherStateManager.StateListener() {
             @Override
-            public void onStateTransitionStart(LauncherState toState) {}
+            public void onStateTransitionStart(LauncherState toState) {
+            }
 
             @Override
             public void onStateTransitionComplete(LauncherState finalState) {
@@ -417,10 +427,6 @@
     public void onConfigurationChanged(Configuration newConfig) {
         int diff = newConfig.diff(mOldConfig);
 
-        if ((diff & CONFIG_LOCALE) != 0) {
-            Folder.setLocaleDependentFields(getResources(), true /* force */);
-        }
-
         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
             onIdpChanged(mDeviceProfile.inv);
         }
@@ -440,12 +446,16 @@
 
     @Override
     public void reapplyUi() {
+        reapplyUi(true /* cancelCurrentAnimation */);
+    }
+
+    public void reapplyUi(boolean cancelCurrentAnimation) {
         if (supportsFakeLandscapeUI()) {
             mRotationMode = mStableDeviceProfile == null
                     ? RotationMode.NORMAL : UiFactory.getRotationMode(mDeviceProfile);
         }
         getRootView().dispatchInsets();
-        getStateManager().reapplyState(true /* cancelCurrentAnimation */);
+        getStateManager().reapplyState(cancelCurrentAnimation);
     }
 
     @Override
@@ -495,6 +505,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);
@@ -546,6 +558,10 @@
         return mStateManager;
     }
 
+    public FolderNameProvider getFolderNameProvider() {
+        return new FolderNameProvider();
+    }
+
     @Override
     public <T extends View> T findViewById(int id) {
         return mLauncherView.findViewById(id);
@@ -602,10 +618,9 @@
         if (info.container >= 0) {
             View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
             if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
-                FolderIconPreviewVerifier verifier =
-                        new FolderIconPreviewVerifier(getDeviceProfile().inv);
-                verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
-                if (verifier.isItemInPreview(info.rank)) {
+                if (new FolderGridOrganizer(getDeviceProfile().inv)
+                        .setFolderInfo((FolderInfo) folderIcon.getTag())
+                        .isItemInPreview(info.rank)) {
                     folderIcon.invalidate();
                 }
             }
@@ -645,7 +660,8 @@
                             .getLauncherAppWidgetInfo(widgetId);
                     if (provider != null) {
                         new WidgetAddFlowHandler(provider)
-                                .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET);
+                                .startConfigActivity(this, widgetInfo,
+                                        REQUEST_RECONFIGURE_APPWIDGET);
                     }
                 }
                 break;
@@ -833,7 +849,8 @@
         }
     }
 
-    @Thunk void completeTwoStageWidgetDrop(
+    @Thunk
+    void completeTwoStageWidgetDrop(
             final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
         CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId);
         Runnable onCompleteRunnable = null;
@@ -873,9 +890,7 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
-
-        getUserEventDispatcher().logActionCommand(Action.Command.STOP,
-                mStateManager.getState().containerType, -1);
+        logStopAndResume(Action.Command.STOP);
 
         mAppWidgetHost.setListenIfResumed(false);
 
@@ -890,9 +905,6 @@
 
     @Override
     protected void onStart() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "Launcher.onStart");
-        }
         RaceConditionTracker.onEvent(ON_START_EVT, ENTER);
         super.onStart();
         if (mLauncherCallbacks != null) {
@@ -904,8 +916,7 @@
 
     private void handleDeferredResume() {
         if (hasBeenResumed() && !mStateManager.getState().disableInteraction) {
-            getUserEventDispatcher().logActionCommand(Action.Command.RESUME,
-                    mStateManager.getState().containerType, -1);
+            logStopAndResume(Action.Command.RESUME);
             getUserEventDispatcher().startSession();
 
             UiFactory.onLauncherStateOrResumeChanged(this);
@@ -932,8 +943,18 @@
         }
     }
 
-    protected void onStateSet(LauncherState state) {
-        getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+    private void logStopAndResume(int command) {
+        int containerType = mStateManager.getState().containerType;
+        if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
+            getUserEventDispatcher().logActionCommand(command,
+                    containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0);
+        } else {
+            getUserEventDispatcher().logActionCommand(command, containerType, -1);
+        }
+
+    }
+
+    public void onStateSetStart(LauncherState state) {
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
@@ -942,6 +963,12 @@
         }
     }
 
+    public void onStateSetEnd(LauncherState state) {
+        getAppWidgetHost().setResumed(state == LauncherState.NORMAL);
+        getWorkspace().setClipChildren(!state.disablePageClipping);
+        finishAutoCancelActionMode();
+    }
+
     @Override
     protected void onResume() {
         RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER);
@@ -1054,7 +1081,8 @@
             mStateManager.goToState(state, false /* animated */);
         }
 
-        PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS);
+        PendingRequestArgs requestArgs = savedState.getParcelable(
+                RUNTIME_STATE_PENDING_REQUEST_ARGS);
         if (requestArgs != null) {
             setWaitingForResult(requestArgs);
         }
@@ -1124,8 +1152,7 @@
      * Creates a view representing a shortcut inflated from the specified resource.
      *
      * @param parent The group the shortcut belongs to.
-     * @param info The data structure describing the shortcut.
-     *
+     * @param info   The data structure describing the shortcut.
      * @return A View inflated from layoutResId.
      */
     public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
@@ -1227,7 +1254,8 @@
      *
      * @param appWidgetId The app widget id
      */
-    @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
+    @Thunk
+    void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
             AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
 
         if (appWidgetInfo == null) {
@@ -1345,7 +1373,9 @@
         return mSharedPrefs;
     }
 
-    public int getOrientation() { return mOldConfig.orientation; }
+    public int getOrientation() {
+        return mOldConfig.orientation;
+    }
 
     @Override
     protected void onNewIntent(Intent intent) {
@@ -1367,6 +1397,7 @@
             if (!internalStateHandled) {
                 // In all these cases, only animate if we're already on home
                 AbstractFloatingView.closeAllOpenViews(this, isStarted());
+                UiFactory.closeSystemWindows();
 
                 if (!isInState(NORMAL)) {
                     // Only change state, if not already the same. This prevents cancelling any
@@ -1429,9 +1460,10 @@
             outState.remove(RUNTIME_STATE_WIDGET_PANEL);
         }
 
-        // We close any open folders and shortcut containers since they will not be re-opened,
+        // We close any open folders and shortcut containers that are not safe for rebind,
         // and we need to make sure this state is reflected.
-        AbstractFloatingView.closeAllOpenViews(this, false);
+        AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE);
+        finishAutoCancelActionMode();
 
         if (mPendingRequestArgs != null) {
             outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
@@ -1568,7 +1600,8 @@
 
     void addAppWidgetImpl(int appWidgetId, ItemInfo info,
             AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
-        if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) {
+        if (!addFlowHandler.startConfigActivity(this, appWidgetId, info,
+                REQUEST_CREATE_APPWIDGET)) {
             // If the configuration flow was not started, add the widget
 
             Runnable onComplete = new Runnable() {
@@ -1578,7 +1611,8 @@
                     mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
-            completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this));
+            completeAddAppWidget(appWidgetId, info, boundWidget,
+                    addFlowHandler.getProviderInfo(this));
             mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
         }
     }
@@ -1604,7 +1638,7 @@
                 break;
             default:
                 throw new IllegalStateException("Unknown item type: " + info.itemType);
-            }
+        }
     }
 
     /**
@@ -1640,10 +1674,9 @@
         } else {
             // In this case, we either need to start an activity to get permission to bind
             // the widget, or we need to start an activity to configure the widget, or both.
-            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS &&
-                    info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
-                appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider(
-                        this, info.componentName);
+            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
+                appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider(
+                        info.componentName);
             } else {
                 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
             }
@@ -1662,7 +1695,7 @@
     FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
             int cellY) {
         final FolderInfo folderInfo = new FolderInfo();
-        folderInfo.title = getText(R.string.folder_name);
+        folderInfo.title = "";
 
         // Update the model
         getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
@@ -1716,8 +1749,6 @@
         return true;
     }
 
-
-
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
@@ -1781,7 +1812,7 @@
 
     @Override
     public int getCurrentState() {
-        if(mStateManager.getState() == LauncherState.ALL_APPS) {
+        if (mStateManager.getState() == LauncherState.ALL_APPS) {
             return StatsLogUtils.LAUNCHER_STATE_ALLAPPS;
         } else if (mStateManager.getState() == OVERVIEW) {
             return StatsLogUtils.LAUNCHER_STATE_OVERVIEW;
@@ -1809,11 +1840,6 @@
 
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startActivitySafely outer");
-        }
-
         if (!hasBeenResumed()) {
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
@@ -1904,6 +1930,10 @@
         if (mPendingExecutor != null) {
             mPendingExecutor.markCompleted();
             mPendingExecutor = null;
+
+            // We might have set this flag previously and forgot to clear it.
+            mAppsView.getAppsStore()
+                    .disableDeferUpdatesSilently(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
         }
     }
 
@@ -1917,8 +1947,7 @@
         // Floating panels (except the full widget sheet) are associated with individual icons. If
         // we are starting a fresh bind, close all such panels as all the icons are about
         // to go away.
-        AbstractFloatingView.closeOpenViews(this, true,
-                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+        AbstractFloatingView.closeOpenViews(this, true, TYPE_ALL & ~TYPE_REBIND_SAFE);
 
         setWorkspaceLoading(true);
 
@@ -2046,7 +2075,7 @@
                     throw new RuntimeException("Invalid Item Type");
             }
 
-             /*
+            /*
              * Remove colliding items.
              */
             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
@@ -2118,6 +2147,14 @@
     }
 
     private View inflateAppWidget(LauncherAppWidgetInfo item) {
+        if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
+            item.providerName = QsbContainerView.getSearchComponentName(this);
+            if (item.providerName == null) {
+                getModelWriter().deleteItemFromDatabase(item);
+                return null;
+            }
+        }
+
         if (mIsSafeModeEnabled) {
             PendingAppWidgetHostView view =
                     new PendingAppWidgetHostView(this, item, mIconCache, true);
@@ -2164,7 +2201,8 @@
                     pendingInfo.spanY = item.spanY;
                     pendingInfo.minSpanX = item.minSpanX;
                     pendingInfo.minSpanY = item.minSpanY;
-                    Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo);
+                    Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this,
+                            pendingInfo);
 
                     boolean isDirectConfig =
                             item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
@@ -2257,9 +2295,7 @@
 
     @Override
     public void executeOnNextDraw(ViewOnDrawExecutor executor) {
-        if (mPendingExecutor != null) {
-            mPendingExecutor.markCompleted();
-        }
+        clearPendingBinds();
         mPendingExecutor = executor;
         if (!isInState(ALL_APPS)) {
             mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
@@ -2320,6 +2356,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");
     }
 
@@ -2341,7 +2382,7 @@
      *
      * Implementation of the method from LauncherModel.Callbacks.
      */
-    public void bindAllApplications(ArrayList<AppInfo> apps) {
+    public void bindAllApplications(AppInfo[] apps) {
         mAppsView.getAppsStore().setApps(apps);
     }
 
@@ -2354,16 +2395,6 @@
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
-    /**
-     * A package was updated.
-     *
-     * Implementation of the method from LauncherModel.Callbacks.
-     */
-    @Override
-    public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) {
-        mAppsView.getAppsStore().addOrUpdateApps(apps);
-    }
-
     @Override
     public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
         mAppsView.getAppsStore().updatePromiseAppProgress(app);
@@ -2411,11 +2442,6 @@
     }
 
     @Override
-    public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) {
-        mAppsView.getAppsStore().removeApps(appInfos);
-    }
-
-    @Override
     public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) {
         mPopupDataProvider.setAllWidgets(allWidgets);
     }
@@ -2460,14 +2486,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);
@@ -2500,7 +2528,7 @@
                         KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
             }
             if (currentFocus.getTag() instanceof ItemInfo
-                    && DeepShortcutManager.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
+                    && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
                 shortcutInfos.add(new KeyboardShortcutInfo(
                         getString(R.string.shortcuts_menu_with_notifications_description),
                         KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
@@ -2528,8 +2556,8 @@
                     if (focusedView instanceof BubbleTextView
                             && focusedView.getTag() instanceof ItemInfo
                             && mAccessibilityDelegate.performAction(focusedView,
-                                    (ItemInfo) focusedView.getTag(),
-                                    LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
+                            (ItemInfo) focusedView.getTag(),
+                            LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) {
                         PopupContainerWithArrow.getOpen(this).requestFocus();
                         return true;
                     }
@@ -2574,6 +2602,12 @@
         return (Launcher) fromContext(context);
     }
 
+    @Override
+    public void returnToHomescreen() {
+        super.returnToHomescreen();
+        getStateManager().goToState(LauncherState.NORMAL);
+    }
+
     /**
      * Just a wrapper around the type cast to allow easier tracking of calls.
      */
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index d07638a..d70abc2 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,14 +19,12 @@
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
-import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
-import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -39,6 +37,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 public class LauncherAppState {
 
@@ -46,7 +45,7 @@
 
     // We do not need any synchronization for this variable as its only written on UI thread.
     private static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
-            new MainThreadInitializedObject<>((c) -> new LauncherAppState(c));
+            new MainThreadInitializedObject<>(LauncherAppState::new);
 
     private final Context mContext;
     private final LauncherModel mModel;
@@ -96,6 +95,7 @@
         if (FeatureFlags.IS_DOGFOOD_BUILD) {
             filter.addAction(ACTION_FORCE_ROLOAD);
         }
+        FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload);
 
         mContext.registerReceiver(mModel, filter);
         UserManagerCompat.getInstance(mContext).enableAndResetCache();
@@ -150,6 +150,8 @@
     LauncherModel setLauncher(Launcher launcher) {
         getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
+        CustomWidgetManager.INSTANCE.get(launcher)
+                .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
         return mModel;
     }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 7f5ac52..1215d43 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -27,14 +27,15 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.util.SparseArray;
-import android.view.LayoutInflater;
 import android.widget.Toast;
 
 import com.android.launcher3.config.FeatureFlags;
 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
@@ -105,6 +114,10 @@
         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.
@@ -180,10 +193,8 @@
             LauncherAppWidgetProviderInfo appWidget) {
         if (appWidget.isCustomWidget()) {
             LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
-            LayoutInflater inflater = (LayoutInflater)
-                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            inflater.inflate(appWidget.initialLayout, lahv);
             lahv.setAppWidget(0, appWidget);
+            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
             return lahv;
         } else if ((mFlags & FLAG_LISTENING) == 0) {
             DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
@@ -207,7 +218,7 @@
                 }
                 view.setAppWidget(appWidgetId, appWidget);
                 view.switchToErrorView();
-                return  view;
+                return view;
             }
         }
     }
@@ -225,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);
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 051846c..b824301 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -29,6 +29,9 @@
  */
 public class LauncherAppWidgetInfo extends ItemInfo {
 
+    public static final int OPTION_SEARCH_WIDGET = 1;
+
+
     public static final int RESTORE_COMPLETED = 0;
 
     /**
@@ -97,6 +100,11 @@
     public Intent bindOptions;
 
     /**
+     * Widget options
+     */
+    public int options;
+
+    /**
      * Nonnull for pending widgets. We use this to get the icon and title for the widget.
      */
     public PackageItemInfo pendingItemInfo;
@@ -137,6 +145,7 @@
         writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId)
                 .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString())
                 .put(LauncherSettings.Favorites.RESTORED, restoreStatus)
+                .put(LauncherSettings.Favorites.OPTIONS, options)
                 .put(LauncherSettings.Favorites.INTENT, bindOptions);
     }
 
@@ -164,4 +173,13 @@
     public final boolean hasRestoreFlag(int flag) {
         return (restoreStatus & flag) == flag;
     }
+
+    /**
+     * returns if widget options include an option or not
+     * @param option
+     * @return
+     */
+    public final boolean hasOptionFlag(int option) {
+        return (options & option) != 0;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d79f5d5..a012412 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -18,28 +18,32 @@
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
 import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+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.ShortcutInfo;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
+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.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;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.CacheDataUpdatedTask;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
@@ -49,28 +53,22 @@
 import com.android.launcher3.model.ShortcutsChangedTask;
 import com.android.launcher3.model.UserLockStateChangedTask;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
+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;
-import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
-import androidx.annotation.Nullable;
-
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
  * LauncherModel object held in a static. Also provide APIs for updating the database state
@@ -82,21 +80,12 @@
 
     static final String TAG = "Launcher.Model";
 
-    private final MainThreadExecutor mUiExecutor = new MainThreadExecutor();
     @Thunk final LauncherAppState mApp;
     @Thunk final Object mLock = new Object();
     @Thunk
     LoaderTask mLoaderTask;
     @Thunk boolean mIsLoaderTaskRunning;
 
-    @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
-    private static final Looper mWorkerLooper;
-    static {
-        sWorkerThread.start();
-        mWorkerLooper = sWorkerThread.getLooper();
-    }
-    @Thunk static final Handler sWorker = new Handler(mWorkerLooper);
-
     // Indicates whether the current model data is valid or not.
     // We start off with everything not loaded. After that, we assume that
     // our monitoring of the package manager provides all updates and we never
@@ -133,33 +122,6 @@
         }
     };
 
-    public interface Callbacks {
-        public void rebindModel();
-
-        public int getCurrentWorkspaceScreen();
-        public void clearPendingBinds();
-        public void startBinding();
-        public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
-        public void bindScreens(IntArray orderedScreenIds);
-        public void finishFirstPageBind(ViewOnDrawExecutor executor);
-        public void finishBindingItems(int pageBoundFirst);
-        public void bindAllApplications(ArrayList<AppInfo> apps);
-        public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
-        public void preAddApps();
-        public void bindAppsAdded(IntArray newScreens,
-                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
-        public void bindPromiseAppProgressUpdated(PromiseAppInfo app);
-        public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
-        public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
-        public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
-        public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
-        public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
-        public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
-        public void onPageBoundSynchronously(int page);
-        public void executeOnNextDraw(ViewOnDrawExecutor executor);
-        public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
-    }
-
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
@@ -172,11 +134,11 @@
     /**
      * Updates the icons and label of all pending icons for the provided package name.
      */
-    public void updateSessionDisplayInfo(final String packageName) {
+    public void updateSessionDisplayInfo(final String packageName, final UserHandle user) {
         HashSet<String> packages = new HashSet<>();
         packages.add(packageName);
         enqueueModelUpdateTask(new CacheDataUpdatedTask(
-                CacheDataUpdatedTask.OP_SESSION_UPDATE, Process.myUserHandle(), packages));
+                CacheDataUpdatedTask.OP_SESSION_UPDATE, user, packages));
     }
 
     /**
@@ -211,6 +173,30 @@
         enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName));
     }
 
+    public void onSessionFailure(String packageName, UserHandle user) {
+        enqueueModelUpdateTask(new BaseModelUpdateTask() {
+            @Override
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                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 onPackageRemoved(String packageName, UserHandle user) {
         onPackagesRemoved(user, packageName);
@@ -218,6 +204,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));
     }
 
@@ -273,7 +260,6 @@
     @Override
     public void onReceive(Context context, 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.
@@ -349,7 +335,7 @@
             if (mCallbacks != null && mCallbacks.get() != null) {
                 final Callbacks oldCallbacks = mCallbacks.get();
                 // Clear any pending bind-runnables from the synchronized load process.
-                mUiExecutor.execute(oldCallbacks::clearPendingBinds);
+                MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds);
 
                 // If there is already one running, tell it to stop.
                 stopLoader();
@@ -393,7 +379,7 @@
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
-            sWorker.post(mLoaderTask);
+            MODEL_EXECUTOR.post(mLoaderTask);
         }
     }
 
@@ -411,16 +397,7 @@
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
                 apps.addPromiseApp(app.getContext(), sessionInfo);
-                if (!apps.added.isEmpty()) {
-                    final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added);
-                    apps.added.clear();
-                    scheduleCallbackTask(new CallbackTask() {
-                        @Override
-                        public void execute(Callbacks callbacks) {
-                            callbacks.bindAppsAddedOrUpdated(arrayList);
-                        }
-                    });
-                }
+                bindApplicationsIfNeeded();
             }
         });
     }
@@ -469,8 +446,8 @@
      * use partial updates similar to {@link UserManagerCompat}
      */
     public void refreshShortcutsIfRequired() {
-        sWorker.removeCallbacks(mShortcutPermissionCheckRunnable);
-        sWorker.post(mShortcutPermissionCheckRunnable);
+        MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
+        MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
     }
 
     /**
@@ -497,14 +474,8 @@
     }
 
     public void enqueueModelUpdateTask(ModelUpdateTask task) {
-        task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor);
-
-        if (sWorkerThread.getThreadId() == Process.myTid()) {
-            task.run();
-        } else {
-            // If we are not on the worker thread, then post to the worker handler
-            sWorker.post(task);
-        }
+        task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
+        MODEL_EXECUTOR.execute(task);
     }
 
     /**
@@ -579,15 +550,4 @@
     public Callbacks getCallback() {
         return mCallbacks != null ? mCallbacks.get() : null;
     }
-
-    /**
-     * @return the looper for the worker thread which can be used to start background tasks.
-     */
-    public static Looper getWorkerLooper() {
-        return mWorkerLooper;
-    }
-
-    public static void setWorkerPriority(final int priority) {
-        Process.setThreadPriority(sWorkerThread.getThreadId(), priority);
-    }
 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 6ad5c36..6081300 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -33,7 +33,6 @@
 import android.content.Intent;
 import android.content.OperationApplicationException;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -50,7 +49,6 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.BaseColumns;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -70,6 +68,7 @@
 import com.android.launcher3.util.IntArray;
 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;
 
@@ -77,7 +76,6 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.io.StringReader;
@@ -873,7 +871,7 @@
                         continue;
                     }
 
-                    if (!Utilities.isLauncherAppTarget(intent)) {
+                    if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
                         continue;
                     }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index e248ba0..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;
@@ -122,11 +126,13 @@
          */
         public static final int CONTAINER_DESKTOP = -100;
         public static final int CONTAINER_HOTSEAT = -101;
+        public static final int CONTAINER_PREDICTION = -102;
 
         static final String containerToString(int container) {
             switch (container) {
                 case CONTAINER_DESKTOP: return "desktop";
                 case CONTAINER_HOTSEAT: return "hotseat";
+                case CONTAINER_PREDICTION: return "prediction";
                 default: return String.valueOf(container);
             }
         }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index dcfd272..6e2626b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -243,6 +243,10 @@
         return 0;
     }
 
+    public float getOverviewScrimAlpha(Launcher launcher) {
+        return 0;
+    }
+
     public String getDescription(Launcher launcher) {
         return launcher.getWorkspace().getCurrentPageDescription();
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 2c8c208..f673508 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,7 +24,8 @@
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
+
+import androidx.annotation.IntDef;
 
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -32,7 +33,6 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.UiFactory;
 
 import java.io.PrintWriter;
@@ -40,8 +40,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
-import androidx.annotation.IntDef;
-
 /**
  * TODO: figure out what kind of tests we can write for this
  *
@@ -136,7 +134,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);
@@ -403,16 +401,12 @@
     }
 
     private void onStateTransitionStart(LauncherState state) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "onStateTransitionStart");
-        }
         if (mState != state) {
             mState.onStateDisabled(mLauncher);
         }
         mState = state;
         mState.onStateEnabled(mLauncher);
-        mLauncher.onStateSet(mState);
+        mLauncher.onStateSetStart(mState);
 
         if (state.disablePageClipping) {
             // Only disable clipping if needed, otherwise leave it as previous value.
@@ -429,17 +423,11 @@
         // Only change the stable states after the transitions have finished
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                        "mCurrentStableState = " + state.getClass().getSimpleName() + " @ " +
-                                android.util.Log.getStackTraceString(new Throwable()));
-            }
             mCurrentStableState = state;
         }
 
         state.onStateTransitionEnd(mLauncher);
-        mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping);
-        mLauncher.finishAutoCancelActionMode();
+        mLauncher.onStateSetEnd(state);
 
         if (state == NORMAL) {
             setRestState(null);
@@ -580,10 +568,6 @@
         private final AnimatorSet mAnim;
 
         public StartAnimRunnable(AnimatorSet anim) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                        "StartAnimRunnable");
-            }
             mAnim = anim;
         }
 
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
deleted file mode 100644
index 5094682..0000000
--- a/src/com/android/launcher3/MainThreadExecutor.java
+++ /dev/null
@@ -1,33 +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;
-
-import android.os.Looper;
-
-import com.android.launcher3.util.LooperExecutor;
-
-/**
- * An executor service that executes its tasks on the main thread.
- *
- * Shutting down this executor is not supported.
- */
-public class MainThreadExecutor extends LooperExecutor {
-
-    public MainThreadExecutor() {
-        super(Looper.getMainLooper());
-    }
-}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 2eeb132..ff2b400 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.Utilities.shouldDisableGestures;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -48,6 +47,7 @@
 import android.widget.ScrollView;
 
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
@@ -368,6 +368,7 @@
      */
     protected void onPageEndTransition() {
         mWasInOverscroll = false;
+        AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
     }
 
     protected int getUnboundedScrollX() {
@@ -847,7 +848,7 @@
          */
 
         // Skip touch handling if there are no pages to swipe
-        if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
+        if (getChildCount() <= 0) return false;
 
         acquireVelocityTrackerAndAddMovement(ev);
 
@@ -889,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;
             }
@@ -928,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;
     }
@@ -1084,7 +1088,9 @@
         if (mFreeScroll) {
             setCurrentPage(getNextPage());
         } else if (wasFreeScroll) {
-            snapToPage(getNextPage());
+            if (getScrollForPage(getNextPage()) != getScrollX()) {
+                snapToPage(getNextPage());
+            }
         }
     }
 
@@ -1095,7 +1101,7 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         // Skip touch handling if there are no pages to swipe
-        if (getChildCount() <= 0 || shouldDisableGestures(ev)) return false;
+        if (getChildCount() <= 0) return false;
 
         acquireVelocityTrackerAndAddMovement(ev);
 
@@ -1103,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.
@@ -1288,6 +1296,10 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_SCROLL: {
+                    Launcher launcher = Launcher.getLauncher(getContext());
+                    if (launcher != null) {
+                        AbstractFloatingView.closeAllOpenViews(launcher);
+                    }
                     // Handle mouse (or ext. device) by shifting the page depending on the scroll
                     final float vscroll;
                     final float hscroll;
@@ -1298,6 +1310,9 @@
                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
                     }
+                    if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) {
+                        return true;
+                    }
                     if (hscroll != 0 || vscroll != 0) {
                         boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
                                                          : (hscroll > 0 || vscroll > 0);
@@ -1314,6 +1329,10 @@
         return super.onGenericMotionEvent(event);
     }
 
+    protected boolean isVerticalScrollable() {
+        return true;
+    }
+
     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
@@ -1531,7 +1550,7 @@
             snapToPage(getNextPage() - 1);
             return true;
         }
-        return false;
+        return onOverscroll(-getMeasuredWidth());
     }
 
     public boolean scrollRight() {
@@ -1539,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
@@ -1559,16 +1586,24 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         final boolean pagesFlipped = isPageOrderFlipped();
-        info.setScrollable(getPageCount() > 1);
-        if (getCurrentPage() < getPageCount() - 1) {
-            info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
-                    : AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+        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);
+            info.addAction(mIsRtl ?
+                AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT
+                : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
         }
-        if (getCurrentPage() > 0) {
-            info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
-                    : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+        if (getCurrentPage() >= offset) {
+            info.addAction(pagesFlipped ?
+                AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
+                : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
+            info.addAction(mIsRtl ?
+                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.
@@ -1587,7 +1622,7 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        event.setScrollable(getPageCount() > 1);
+        event.setScrollable(mAllowOverScroll || getPageCount() > 1);
     }
 
     @Override
@@ -1606,8 +1641,21 @@
                 if (pagesFlipped ? scrollRight() : scrollLeft()) {
                     return true;
                 }
+            } break;
+            case android.R.id.accessibilityActionPageRight: {
+                if (!mIsRtl) {
+                  return scrollRight();
+                } else {
+                  return scrollLeft();
+                }
             }
-            break;
+            case android.R.id.accessibilityActionPageLeft: {
+                if (!mIsRtl) {
+                  return scrollLeft();
+                } else {
+                  return scrollRight();
+                }
+            }
         }
         return false;
     }
diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java
index 380078b..af5402a 100644
--- a/src/com/android/launcher3/Partner.java
+++ b/src/com/android/launcher3/Partner.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.PackageManagerHelper.findSystemApk;
+
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.util.DisplayMetrics;
@@ -59,7 +61,7 @@
      */
     public static synchronized Partner get(PackageManager pm) {
         if (!sSearched) {
-            Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
+            Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);
             if (apkInfo != null) {
                 sPartner = new Partner(apkInfo.first, apkInfo.second);
             }
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 55cb6f2..c8c590d 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;
@@ -29,6 +30,7 @@
 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;
@@ -240,6 +242,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);
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index b0da6b9..a87c446 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -18,6 +18,7 @@
 
 import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -27,19 +28,23 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
-import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
+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.
  */
@@ -66,15 +71,34 @@
 
         SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+        if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
+                || info == null || user == null) {
+            // Invalid intent.
+            return;
+        }
 
-        if (TextUtils.isEmpty(info.getAppPackageName()) ||
-                info.getInstallReason() != PackageManager.INSTALL_REASON_USER) {
+        PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+        if (TextUtils.isEmpty(info.getAppPackageName())
+                || info.getInstallReason() != PackageManager.INSTALL_REASON_USER
+                || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
+            packageInstallerCompat.removePromiseIconId(info.getSessionId());
             return;
         }
 
         queueAppIconAddition(context, info.getAppPackageName(), user);
     }
 
+    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()) {
+            // Ensure application isn't already installed.
+            queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
+                    sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
+        }
+    }
+
     public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
         List<LauncherActivityInfo> activities = LauncherAppsCompat.getInstance(context)
                 .getActivityList(packageName, user);
@@ -82,7 +106,18 @@
             // no activity found
             return;
         }
-        InstallShortcutReceiver.queueActivityInfo(activities.get(0), context);
+        queueAppIconAddition(context, packageName, activities.get(0).getLabel(), null, user);
+    }
+
+    private static void queueAppIconAddition(Context context, String packageName,
+            CharSequence label, Bitmap icon, UserHandle user) {
+        Intent data = new Intent();
+        data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
+                new ComponentName(packageName, "")).setPackage(packageName));
+        data.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
+        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
+
+        InstallShortcutReceiver.queueApplication(data, user, context);
     }
 
     public static boolean isEnabled(Context context) {
@@ -103,7 +138,7 @@
             // grid.
             prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
         } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
-            new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+            new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
         }
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 65aa3a7..5d0effa 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,20 +16,17 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
+import android.app.Person;
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
@@ -44,7 +41,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.Message;
@@ -57,7 +53,6 @@
 import android.text.style.TtsSpan;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.Pair;
 import android.util.TypedValue;
 import android.view.MotionEvent;
 import android.view.View;
@@ -66,7 +61,6 @@
 
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -78,22 +72,13 @@
 import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
-import java.io.Closeable;
-import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.StringTokenizer;
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-
 /**
  * Various utilities shared amongst the Launcher's classes.
  */
@@ -109,6 +94,9 @@
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
+    public static final String[] EMPTY_STRING_ARRAY = new String[0];
+    public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
+
     public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
 
     public static final boolean ATLEAST_P =
@@ -120,24 +108,12 @@
     public static final boolean ATLEAST_OREO =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
 
-    public static final int SINGLE_FRAME_MS = 16;
-
     /**
      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
      */
     public static final int EDGE_NAV_BAR = 1 << 8;
 
     /**
-     * Set on a motion event do disallow any gestures and only handle touch.
-     * See {@link MotionEvent#setEdgeFlags(int)}.
-     */
-    public static final int FLAG_NO_GESTURES = 1 << 9;
-
-    public static boolean shouldDisableGestures(MotionEvent ev) {
-        return (ev.getEdgeFlags() & FLAG_NO_GESTURES) == FLAG_NO_GESTURES;
-    }
-
-    /**
      * Indicates if the device has a debug build. Should only be used to store additional info or
      * add extra logging and not for changing the app behavior.
      */
@@ -154,18 +130,6 @@
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
 
-    // These values are same as that in {@link AsyncTask}.
-    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
-    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
-    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
-    private static final int KEEP_ALIVE = 1;
-    /**
-     * An {@link Executor} to be used with async task with no limit on the queue size.
-     */
-    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
-            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
-            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
-
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
 
@@ -253,7 +217,6 @@
         return scale;
     }
 
-
     /**
      * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
      */
@@ -389,53 +352,6 @@
         return min + (value * (max - min));
     }
 
-    public static boolean isSystemApp(Context context, Intent intent) {
-        PackageManager pm = context.getPackageManager();
-        ComponentName cn = intent.getComponent();
-        String packageName = null;
-        if (cn == null) {
-            ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-            if ((info != null) && (info.activityInfo != null)) {
-                packageName = info.activityInfo.packageName;
-            }
-        } else {
-            packageName = cn.getPackageName();
-        }
-        if (packageName != null) {
-            try {
-                PackageInfo info = pm.getPackageInfo(packageName, 0);
-                return (info != null) && (info.applicationInfo != null) &&
-                        ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
-            } catch (NameNotFoundException e) {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    /*
-     * Finds a system apk which had a broadcast receiver listening to a particular action.
-     * @param action intent action used to find the apk
-     * @return a pair of apk package name and the resources.
-     */
-    static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
-        final Intent intent = new Intent(action);
-        for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
-            if (info.activityInfo != null &&
-                    (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                final String packageName = info.activityInfo.packageName;
-                try {
-                    final Resources res = pm.getResourcesForApplication(packageName);
-                    return Pair.create(packageName, res);
-                } catch (NameNotFoundException e) {
-                    Log.w(TAG, "Failed to find resources for " + packageName);
-                }
-            }
-        }
-        return null;
-    }
-
     /**
      * Trims the string, removing all whitespace at the beginning and end of the string.
      * Non-breaking whitespaces are also removed.
@@ -460,51 +376,10 @@
         return (int) Math.ceil(fm.bottom - fm.top);
     }
 
-    /**
-     * Convenience println with multiple args.
-     */
-    public static void println(String key, Object... args) {
-        StringBuilder b = new StringBuilder();
-        b.append(key);
-        b.append(": ");
-        boolean isFirstArgument = true;
-        for (Object arg : args) {
-            if (isFirstArgument) {
-                isFirstArgument = false;
-            } else {
-                b.append(", ");
-            }
-            b.append(arg);
-        }
-        System.out.println(b.toString());
-    }
-
     public static boolean isRtl(Resources res) {
         return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
     }
 
-    /**
-     * Returns true if the intent is a valid launch intent for a launcher activity of an app.
-     * This is used to identify shortcuts which are different from the ones exposed by the
-     * applications' manifest file.
-     *
-     * @param launchIntent The intent that will be launched when the shortcut is clicked.
-     */
-    public static boolean isLauncherAppTarget(Intent launchIntent) {
-        if (launchIntent != null
-                && Intent.ACTION_MAIN.equals(launchIntent.getAction())
-                && launchIntent.getComponent() != null
-                && launchIntent.getCategories() != null
-                && launchIntent.getCategories().size() == 1
-                && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
-                && TextUtils.isEmpty(launchIntent.getDataString())) {
-            // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
-            Bundle extras = launchIntent.getExtras();
-            return extras == null || extras.keySet().isEmpty();
-        }
-        return false;
-    }
-
     public static float dpiFromPx(int size, DisplayMetrics metrics){
         float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
         return (size / densityRatio);
@@ -607,18 +482,6 @@
         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
     }
 
-    public static void closeSilently(Closeable c) {
-        if (c != null) {
-            try {
-                c.close();
-            } catch (IOException e) {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
-                    Log.d(TAG, "Error closing", e);
-                }
-            }
-        }
-    }
-
     public static boolean isBinderSizeError(Exception e) {
         return e.getCause() instanceof TransactionTooLargeException
                 || e.getCause() instanceof DeadObjectException;
@@ -721,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 = LauncherIcons.getBadgeSizeForIconSize(iconSize);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
                     insetFraction, insetFraction, 0, 0);
@@ -733,25 +596,6 @@
         }
     }
 
-    public static int[] getIntArrayFromString(String tokenized) {
-        StringTokenizer tokenizer = new StringTokenizer(tokenized, ",");
-        int[] array = new int[tokenizer.countTokens()];
-        int count = 0;
-        while (tokenizer.hasMoreTokens()) {
-            array[count] = Integer.parseInt(tokenizer.nextToken());
-            count++;
-        }
-        return array;
-    }
-
-    public static String getStringFromIntArray(int[] array) {
-        StringBuilder str = new StringBuilder();
-        for (int value : array) {
-            str.append(value).append(",");
-        }
-        return str.toString();
-    }
-
     public static float squaredHypot(float x, float y) {
         return x * x + y * y;
     }
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 6d1bc1a..c6381b0 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -1,5 +1,8 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -23,21 +26,23 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.LongSparseArray;
 
+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.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SQLiteCacheHelper;
@@ -50,11 +55,8 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 
-import androidx.annotation.Nullable;
-
 public class WidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
@@ -68,23 +70,18 @@
      * Note: synchronized block used for this variable is expensive and the block should always
      * be posted to a background thread.
      */
-    @Thunk final Set<Bitmap> mUnusedBitmaps =
-            Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
+    @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>());
 
     private final Context mContext;
     private final IconCache mIconCache;
     private final UserManagerCompat mUserManager;
     private final CacheDb mDb;
 
-    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    @Thunk final Handler mWorkerHandler;
-
     public WidgetPreviewLoader(Context context, IconCache iconCache) {
         mContext = context;
         mIconCache = iconCache;
         mUserManager = UserManagerCompat.getInstance(context);
         mDb = new CacheDb(context);
-        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
     }
 
     /**
@@ -99,7 +96,7 @@
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
         PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
-        task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
+        task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
 
         CancellationSignal signal = new CancellationSignal();
         signal.setOnCancelListener(task);
@@ -453,7 +450,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);
 
@@ -494,12 +491,7 @@
 
     private Drawable mutateOnMainThread(final Drawable drawable) {
         try {
-            return mMainThreadExecutor.submit(new Callable<Drawable>() {
-                @Override
-                public Drawable call() throws Exception {
-                    return drawable.mutate();
-                }
-            }).get();
+            return MAIN_EXECUTOR.submit(drawable::mutate).get();
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new RuntimeException(e);
@@ -607,7 +599,7 @@
 
             // Write the generated preview to the DB in the worker thread
             if (mVersions != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         if (!isCancelled()) {
@@ -637,7 +629,7 @@
             // recycled set immediately. Otherwise, it will be recycled after the preview is written
             // to disk.
             if (preview != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         synchronized (mUnusedBitmaps) {
@@ -658,7 +650,7 @@
             // in the tasks's onCancelled() call, and if cancelled while the task is writing to
             // disk, it will be cancelled in the task's onPostExecute() call.
             if (mBitmapToRecycle != null) {
-                mWorkerHandler.post(new Runnable() {
+                MODEL_EXECUTOR.post(new Runnable() {
                     @Override
                     public void run() {
                         synchronized (mUnusedBitmaps) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9f846bb..9eeb286 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -67,9 +67,9 @@
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -81,6 +81,7 @@
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -89,8 +90,8 @@
 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.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;
@@ -132,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;
 
@@ -370,10 +368,6 @@
 
     @Override
     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "onDragStart 1");
-        }
         if (ENFORCE_DRAG_EVENT_ORDER) {
             enforceDragParity("onDragStart", 0, 0);
         }
@@ -425,8 +419,7 @@
 
         // Always enter the spring loaded mode
         if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "onDragStart 2");
+            Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Switching to SPRING_LOADED");
         }
         mLauncher.getStateManager().goToState(SPRING_LOADED);
     }
@@ -1042,6 +1035,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 &&
@@ -1056,13 +1056,18 @@
             if (!mOverlayShown) {
                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
+                mLauncher.getStatsLogManager().logSwipeOnContainer(true, 0);
             }
             mOverlayShown = true;
             // Not announcing the overlay page for accessibility since it announces itself.
         } else if (Float.compare(scroll, 0f) == 0) {
             if (mOverlayShown) {
-                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+                UserEventDispatcher ued = mLauncher.getUserEventDispatcher();
+                if (!ued.isPreviousHomeGesture()) {
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
+                    mLauncher.getStatsLogManager().logSwipeOnContainer(false, -1);
+                }
             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
                 // accessibility since default announcements were disabled while in overscroll
@@ -1160,7 +1165,7 @@
     }
 
     protected void setWallpaperDimension() {
-        Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+        Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() {
             @Override
             public void run() {
                 final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize;
@@ -1270,6 +1275,10 @@
         return !mLauncher.isInState(NORMAL);
     }
 
+    private boolean workspaceInScrollableState() {
+        return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
+    }
+
     /** Returns whether a drag should be allowed to be started from the current workspace state. */
     public boolean workspaceIconsCanBeDragged() {
         return mLauncher.getStateManager().getState().workspaceIconsCanBeDragged;
@@ -1463,6 +1472,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();
@@ -1488,7 +1500,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.
@@ -1746,6 +1758,9 @@
     public void prepareAccessibilityDrop() { }
 
     public void onDrop(final DragObject d, DragOptions options) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDrop");
+        }
         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
         CellLayout dropTargetLayout = mDropToLayout;
 
@@ -2423,6 +2438,9 @@
      * to add an item to one of the workspace screens.
      */
     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDropExternal");
+        }
         if (d.dragInfo instanceof PendingAddShortcutInfo) {
             WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
                     .activityInfo.createWorkspaceItemInfo();
@@ -2525,22 +2543,22 @@
             View view;
 
             switch (info.itemType) {
-            case ITEM_TYPE_APPLICATION:
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                if (info.container == NO_ID && info instanceof AppInfo) {
-                    // Came from all apps -- make a copy
-                    info = ((AppInfo) info).makeWorkspaceItem();
-                    d.dragInfo = info;
-                }
-                view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
-                        (FolderInfo) info);
-                break;
-            default:
-                throw new IllegalStateException("Unknown item type: " + info.itemType);
+                case ITEM_TYPE_APPLICATION:
+                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                    if (info instanceof AppInfo) {
+                        // Came from all apps -- make a copy
+                        info = ((AppInfo) info).makeWorkspaceItem();
+                        d.dragInfo = info;
+                    }
+                    view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info);
+                    break;
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
+                            (FolderInfo) info);
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown item type: " + info.itemType);
             }
 
             // First we find the cell nearest to point at which the item is
@@ -2798,10 +2816,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) {
@@ -2848,7 +2883,7 @@
     @Override
     public boolean scrollLeft() {
         boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState) {
+        if (!mIsSwitchingState && workspaceInScrollableState()) {
             result = super.scrollLeft();
         }
         Folder openFolder = Folder.getOpen(mLauncher);
@@ -2861,7 +2896,7 @@
     @Override
     public boolean scrollRight() {
         boolean result = false;
-        if (!workspaceInModalState() && !mIsSwitchingState) {
+        if (!mIsSwitchingState && workspaceInScrollableState()) {
             result = super.scrollRight();
         }
         Folder openFolder = Folder.getOpen(mLauncher);
@@ -2953,7 +2988,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)) {
@@ -2976,7 +3011,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;
@@ -3001,7 +3036,7 @@
     }
 
     void clearDropTargets() {
-        mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
+        mapOverItems(new ItemOperator() {
             @Override
             public boolean evaluate(ItemInfo info, View v) {
                 if (v instanceof DropTarget) {
@@ -3046,10 +3081,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 */);
+                        }
                     }
                 }
             }
@@ -3073,18 +3110,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;
@@ -3094,103 +3130,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().getItemsInReadingOrder();
-                // 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) {
@@ -3202,21 +3203,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) {
@@ -3240,7 +3245,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
@@ -3256,6 +3261,10 @@
         }
     }
 
+    public boolean isOverlayShown() {
+        return mOverlayShown;
+    }
+
     void moveToDefaultScreen() {
         int page = DEFAULT_PAGE;
         if (!workspaceInModalState() && getNextPage() != page) {
@@ -3360,7 +3369,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 5a2373b..23795c5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -16,17 +16,23 @@
 
 package com.android.launcher3;
 
+import android.app.Person;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.ContentWriter;
 
+import java.util.Arrays;
+
 /**
  * Represents a launchable icon on the workspaces and in folders.
  */
@@ -44,24 +50,26 @@
      * The icon was added as an auto-install app, and is not ready to be used. This flag can't
      * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout
      * parsing.
+     *
+     * OR this icon was added due to it being an active install session created by the user.
      */
-    public static final int FLAG_AUTOINSTALL_ICON = 2; //0B10;
+    public static final int FLAG_AUTOINSTALL_ICON = 1 << 1;
 
     /**
      * The icon is being installed. If {@link #FLAG_RESTORED_ICON} or {@link #FLAG_AUTOINSTALL_ICON}
      * is set, then the icon is either being installed or is in a broken state.
      */
-    public static final int FLAG_INSTALL_SESSION_ACTIVE = 4; // 0B100;
+    public static final int FLAG_INSTALL_SESSION_ACTIVE = 1 << 2;
 
     /**
      * Indicates that the widget restore has started.
      */
-    public static final int FLAG_RESTORE_STARTED = 8; //0B1000;
+    public static final int FLAG_RESTORE_STARTED = 1 << 3;
 
     /**
      * Web UI supported.
      */
-    public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
+    public static final int FLAG_SUPPORTS_WEB_UI = 1 << 4;
 
     /**
      * The intent used to start the application.
@@ -83,10 +91,17 @@
     public int status;
 
     /**
+     * A set of person's Id associated with the WorkspaceItemInfo, this is only used if the item
+     * represents a deep shortcut.
+     */
+    @NonNull private String[] personKeys = Utilities.EMPTY_STRING_ARRAY;
+
+    /**
      * The installation progress [0-100] of the package that this shortcut represents.
      */
     private int mInstallProgress;
 
+
     public WorkspaceItemInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
     }
@@ -98,6 +113,7 @@
         iconResource = info.iconResource;
         status = info.status;
         mInstallProgress = info.mInstallProgress;
+        personKeys = info.personKeys.clone();
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeWorkspaceItem. */
@@ -175,6 +191,10 @@
             runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
+
+        Person[] persons = UiFactory.getPersons(shortcutInfo);
+        personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
+            : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
     }
 
     /** Returns the WorkspaceItemInfo id associated with the deep shortcut. */
@@ -183,11 +203,16 @@
                 getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null;
     }
 
+    @NonNull
+    public String[] getPersonKeys() {
+        return personKeys;
+    }
+
     @Override
     public ComponentName getTargetComponent() {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
-                || hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) {
+                || 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.
@@ -196,4 +221,9 @@
         }
         return cn;
     }
+
+    @Override
+    public ItemInfoWithIcon clone() {
+        return new WorkspaceItemInfo(this);
+    }
 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 
 /**
@@ -96,14 +95,13 @@
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
             if (!hotseat.getRotationMode().isTransposed) {
-                // Set the hotseat's pivot point to match the workspace's, so that it scales together.
-                DragLayer dragLayer = mLauncher.getDragLayer();
-                float[] workspacePivot =
-                        new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
-                dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
-                dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
-                hotseat.setPivotX(workspacePivot[0]);
-                hotseat.setPivotY(workspacePivot[1]);
+                // Set the hotseat's pivot point to match the workspace's, so that it scales
+                // together. Since both hotseat and workspace can move, transform the point
+                // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+                // related methods.
+                hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+                hotseat.setPivotX(mWorkspace.getPivotX()
+                        + mWorkspace.getLeft() - hotseat.getLeft());
             }
             float hotseatScale = hotseatScaleAndTranslation.scale;
             Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index fd4df52..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,16 +32,17 @@
 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;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
@@ -115,7 +118,7 @@
 
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
-        if (!fromKeyboard && DeepShortcutManager.supportsShortcuts(item)) {
+        if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
             info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null
                     ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
         }
@@ -163,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 ea9b077..2d5b040 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -26,14 +26,18 @@
 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;
 import android.view.View;
 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,11 +49,12 @@
 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.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -59,13 +64,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.
  */
@@ -199,7 +197,10 @@
 
         // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
         // Overview states. We shouldn't intercept for the scrubber in these cases.
-        if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
+        if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+            mTouchHandler = null;
+            return false;
+        }
 
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             AllAppsRecyclerView rv = getActiveRecyclerView();
@@ -218,6 +219,16 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            AllAppsRecyclerView rv = getActiveRecyclerView();
+            if (rv != null && rv.getScrollbar()
+                .isHitInParent(ev.getX(), ev.getY(), mFastScrollerOffset)) {
+                mTouchHandler = rv.getScrollbar();
+            } else {
+                mTouchHandler = null;
+            }
+        }
+
         if (mTouchHandler != null) {
             mTouchHandler.handleTouchEvent(ev, mFastScrollerOffset);
             return true;
@@ -297,7 +308,11 @@
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        // This is filled in {@link AllAppsRecyclerView}
+        if (getApps().hasFilter()) {
+            targetParent.containerType = ContainerType.SEARCHRESULT;
+        } else {
+            targetParent.containerType = ContainerType.ALLAPPS;
+        }
     }
 
     @Override
@@ -307,6 +322,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();
@@ -623,23 +639,4 @@
 
         return super.performAccessibilityAction(action, arguments);
     }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "AllAppsContainerView.dispatchTouchEvent " + ev);
-        }
-        final boolean result = super.dispatchTouchEvent(ev);
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                if (result) mAllAppsStore.enableDeferUpdates(
-                        AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION);
-                break;
-        }
-        return result;
-    }
 }
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/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 69068c6..5b73940 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -81,4 +81,9 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    @Override
+    protected boolean isVerticalScrollable() {
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index a0e9dc5..f82e380 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -26,13 +26,15 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.BaseRecyclerView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+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;
@@ -41,8 +43,6 @@
 
 import java.util.List;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 /**
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
@@ -114,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);
     }
 
@@ -420,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/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 160042e..c4b2f68 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
+
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -26,8 +29,7 @@
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -39,33 +41,37 @@
 
     // Defer updates flag used to defer all apps updates to the next draw.
     public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
-    // Defer updates flag used to defer all apps updates while the user interacts with all apps.
-    public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1;
     // Defer updates flag used to defer all apps updates by a test's request.
-    public static final int DEFER_UPDATES_TEST = 1 << 2;
+    public static final int DEFER_UPDATES_TEST = 1 << 1;
 
     private PackageUserKey mTempKey = new PackageUserKey(null, null);
-    private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>();
+    private AppInfo mTempInfo = new AppInfo();
+
+    private AppInfo[] mApps = EMPTY_ARRAY;
+
     private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>();
     private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>();
 
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
-    public Collection<AppInfo> getApps() {
-        return mComponentToAppMap.values();
+    public AppInfo[] getApps() {
+        return mApps;
     }
 
     /**
      * Sets the current set of apps.
      */
-    public void setApps(List<AppInfo> apps) {
-        mComponentToAppMap.clear();
-        addOrUpdateApps(apps);
+    public void setApps(AppInfo[] apps) {
+        mApps = apps;
+        notifyUpdate();
     }
 
     public AppInfo getApp(ComponentKey key) {
-        return mComponentToAppMap.get(key);
+        mTempInfo.componentName = key.componentName;
+        mTempInfo.user = key.user;
+        int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR);
+        return index < 0 ? null : mApps[index];
     }
 
     public void enableDeferUpdates(int flag) {
@@ -80,31 +86,14 @@
         }
     }
 
+    public void disableDeferUpdatesSilently(int flag) {
+        mDeferUpdatesFlags &= ~flag;
+    }
+
     public int getDeferUpdatesFlags() {
         return mDeferUpdatesFlags;
     }
 
-    /**
-     * Adds or updates existing apps in the list
-     */
-    public void addOrUpdateApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.put(app.toComponentKey(), app);
-        }
-        notifyUpdate();
-    }
-
-    /**
-     * Removes some apps from the list.
-     */
-    public void removeApps(List<AppInfo> apps) {
-        for (AppInfo app : apps) {
-            mComponentToAppMap.remove(app.toComponentKey());
-        }
-        notifyUpdate();
-    }
-
-
     private void notifyUpdate() {
         if (mDeferUpdatesFlags != 0) {
             mUpdatePending = true;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4683893..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,11 +2,10 @@
 
 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;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
+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.Interpolators.FAST_OUT_SLOW_IN;
@@ -17,7 +16,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.util.FloatProperty;
-import android.util.Log;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -31,7 +29,6 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringObjectAnimator;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 
@@ -135,15 +132,6 @@
         } else {
             mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
         }
-
-        if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-            // Translate hotseat with the shelf until reaching overview.
-            float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-            if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
-                float hotseatShift = (progress - overviewProgress) * mShiftRange;
-                mLauncher.getHotseat().setTranslationY(hotseatShift);
-            }
-        }
     }
 
     public float getProgress() {
@@ -168,10 +156,6 @@
     @Override
     public void setStateWithAnimation(LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                    "setStateWithAnimation " + toState.getClass().getSimpleName());
-        }
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
@@ -212,13 +196,14 @@
         PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
                 : config.getPropertySetter(builder);
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
-        boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
+        boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
         Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        setter.setViewAlpha(mAppsView.getContentView(), hasContent ? 1 : 0, allAppsFade);
-        setter.setViewAlpha(mAppsView.getScrollBar(), hasContent ? 1 : 0, allAppsFade);
-        mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasContent, setter,
-                allAppsFade);
+        Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, 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);
         mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
         setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 2ad92e1..0c4be62 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -21,16 +21,13 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -145,9 +142,7 @@
 
     // The of ordered component names as a result of a search query
     private ArrayList<ComponentKey> mSearchResults;
-    private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
     private AllAppsGridAdapter mAdapter;
-    private AlphabeticIndexCompat mIndexer;
     private AppInfoComparator mAppNameComparator;
     private final int mNumAppsPerRow;
     private int mNumAppRowsInAdapter;
@@ -156,7 +151,6 @@
     public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
         mAllAppsStore = appsStore;
         mLauncher = Launcher.getLauncher(context);
-        mIndexer = new AlphabeticIndexCompat(context);
         mAppNameComparator = new AppInfoComparator(context);
         mIsWork = isWork;
         mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
@@ -263,7 +257,7 @@
             TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
             for (AppInfo info : mApps) {
                 // Add the section to the cache
-                String sectionName = getAndUpdateCachedSectionName(info.title);
+                String sectionName = info.sectionName;
 
                 // Add it to the mapping
                 ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
@@ -279,12 +273,6 @@
             for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
                 mApps.addAll(entry.getValue());
             }
-        } else {
-            // Just compute the section headers for use below
-            for (AppInfo info : mApps) {
-                // Add the section to the cache
-                getAndUpdateCachedSectionName(info.title);
-            }
         }
 
         // Recompose the set of adapter items from the current set of apps
@@ -301,11 +289,6 @@
     }
 
     private void refreshRecyclerView() {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "refreshRecyclerView @ " + android.util.Log.getStackTraceString(
-                            new Throwable()));
-        }
         if (mAdapter != null) {
             mAdapter.notifyDataSetChanged();
         }
@@ -325,7 +308,7 @@
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
         for (AppInfo info : getFiltersAppInfos()) {
-            String sectionName = getAndUpdateCachedSectionName(info.title);
+            String sectionName = info.sectionName;
 
             // Create a new section if the section names do not match
             if (!sectionName.equals(lastSectionName)) {
@@ -433,18 +416,4 @@
         }
         return result;
     }
-
-    /**
-     * Returns the cached section name for the given title, recomputing and updating the cache if
-     * the title has no cached section name.
-     */
-    private String getAndUpdateCachedSectionName(CharSequence title) {
-        String sectionName = mCachedSectionNames.get(title);
-        if (sectionName == null) {
-            sectionName = mIndexer.computeSectionName(title);
-            mCachedSectionNames.put(title, sectionName);
-        }
-        return sectionName;
-    }
-
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index 922e4f1..f899587 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -46,8 +46,8 @@
      */
     boolean hasVisibleContent();
 
-    void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
-            PropertySetter setter, Interpolator fadeInterpolator);
+    void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade);
 
     /**
      * Scrolls the content vertically.
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 66dced9..42a0eee 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -27,6 +27,10 @@
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
@@ -40,10 +44,6 @@
 import java.util.ArrayList;
 import java.util.Map;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
 public class FloatingHeaderView extends LinearLayout implements
         ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow>, Insettable,
         OnHeightUpdatedListener {
@@ -363,14 +363,14 @@
         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
     }
 
-    public void setContentVisibility(boolean hasHeader, boolean hasContent, PropertySetter setter,
-            Interpolator fadeInterpolator) {
+    public void setContentVisibility(boolean hasHeader, boolean hasAllAppsContent,
+            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
         for (FloatingHeaderRow row : mAllRows) {
-            row.setContentVisibility(hasHeader, hasContent, setter, fadeInterpolator);
+            row.setContentVisibility(hasHeader, hasAllAppsContent, setter, headerFade, allAppsFade);
         }
 
-        allowTouchForwarding(hasContent);
-        setter.setFloat(mTabLayout, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+        allowTouchForwarding(hasAllAppsContent);
+        setter.setFloat(mTabLayout, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
     }
 
     protected void allowTouchForwarding(boolean allow) {
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index b283ff4..535ef54 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -64,10 +64,10 @@
     }
 
     @Override
-    public void setContentVisibility(boolean hasHeaderExtra, boolean hasContent,
-            PropertySetter setter, Interpolator fadeInterpolator) {
+    public void setContentVisibility(boolean hasHeaderExtra, boolean hasAllAppsContent,
+            PropertySetter setter, Interpolator headerFade, Interpolator allAppsFade) {
         // Don't use setViewAlpha as we want to control the visibility ourselves.
-        setter.setFloat(mView, ALPHA, hasContent ? 1 : 0, fadeInterpolator);
+        setter.setFloat(mView, ALPHA, hasAllAppsContent ? 1 : 0, headerFade);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 8ac9d66..eabd283 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -27,7 +27,7 @@
  */
 public class AlphaUpdateListener extends AnimationSuccessListener
         implements AnimatorUpdateListener {
-    private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
+    public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
 
     private View mView;
 
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 2c440bb..4a52795 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,15 +26,15 @@
 import android.animation.ValueAnimator;
 import android.util.Log;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
-
 /**
  * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
  * and durations.
@@ -250,6 +250,17 @@
         }
     }
 
+    /**
+     * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
+     * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+     */
+    public void dispatchOnCancelWithoutCancelRunnable() {
+        Runnable onCancel = mOnCancelRunnable;
+        setOnCancelRunnable(null);
+        dispatchOnCancel();
+        setOnCancelRunnable(onCancel);
+    }
+
     public void dispatchOnCancel() {
         dispatchOnCancelRecursively(mAnim);
     }
@@ -283,10 +294,6 @@
         mOnCancelRunnable = runnable;
     }
 
-    public Runnable getOnCancelRunnable() {
-        return mOnCancelRunnable;
-    }
-
     public void skipToEnd() {
         mSkipToEnd = true;
         for (SpringAnimation spring : mSprings) {
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 52a896e..cd30dea 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -39,6 +39,8 @@
     public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
     public static final int ANIM_OVERVIEW_FADE = 9;
     public static final int ANIM_ALL_APPS_FADE = 10;
+    public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
+    public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
 
     public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
 
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index b169cb8..fccc120 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
+import android.content.Context;
 import android.graphics.Path;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
@@ -38,6 +39,7 @@
     public static final Interpolator LINEAR = new LinearInterpolator();
 
     public static final Interpolator ACCEL = new AccelerateInterpolator();
+    public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f);
     public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f);
     public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2);
 
@@ -47,6 +49,7 @@
     public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
     public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
     public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
+    public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f);
 
     public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
 
@@ -144,7 +147,8 @@
     public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound,
             float upperBound) {
         if (upperBound <= lowerBound) {
-            throw new IllegalArgumentException("lowerBound must be less than upperBound");
+            throw new IllegalArgumentException(String.format(
+                    "lowerBound (%f) must be less than upperBound (%f)", lowerBound, upperBound));
         }
         return t -> {
             if (t < lowerBound) {
@@ -187,13 +191,13 @@
          * @param totalDistancePx The distance against which progress is calculated.
          */
         public OvershootParams(float startProgress, float overshootPastProgress,
-                float endProgress, float velocityPxPerMs, int totalDistancePx) {
+                float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) {
             velocityPxPerMs = Math.abs(velocityPxPerMs);
             start = startProgress;
             int startPx = (int) (start * totalDistancePx);
             // Overshoot by about half a frame.
             float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs *
-                    SINGLE_FRAME_MS / totalDistancePx / 2;
+                    getSingleFrameMs(context) / totalDistancePx / 2;
             overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f);
             end = overshootPastProgress + overshootBy;
             int endPx = (int) (end  * totalDistancePx);
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/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 395fed2..91a3106 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -96,7 +96,10 @@
             }
         });
 
-        mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false);
+        mSpring.addUpdateListener((animation, value, velocity) -> {
+            mSpringEnded = false;
+            mEnded = false;
+        });
         mSpring.addEndListener((animation, canceled, value, velocity) -> {
             mSpringEnded = true;
             tryEnding();
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 43ae651..81c95cb 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -53,9 +53,6 @@
     }
 
     public static void sendStateEventToTest(Context context, int stateOrdinal) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "sendStateEventToTest");
-        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index dfdcc70..46c9006 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -3,7 +3,6 @@
 import android.content.Context;
 import android.icu.text.AlphabeticIndex;
 import android.os.LocaleList;
-import android.util.Log;
 
 import com.android.launcher3.Utilities;
 
@@ -12,28 +11,32 @@
 import androidx.annotation.NonNull;
 
 public class AlphabeticIndexCompat {
-    private static final String TAG = "AlphabeticIndexCompat";
 
     private static final String MID_DOT = "\u2219";
-    private final BaseIndex mBaseIndex;
     private final String mDefaultMiscLabel;
 
+    private final AlphabeticIndex.ImmutableIndex mBaseIndex;
+
     public AlphabeticIndexCompat(Context context) {
-        BaseIndex index = null;
+        this(context.getResources().getConfiguration().getLocales());
+    }
 
-        try {
-            index = new AlphabeticIndexVN(context);
-        } catch (Exception e) {
-            Log.d(TAG, "Unable to load the system index", e);
+    public AlphabeticIndexCompat(LocaleList locales) {
+        int localeCount = locales.size();
+
+        Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0);
+        AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale);
+        for (int i = 1; i < localeCount; i++) {
+            indexBuilder.addLabels(locales.get(i));
         }
+        indexBuilder.addLabels(Locale.ENGLISH);
+        mBaseIndex = indexBuilder.buildImmutableIndex();
 
-        mBaseIndex = index == null ? new BaseIndex() : index;
-
-        if (context.getResources().getConfiguration().locale
-                .getLanguage().equals(Locale.JAPANESE.getLanguage())) {
+        if (primaryLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
             // Japanese character 他 ("misc")
             mDefaultMiscLabel = "\u4ed6";
-            // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
+            // TODO(winsonc, omakoto): We need to handle Japanese sections better,
+            // especially the kanji
         } else {
             // Dot
             mDefaultMiscLabel = MID_DOT;
@@ -45,7 +48,7 @@
      */
     public String computeSectionName(@NonNull CharSequence cs) {
         String s = Utilities.trim(cs);
-        String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
+        String sectionName = mBaseIndex.getBucket(mBaseIndex.getBucketIndex(s)).getLabel();
         if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
             int c = s.codePointAt(0);
             boolean startsWithDigit = Character.isDigit(c);
@@ -66,71 +69,4 @@
         }
         return sectionName;
     }
-
-    /**
-     * Base class to support Alphabetic indexing if not supported by the framework.
-     * TODO(winsonc): disable for non-english locales
-     */
-    private static class BaseIndex {
-
-        private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
-        private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
-
-        /**
-         * Returns the index of the bucket in which the given string should appear.
-         */
-        protected int getBucketIndex(@NonNull String s) {
-            if (s.isEmpty()) {
-                return UNKNOWN_BUCKET_INDEX;
-            }
-            int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
-            if (index != -1) {
-                return index;
-            }
-            return UNKNOWN_BUCKET_INDEX;
-        }
-
-        /**
-         * Returns the label for the bucket at the given index (as returned by getBucketIndex).
-         */
-        protected String getBucketLabel(int index) {
-            return BUCKETS.substring(index, index + 1);
-        }
-    }
-
-    /**
-     * Implementation based on {@link AlphabeticIndex}.
-     */
-    private static class AlphabeticIndexVN extends BaseIndex {
-
-        private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
-
-        public AlphabeticIndexVN(Context context) {
-            LocaleList locales = context.getResources().getConfiguration().getLocales();
-            int localeCount = locales.size();
-
-            Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0);
-            AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale);
-            for (int i = 1; i < localeCount; i++) {
-                indexBuilder.addLabels(locales.get(i));
-            }
-            indexBuilder.addLabels(Locale.ENGLISH);
-
-            mAlphabeticIndex = indexBuilder.buildImmutableIndex();
-        }
-
-        /**
-         * Returns the index of the bucket in which {@param s} should appear.
-         */
-        protected int getBucketIndex(String s) {
-            return mAlphabeticIndex.getBucketIndex(s);
-        }
-
-        /**
-         * Returns the label for the bucket at the given index
-         */
-        protected String getBucketLabel(int index) {
-            return mAlphabeticIndex.getBucket(index).getLabel();
-        }
-    }
 }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 3243256..fc5d11c 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -23,19 +23,18 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.HashMap;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 public abstract class AppWidgetManagerCompat {
 
     private static final Object sInstanceLock = new Object();
@@ -63,11 +62,9 @@
     }
 
     public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
-                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
-            return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId);
+        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+            return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
         }
-
         AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
         return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
     }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index 1065748..c8b1f67 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -24,12 +24,15 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -37,8 +40,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
 
     private final UserManager mUserManager;
@@ -54,14 +55,11 @@
             return Collections.emptyList();
         }
         if (packageUser == null) {
-            ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
+            ArrayList<AppWidgetProviderInfo> providers = new ArrayList<>();
             for (UserHandle user : mUserManager.getUserProfiles()) {
                 providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
             }
-
-            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
-                providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
-            }
+            providers.addAll(getCustomWidgets());
             return providers;
         }
         // Only get providers for the given package/user.
@@ -74,9 +72,9 @@
             }
         }
 
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser)
+        if (Process.myUserHandle().equals(packageUser.mUser)
                 && mContext.getPackageName().equals(packageUser.mPackageName)) {
-            providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
+            providers.addAll(getCustomWidgets());
         }
         return providers;
     }
@@ -87,9 +85,7 @@
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
             return false;
         }
-
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
-                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
             return true;
         }
         return mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -108,9 +104,8 @@
             }
         }
 
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) {
-            for (LauncherAppWidgetProviderInfo info :
-                    CustomWidgetParser.getCustomWidgets(mContext)) {
+        if (Process.myUserHandle().equals(user)) {
+            for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
                 if (info.provider.equals(provider)) {
                     return info;
                 }
@@ -131,13 +126,13 @@
                 result.put(new ComponentKey(info.provider, user), info);
             }
         }
-
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
-            for (LauncherAppWidgetProviderInfo info :
-                    CustomWidgetParser.getCustomWidgets(mContext)) {
-                result.put(new ComponentKey(info.provider, info.getProfile()), info);
-            }
+        for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
+            result.put(new ComponentKey(info.provider, info.getProfile()), info);
         }
         return result;
     }
+
+    List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+        return CustomWidgetManager.INSTANCE.get(mContext).getCustomWidgets();
+    }
 }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
index b7b0563..11ec333 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -19,14 +19,14 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.Collections;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
 
     AppWidgetManagerCompatVO(Context context) {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
index 58fc73d..39f6949 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompat.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -22,30 +22,34 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
 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.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 public abstract class LauncherAppsCompat {
 
     public interface OnAppsChangedCallbackCompat {
-        void onPackageRemoved(String packageName, UserHandle user);
-        void onPackageAdded(String packageName, UserHandle user);
-        void onPackageChanged(String packageName, UserHandle user);
-        void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing);
-        void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
-        void onPackagesSuspended(String[] packageNames, UserHandle user);
-        void onPackagesUnsuspended(String[] packageNames, UserHandle user);
-        void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts,
-                UserHandle user);
+        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() {
@@ -88,4 +92,8 @@
             @Nullable PackageUserKey packageUser);
 
     public abstract List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions();
+
+    public abstract void registerSessionCallback(LooperExecutor executor,
+                                                 SessionCallback sessionCallback);
+    public abstract void unregisterSessionCallback(SessionCallback sessionCallback);
 }
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
index 1d19b53..281274c 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -23,6 +23,7 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -31,11 +32,15 @@
 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.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import androidx.annotation.NonNull;
@@ -167,6 +172,10 @@
 
         @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);
         }
 
@@ -205,5 +214,17 @@
     public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
         return mContext.getPackageManager().getPackageInstaller().getAllSessions();
     }
+
+    @Override
+    public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+        mContext.getPackageManager().getPackageInstaller().registerSessionCallback(sessionCallback,
+                executor.getHandler());
+    }
+
+    @Override
+    public void unregisterSessionCallback(SessionCallback sessionCallback) {
+        mContext.getPackageManager().getPackageInstaller()
+                .unregisterSessionCallback(sessionCallback);
+    }
 }
 
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 6e7a1bd..5e13d00 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -16,6 +16,8 @@
 
 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;
@@ -30,19 +32,17 @@
 import android.os.Process;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 @TargetApi(26)
 public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
 
@@ -120,7 +120,7 @@
                 }
             } else {
                 // Block the worker thread until the accept() is called.
-                new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() {
+                MODEL_EXECUTOR.execute(new Runnable() {
                     @Override
                     public void run() {
                         try {
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
index 0a1811e..48805af 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVQ.java
@@ -18,8 +18,10 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionCallback;
+
+import com.android.launcher3.util.LooperExecutor;
 
 import java.util.List;
 
@@ -33,4 +35,14 @@
     public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
         return mLauncherApps.getAllPackageInstallerSessions();
     }
+
+    @Override
+    public void registerSessionCallback(LooperExecutor executor, SessionCallback sessionCallback) {
+        mLauncherApps.registerPackageInstallerSessionCallback(executor, sessionCallback);
+    }
+
+    @Override
+    public void unregisterSessionCallback(SessionCallback sessionCallback) {
+        mLauncherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
+    }
 }
diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java
index 7dad7e9..55df98b 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompat.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java
@@ -19,14 +19,25 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+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;
+import com.android.launcher3.util.PackageUserKey;
+
 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;
@@ -43,10 +54,19 @@
         }
     }
 
+    public static UserHandle getUserHandle(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();
+    public abstract HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache();
+
+    /**
+     * @return an active SessionInfo for {@param pkg} or null if none exists.
+     */
+    public abstract SessionInfo getActiveSessionInfo(UserHandle user, String pkg);
 
     public abstract void onStop();
 
@@ -55,30 +75,44 @@
         public final String packageName;
         public final int state;
         public final int progress;
+        public final UserHandle user;
 
-        private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) {
+        private PackageInstallInfo(@NonNull 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) {
+        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) {
+        public static PackageInstallInfo fromInstallingState(SessionInfo info) {
             return new PackageInstallInfo(info);
         }
 
-        public static PackageInstallInfo fromState(int state, String packageName) {
-            return new PackageInstallInfo(packageName, state, 0 /* progress */);
+        public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) {
+            return new PackageInstallInfo(packageName, state, 0 /* progress */, user);
         }
 
     }
 
-    public abstract List<PackageInstaller.SessionInfo> getAllVerifiedSessions();
+    public abstract List<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
index a34ca50..409b21d 100644
--- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
+++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java
@@ -16,22 +16,26 @@
 
 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.os.Handler;
-import android.os.Process;
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
+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.LauncherModel;
 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;
@@ -39,42 +43,78 @@
 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<String> mActiveSessions = new SparseArray<>();
+    @Thunk final SparseArray<PackageUserKey> mActiveSessions = new SparseArray<>();
 
     @Thunk final PackageInstaller mInstaller;
     private final IconCache mCache;
-    private final Handler mWorker;
     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();
-        mWorker = new Handler(LauncherModel.getWorkerLooper());
-        mInstaller.registerSessionCallback(mCallback, mWorker);
         mLauncherApps = LauncherAppsCompat.getInstance(context);
+        mLauncherApps.registerSessionCallback(MODEL_EXECUTOR, mCallback);
+        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<>();
-        UserHandle user = Process.myUserHandle();
+    public HashMap<PackageUserKey, SessionInfo> updateAndGetActiveSessionCache() {
+        HashMap<PackageUserKey, SessionInfo> activePackages = new HashMap<>();
         for (SessionInfo info : getAllVerifiedSessions()) {
-            addSessionInfoToCache(info, user);
+            addSessionInfoToCache(info, getUserHandle(info));
             if (info.getAppPackageName() != null) {
-                activePackages.put(info.getAppPackageName(), info);
-                mActiveSessions.put(info.getSessionId(), info.getAppPackageName());
+                activePackages.put(new PackageUserKey(info.getAppPackageName(),
+                        getUserHandle(info)), 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) {
@@ -85,7 +125,7 @@
 
     @Override
     public void onStop() {
-        mInstaller.unregisterSessionCallback(mCallback);
+        mLauncherApps.unregisterSessionCallback(mCallback);
     }
 
     @Thunk void sendUpdate(PackageInstallInfo info) {
@@ -95,6 +135,30 @@
         }
     }
 
+    /**
+     * 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
+     */
+    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())
+                && mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0,
+                        getUserHandle(sessionInfo)) == null) {
+            SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+            mPromiseIconIds.add(sessionInfo.getSessionId());
+            updatePromiseIconPrefs();
+        }
+    }
+
     private final SessionCallback mCallback = new SessionCallback() {
 
         @Override
@@ -107,19 +171,31 @@
                             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.
-            String packageName = mActiveSessions.get(sessionId);
+            PackageUserKey key = mActiveSessions.get(sessionId);
             mActiveSessions.remove(sessionId);
 
-            if (packageName != null) {
-                sendUpdate(PackageInstallInfo.fromState(
-                        success ? STATUS_INSTALLED : STATUS_FAILED,
-                        packageName));
+            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()
+                        && mPromiseIconIds.contains(sessionId)) {
+                    LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+                    if (appState != null) {
+                        appState.getModel().onSessionFailure(packageName, key.mUser);
+                    }
+                    // If it is successful, the id is removed in the the package added flow.
+                    removePromiseIconId(sessionId);
+                }
             }
         }
 
@@ -136,17 +212,23 @@
 
         @Override
         public void onBadgingChanged(int sessionId) {
-            pushSessionDisplayToLauncher(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(sessionId, session.getAppPackageName());
-                addSessionInfoToCache(session, Process.myUserHandle());
+                UserHandle user = getUserHandle(session);
+                mActiveSessions.put(session.getSessionId(),
+                        new PackageUserKey(session.getAppPackageName(), user));
+                addSessionInfoToCache(session, user);
                 LauncherAppState app = LauncherAppState.getInstanceNoCreate();
                 if (app != null) {
-                    app.getModel().updateSessionDisplayInfo(session.getAppPackageName());
+                    app.getModel().updateSessionDisplayInfo(session.getAppPackageName(),
+                            user);
                 }
                 return session;
             }
@@ -165,7 +247,7 @@
             if (!mSessionVerifiedMap.containsKey(pkg)) {
                 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext);
                 boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg,
-                        ApplicationInfo.FLAG_SYSTEM, Process.myUserHandle()) != null;
+                        ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null;
                 mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
             }
         }
@@ -185,4 +267,23 @@
         }
         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/BaseFlags.java
index 45639e0..4b90e42 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -18,19 +18,15 @@
 
 import static androidx.core.util.Preconditions.checkNotNull;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.Keep;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.TogglableFlag;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -41,11 +37,9 @@
  * Defines a set of flags used to control various launcher behaviors.
  *
  * <p>All the flags should be defined here with appropriate default values.
- *
- * <p>This class is kept package-private to prevent direct access.
  */
 @Keep
-abstract class BaseFlags {
+public abstract class BaseFlags {
 
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
@@ -66,6 +60,11 @@
     // 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;
 
+    // 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;
 
@@ -75,9 +74,6 @@
     //Feature flag to enable pulling down navigation shade from workspace.
     public static final boolean PULL_DOWN_STATUS_BAR = true;
 
-    // When true, custom widgets are loaded using CustomWidgetParser.
-    public static final boolean ENABLE_CUSTOM_WIDGETS = false;
-
     // Features to control Launcher3Go behavior
     public static final boolean GO_DISABLE_WIDGETS = false;
 
@@ -105,22 +101,38 @@
             "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
 
     public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
-            "ENABLE_HINTS_IN_OVERVIEW", false,
+            "ENABLE_HINTS_IN_OVERVIEW", true,
             "Show chip hints and gleams on the overview screen");
 
     public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
             "FAKE_LANDSCAPE_UI", false,
             "Rotate launcher UI instead of using transposed layout");
 
+    public static final TogglableFlag FOLDER_NAME_SUGGEST = new TogglableFlag(
+            "FOLDER_NAME_SUGGEST", true,
+            "Suggests folder names instead of blank text.");
+
+    public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag(
+            "APP_SEARCH_IMPROVEMENTS", true,
+            "Adds localized title and keyword search and ranking");
+
+    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 ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
+            "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
+            "Allow Launcher to handle nav bar gestures while Assistant is running over it");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
             synchronized (sLock) {
-                for (TogglableFlag flag : sFlags) {
+                for (BaseTogglableFlag flag : sFlags) {
                     flag.initialize(context);
                 }
             }
         }
+        APP_SEARCH_IMPROVEMENTS.initialize(context);
     }
 
     static List<TogglableFlag> getTogglableFlags() {
@@ -132,27 +144,30 @@
         SortedMap<String, TogglableFlag> flagsByKey = new TreeMap<>();
         synchronized (sLock) {
             for (TogglableFlag flag : sFlags) {
-                flagsByKey.put(flag.key, flag);
+                flagsByKey.put(((BaseTogglableFlag) flag).getKey(), flag);
             }
         }
         return new ArrayList<>(flagsByKey.values());
     }
 
-    public static class TogglableFlag {
+    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;
 
-        TogglableFlag(
+        public BaseTogglableFlag(
                 String key,
                 boolean defaultValue,
                 String description) {
             this.key = checkNotNull(key);
             this.currentValue = this.defaultValue = defaultValue;
             this.description = checkNotNull(description);
+
             synchronized (sLock) {
-                sFlags.add(this);
+                sFlags.add((TogglableFlag)this);
             }
         }
 
@@ -162,18 +177,22 @@
             currentValue = value;
         }
 
-        @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
         public String getKey() {
             return key;
         }
-        void initialize(Context context) {
-            currentValue = getFromStorage(context, defaultValue);
+
+        protected void initialize(Context context) {
+            currentValue = getFromStorage(context, getDefaultValue());
         }
 
+        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();
@@ -182,11 +201,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. */
@@ -203,6 +222,8 @@
             return "TogglableFlag{"
                     + "key=" + key + ", "
                     + "defaultValue=" + defaultValue + ", "
+                    + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", "
+                    + "currentValue=" + currentValue + ", "
                     + "description=" + description
                     + "}";
         }
@@ -213,9 +234,9 @@
                 return true;
             }
             if (o instanceof TogglableFlag) {
-                TogglableFlag that = (TogglableFlag) o;
+                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;
@@ -227,54 +248,10 @@
             h$ *= 1000003;
             h$ ^= key.hashCode();
             h$ *= 1000003;
-            h$ ^= defaultValue ? 1231 : 1237;
+            h$ ^= getDefaultValue() ? 1231 : 1237;
             h$ *= 1000003;
             h$ ^= description.hashCode();
             return h$;
         }
     }
-
-    /**
-     * Stores the FeatureFlag's value in Settings.Global instead of our SharedPrefs.
-     * This is useful if we want to be able to control this flag from another process.
-     */
-    public static final class ToggleableGlobalSettingsFlag extends TogglableFlag {
-        private ContentResolver contentResolver;
-
-        ToggleableGlobalSettingsFlag(String key, boolean defaultValue, String description) {
-            super(key, defaultValue, description);
-        }
-
-        @Override
-        public void initialize(Context context) {
-            contentResolver = context.getContentResolver();
-            contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
-                    new ContentObserver(new Handler(Looper.getMainLooper())) {
-                        @Override
-                        public void onChange(boolean selfChange) {
-                            superInitialize(context);
-                    }});
-            superInitialize(context);
-        }
-
-        private void superInitialize(Context context) {
-            super.initialize(context);
-        }
-
-        @Override
-        public void updateStorage(Context context, boolean value) {
-            if (contentResolver == null) {
-                return;
-            }
-            Settings.Global.putInt(contentResolver, getKey(), value ? 1 : 0);
-        }
-
-        @Override
-        boolean getFromStorage(Context context, boolean defaultValue) {
-            if (contentResolver == null) {
-                return defaultValue;
-            }
-            return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
index 5ecb186..54e5322 100644
--- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java
+++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java
@@ -26,12 +26,13 @@
 import android.widget.Toast;
 
 import com.android.launcher3.R;
-import com.android.launcher3.config.BaseFlags.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}.
@@ -62,7 +63,7 @@
 
         @Override
         public boolean getBoolean(String key, boolean defaultValue) {
-            for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+            for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
                 if (flag.getKey().equals(key)) {
                     return flag.getFromStorage(mContext, defaultValue);
                 }
@@ -83,7 +84,7 @@
         // flag with a different value than the default. That way, when we flip flags in
         // future, engineers will pick up the new value immediately. To accomplish this, we use a
         // custom preference data store.
-        for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
+        for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) {
             SwitchPreference switchPreference = new SwitchPreference(mContext);
             switchPreference.setKey(flag.getKey());
             switchPreference.setDefaultValue(flag.getDefaultValue());
@@ -99,7 +100,7 @@
     /**
      * Updates the summary to show the description and whether the flag overrides the default value.
      */
-    private void updateSummary(SwitchPreference switchPreference, TogglableFlag flag) {
+    private void updateSummary(SwitchPreference switchPreference, BaseTogglableFlag flag) {
         String onWarning = flag.getDefaultValue() ? "" : "<b>OVERRIDDEN</b><br>";
         String offWarning = flag.getDefaultValue() ? "<b>OVERRIDDEN</b><br>" : "";
         switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.getDescription()));
@@ -134,7 +135,7 @@
         }
     }
 
-    private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) {
+    private boolean getFlagStateFromSharedPrefs(BaseTogglableFlag flag) {
         return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue());
     }
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index a72089d..9fb1090 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
@@ -47,7 +48,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompatVO;
@@ -55,7 +55,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -153,16 +152,6 @@
         PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
                 img.getBitmap().getWidth(), img.getWidth());
 
-        Intent homeIntent = listener.addToIntent(
-                new Intent(Intent.ACTION_MAIN)
-                        .addCategory(Intent.CATEGORY_HOME)
-                        .setPackage(getPackageName())
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-
-        listener.initWhenReady();
-        startActivity(homeIntent,
-                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
-        mFinishOnPause = true;
 
         // Start a system drag and drop. We use a transparent bitmap as preview for system drag
         // as the preview is handled internally by launcher.
@@ -179,6 +168,18 @@
                 outShadowTouchPoint.set(SHADOW_SIZE / 2, SHADOW_SIZE / 2);
             }
         }, null, View.DRAG_FLAG_GLOBAL);
+
+
+        Intent homeIntent = listener.addToIntent(
+                new Intent(Intent.ACTION_MAIN)
+                        .addCategory(Intent.CATEGORY_HOME)
+                        .setPackage(getPackageName())
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+
+        listener.initWhenReady();
+        startActivity(homeIntent,
+                ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out).toBundle());
+        mFinishOnPause = true;
         return false;
     }
 
@@ -232,7 +233,7 @@
                 mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache());
                 mWidgetCell.ensurePreview();
             }
-        }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper()));
+        }.executeOnExecutor(MODEL_EXECUTOR);
         // TODO: Create a worker looper executor and reuse that everywhere.
     }
 
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index c719c1c..1b08723 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -137,9 +137,6 @@
 
     @Override
     public boolean shouldStartDrag(double distanceDragged) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_DRAG_TAG, "BIDL.shouldStartDrag");
-        }
         // Stay in pre-drag mode, if workspace is locked.
         return !mLauncher.isWorkspaceLocked();
     }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 72a1abb..dcdf5d6 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.ATLEAST_Q;
 
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -56,6 +57,12 @@
 public class DragController implements DragDriver.EventListener, TouchController {
     private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
 
+    /**
+     * When a drag is started from a deep press, you need to drag this much farther than normal to
+     * end a pre-drag. See {@link DragOptions.PreDragCondition#shouldStartDrag(double)}.
+     */
+    private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
+
     @Thunk Launcher mLauncher;
     private FlingToDeleteHelper mFlingToDeleteHelper;
 
@@ -91,9 +98,10 @@
 
     private DropTarget mLastDropTarget;
 
-    @Thunk int mLastTouch[] = new int[2];
-    @Thunk long mLastTouchUpTime = -1;
-    @Thunk int mDistanceSinceScroll = 0;
+    private final int[] mLastTouch = new int[2];
+    private long mLastTouchUpTime = -1;
+    private int mLastTouchClassification;
+    private int mDistanceSinceScroll = 0;
 
     private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
@@ -204,7 +212,7 @@
         }
 
         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        dragView.show(mMotionDownX, mMotionDownY);
+        dragView.show(mLastTouch[0], mLastTouch[1]);
         mDistanceSinceScroll = 0;
 
         if (!mIsInPreDrag) {
@@ -213,9 +221,7 @@
             mOptions.preDragCondition.onPreDragStart(mDragObject);
         }
 
-        mLastTouch[0] = mMotionDownX;
-        mLastTouch[1] = mMotionDownY;
-        handleMoveEvent(mMotionDownX, mMotionDownY);
+        handleMoveEvent(mLastTouch[0], mLastTouch[1]);
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
         return dragView;
     }
@@ -430,6 +436,11 @@
         final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
         final int dragLayerX = dragLayerPos[0];
         final int dragLayerY = dragLayerPos[1];
+        mLastTouch[0] = dragLayerX;
+        mLastTouch[1] = dragLayerY;
+        if (ATLEAST_Q) {
+            mLastTouchClassification = ev.getClassification();
+        }
 
         switch (action) {
             case MotionEvent.ACTION_DOWN:
@@ -474,10 +485,6 @@
     }
 
     private void handleMoveEvent(int x, int y) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "handleMoveEvent 1");
-        }
         mDragObject.dragView.move(x, y);
 
         // Drop on someone?
@@ -492,22 +499,12 @@
         mLastTouch[0] = x;
         mLastTouch[1] = y;
 
-        if (TestProtocol.sDebugTracing) {
-           Log.d(TestProtocol.NO_DRAG_TAG,
-                    "handleMoveEvent Conditions " +
-                            mIsInPreDrag + ", " +
-                            (mIsInPreDrag && mOptions.preDragCondition != null) + ", " +
-                            (mIsInPreDrag && mOptions.preDragCondition != null
-                                    && mOptions.preDragCondition.shouldStartDrag(
-                                    mDistanceSinceScroll)));
+        int distanceDragged = mDistanceSinceScroll;
+        if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
+            distanceDragged /= DEEP_PRESS_DISTANCE_FACTOR;
         }
-
         if (mIsInPreDrag && mOptions.preDragCondition != null
-                && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                        "handleMoveEvent 2");
-            }
+                && mOptions.preDragCondition.shouldStartDrag(distanceDragged)) {
             callOnDragStart();
         }
     }
@@ -545,10 +542,6 @@
      * Call this from a drag source view.
      */
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "onControllerTouchEvent");
-        }
         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
             return false;
         }
@@ -601,6 +594,9 @@
     }
 
     private void drop(DropTarget dropTarget, Runnable flingAnimation) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragController.drop");
+        }
         final int[] coordinates = mCoordinatesTemp;
         mDragObject.x = coordinates[0];
         mDragObject.y = coordinates[1];
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 84fc94d..01e0f92 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -17,10 +17,12 @@
 package com.android.launcher3.dragndrop;
 
 import android.content.Context;
+import android.util.Log;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
 import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Base class for driving a drag/drop operation.
@@ -52,10 +54,16 @@
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_UP:
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_UP");
+                }
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
                 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 6ba015b..0b6d8fb 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -48,12 +48,13 @@
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.uioverrides.UiFactory;
@@ -92,7 +93,8 @@
 
     // Related to adjacent page hints
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
-    private final WorkspaceAndHotseatScrim mScrim;
+    private final WorkspaceAndHotseatScrim mWorkspaceScrim;
+    private final OverviewScrim mOverviewScrim;
 
     /**
      * Used to create a new DragLayer from XML.
@@ -108,12 +110,13 @@
         setChildrenDrawingOrderEnabled(true);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
-        mScrim = new WorkspaceAndHotseatScrim(this);
+        mWorkspaceScrim = new WorkspaceAndHotseatScrim(this);
+        mOverviewScrim = new OverviewScrim(this);
     }
 
     public void setup(DragController dragController, Workspace workspace) {
         mDragController = dragController;
-        mScrim.setWorkspace(workspace);
+        mWorkspaceScrim.setWorkspace(workspace);
         recreateControllers();
     }
 
@@ -281,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);
@@ -529,25 +533,39 @@
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the background below children.
-        mScrim.draw(canvas);
+        mWorkspaceScrim.draw(canvas);
+        mOverviewScrim.updateCurrentScrimmedView(this);
         mFocusIndicatorHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
 
     @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (child == mOverviewScrim.getScrimmedView()) {
+            mOverviewScrim.draw(canvas);
+        }
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
-        mScrim.setSize(w, h);
+        mWorkspaceScrim.setSize(w, h);
     }
 
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
-        mScrim.onInsetsChanged(insets);
+        mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
+        mOverviewScrim.onInsetsChanged(insets);
     }
 
     public WorkspaceAndHotseatScrim getScrim() {
-        return mScrim;
+        return mWorkspaceScrim;
+    }
+
+    public OverviewScrim getOverviewScrim() {
+        return mOverviewScrim;
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 09c5e5b..f66d07e 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.dragndrop;
 
 import static com.android.launcher3.Utilities.getBadge;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -41,16 +42,19 @@
 import android.os.Looper;
 import android.view.View;
 
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
 import com.android.launcher3.FastBitmapDrawable;
+import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.util.Themes;
@@ -58,10 +62,6 @@
 
 import java.util.Arrays;
 
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
 public class DragView extends View implements LauncherStateManager.StateListener {
     private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
     private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -210,7 +210,7 @@
             return;
         }
         // Load the adaptive icon on a background thread and add the view in ui thread.
-        new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(new Runnable() {
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
                 Object[] outObj = new Object[1];
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/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 0c5a1fc..0bb3fba 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -28,8 +30,9 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.Launcher;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
@@ -66,20 +69,24 @@
         return mBadge;
     }
 
-    public static FolderAdaptiveIcon createFolderAdaptiveIcon(
+    public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
             Launcher launcher, int folderId, Point dragViewSize) {
         Preconditions.assertNonUiThread();
         int margin = launcher.getResources()
                 .getDimensionPixelSize(R.dimen.blur_size_medium_outline);
 
         // Allocate various bitmaps on the background thread, because why not!
-        final Bitmap badge = Bitmap.createBitmap(
-                dragViewSize.x - margin, dragViewSize.y - margin, Bitmap.Config.ARGB_8888);
+        int width = dragViewSize.x - margin;
+        int height = dragViewSize.y - margin;
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+        final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
         // Create the actual drawable on the UI thread to avoid race conditions with
         // FolderIcon draw pass
         try {
-            return new MainThreadExecutor().submit(() -> {
+            return MAIN_EXECUTOR.submit(() -> {
                 FolderIcon icon = launcher.findFolderIcon(folderId);
                 return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize);
             }).get();
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2ef6d70..8df5e7d 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -17,6 +17,8 @@
 package com.android.launcher3.folder;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
@@ -26,12 +28,12 @@
 import android.annotation.SuppressLint;
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.text.InputType;
 import android.text.Selection;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Pair;
@@ -74,7 +76,11 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+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;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
@@ -126,9 +132,6 @@
     private static final Rect sTempRect = new Rect();
     private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
 
-    private static String sDefaultFolderName;
-    private static String sHintText;
-
     private final Alarm mReorderAlarm = new Alarm();
     private final Alarm mOnExitAlarm = new Alarm();
     private final Alarm mOnScrollHintAlarm = new Alarm();
@@ -148,7 +151,7 @@
     public ExtendedEditText mFolderName;
     private PageIndicatorDots mPageIndicator;
 
-    private View mFooter;
+    protected View mFooter;
     private int mFooterHeight;
 
     // Cell ranks used for drag and drop
@@ -173,8 +176,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")
@@ -196,8 +197,6 @@
         super(context, attrs);
         setAlwaysDrawnWithCacheEnabled(false);
 
-        setLocaleDependentFields(getResources(), false /* force */);
-
         mLauncher = Launcher.getLauncher(context);
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -315,10 +314,15 @@
         // 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);
 
-        mFolderName.setHint(sDefaultFolderName.contentEquals(newTitle) ? sHintText : null);
+        if (TextUtils.isEmpty(mInfo.title)) {
+            mFolderName.setHint(R.string.folder_hint_text);
+        } else {
+            mFolderName.setHint(null);
+        }
 
         sendCustomAccessibilityEvent(
                 this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
@@ -385,7 +389,7 @@
         mInfo = info;
         ArrayList<WorkspaceItemInfo> children = info.contents;
         Collections.sort(children, ITEM_POS_COMPARATOR);
-        mContent.bindItems(children);
+        updateItemLocationsInDatabaseBatch();
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
         if (lp == null) {
@@ -393,30 +397,40 @@
             lp.customPosition = true;
             setLayoutParams(lp);
         }
-        centerAboutIcon();
-
         mItemsInvalidated = true;
-        updateTextViewFocus();
         mInfo.addListener(this);
 
-        if (!sDefaultFolderName.contentEquals(mInfo.title)) {
+        if (!TextUtils.isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
             mFolderName.setHint(null);
         } else {
             mFolderName.setText("");
-            mFolderName.setHint(sHintText);
+            mFolderName.setHint(R.string.folder_hint_text);
         }
-
         // 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();
             }
         });
     }
 
+
+    /**
+     * Show suggested folder title.
+     */
+    public void showSuggestedTitle(CharSequence suggestName) {
+        if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) {
+            if (!TextUtils.isEmpty(suggestName)) {
+                mFolderName.setHint(suggestName);
+                mFolderName.setText(suggestName);
+                mFolderName.showKeyboard();
+                mInfo.title = suggestName;
+            }
+            animateOpen();
+        }
+    }
+
     /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
@@ -431,9 +445,6 @@
     }
 
     private void startAnimation(final AnimatorSet a) {
-        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
-            mCurrentAnimator.cancel();
-        }
         final Workspace workspace = mLauncher.getWorkspace();
         final CellLayout currentCellLayout =
                 (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
@@ -473,17 +484,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,23 +543,21 @@
         }
 
         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
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
-        centerAboutIcon();
-
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mFolderIcon.setBackgroundVisible(false);
+                mFolderIcon.setIconVisible(false);
                 mFolderIcon.drawLeaveBehindIfExists();
             }
             @Override
@@ -524,7 +565,11 @@
                 mState = STATE_OPEN;
                 announceAccessibilityChanges();
 
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("folder opened");
+                mLauncher.getUserEventDispatcher().logActionOnItem(
+                        Touch.TAP,
+                        Direction.NONE,
+                        ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+
                 mContent.setFocusOnFirstChild();
             }
         });
@@ -570,20 +615,9 @@
         if (mDragController.isDragging()) {
             mDragController.forceTouchMove();
         }
-
         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;
@@ -619,6 +653,9 @@
     }
 
     private void animateClosed() {
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
         a.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -646,7 +683,7 @@
         clearFocus();
         if (mFolderIcon != null) {
             mFolderIcon.setVisibility(View.VISIBLE);
-            mFolderIcon.setBackgroundVisible(true);
+            mFolderIcon.setIconVisible(true);
             mFolderIcon.mFolderName.setTextVisibility(true);
             if (wasAnimated) {
                 mFolderIcon.animateBgShadowAndStroke();
@@ -668,6 +705,8 @@
             } else if (mDragInProgress) {
                 mDeleteFolderOnDropCompleted = true;
             }
+        } else if (!mDragInProgress) {
+            mContent.unbindItems();
         }
         mSuppressFolderDeletion = false;
         clearDragInfo();
@@ -822,9 +861,9 @@
         }
     }
 
+    @Override
     public void onDropCompleted(final View target, final DragObject d,
             final boolean success) {
-
         if (success) {
             if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) {
                 replaceFolderWithFinalItem();
@@ -834,9 +873,9 @@
             WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo;
             View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
                     ? mCurrentDragView : mContent.createNewView(info);
-            ArrayList<View> views = getItemsInReadingOrder();
+            ArrayList<View> views = getIconsInReadingOrder();
             views.add(info.rank, icon);
-            mContent.arrangeChildren(views, views.size());
+            mContent.arrangeChildren(views);
             mItemsInvalidated = true;
 
             try (SuppressInfoChanges s = new SuppressInfoChanges()) {
@@ -863,7 +902,6 @@
         // Reordering may have occured, and we need to save the new item locations. We do this once
         // at the end to prevent unnecessary database operations.
         updateItemLocationsInDatabaseBatch();
-
         // Use the item count to check for multi-page as the folder UI may not have
         // been refreshed yet.
         if (getItemCount() <= mContent.itemsPerPage()) {
@@ -874,16 +912,21 @@
     }
 
     private void updateItemLocationsInDatabaseBatch() {
-        ArrayList<View> list = getItemsInReadingOrder();
-        ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
-        for (int i = 0; i < list.size(); i++) {
-            View v = list.get(i);
-            ItemInfo info = (ItemInfo) v.getTag();
-            info.rank = i;
-            items.add(info);
+        FolderGridOrganizer verifier = new FolderGridOrganizer(
+                mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+
+        ArrayList<ItemInfo> items = new ArrayList<>();
+        int total = mInfo.contents.size();
+        for (int i = 0; i < total; i++) {
+            WorkspaceItemInfo itemInfo = mInfo.contents.get(i);
+            if (verifier.updateRankAndPos(itemInfo, i)) {
+                items.add(itemInfo);
+            }
         }
 
-        mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
+        if (!items.isEmpty()) {
+            mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0);
+        }
     }
 
     public void notifyDrop() {
@@ -948,28 +991,16 @@
         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() {
+    protected int getContentAreaHeight() {
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        int maxContentAreaHeight = grid.availableHeightPx
-                - grid.getTotalWorkspacePadding().y - mFooterHeight;
+        int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
+                - mFooterHeight;
         int height = Math.min(maxContentAreaHeight,
                 mContent.getDesiredHeight());
         return Math.max(height, MIN_CONTENT_DIMEN);
@@ -1021,22 +1052,12 @@
      * Rearranges the children based on their rank.
      */
     public void rearrangeChildren() {
-        rearrangeChildren(-1);
-    }
-
-    /**
-     * Rearranges the children based on their rank.
-     * @param itemCount if greater than the total children count, empty spaces are left at the end,
-     * otherwise it is ignored.
-     */
-    public void rearrangeChildren(int itemCount) {
-        ArrayList<View> views = getItemsInReadingOrder();
-        mContent.arrangeChildren(views, Math.max(itemCount, views.size()));
+        mContent.arrangeChildren(getIconsInReadingOrder());
         mItemsInvalidated = true;
     }
 
     public int getItemCount() {
-        return mContent.getItemCount();
+        return mInfo.contents.size();
     }
 
     @Thunk void replaceFolderWithFinalItem() {
@@ -1044,7 +1065,7 @@
         Runnable onCompleteRunnable = new Runnable() {
             @Override
             public void run() {
-                int itemCount = mInfo.contents.size();
+                int itemCount = getItemCount();
                 if (itemCount <= 1) {
                     View newIcon = null;
 
@@ -1121,9 +1142,12 @@
                     return false;
                 }
             });
+        } else {
+            setOnKeyListener(null);
         }
     }
 
+    @Override
     public void onDrop(DragObject d, DragOptions options) {
         // If the icon was dropped while the page was being scrolled, we need to compute
         // the target location again such that the icon is placed of the final page.
@@ -1171,12 +1195,6 @@
                 // before creating the view, so that WorkspaceItemInfo is updated appropriately.
                 mLauncher.getModelWriter().addOrMoveItemInDatabase(
                         si, mInfo.id, 0, si.cellX, si.cellY);
-
-                // We only need to update the locations if it doesn't get handled in
-                // #onDropCompleted.
-                if (d.dragSource != this) {
-                    updateItemLocationsInDatabaseBatch();
-                }
                 mIsExternalDrag = false;
             } else {
                 currentDragView = mCurrentDragView;
@@ -1203,7 +1221,13 @@
 
             // Temporarily suppress the listener, as we did all the work already here.
             try (SuppressInfoChanges s = new SuppressInfoChanges()) {
-                mInfo.add(si, false);
+                mInfo.add(si, mEmptyCellRank, false);
+            }
+
+            // We only need to update the locations if it doesn't get handled in
+            // #onDropCompleted.
+            if (d.dragSource != this) {
+                updateItemLocationsInDatabaseBatch();
             }
         }
 
@@ -1226,22 +1250,29 @@
     // to correspond to the animation of the icon back into the folder. This is
     public void hideItem(WorkspaceItemInfo info) {
         View v = getViewForInfo(info);
-        v.setVisibility(INVISIBLE);
+        if (v != null) {
+            v.setVisibility(INVISIBLE);
+        }
     }
     public void showItem(WorkspaceItemInfo info) {
         View v = getViewForInfo(info);
-        v.setVisibility(VISIBLE);
+        if (v != null) {
+            v.setVisibility(VISIBLE);
+        }
     }
 
     @Override
     public void onAdd(WorkspaceItemInfo item, int rank) {
-        View view = mContent.createAndAddViewForRank(item, rank);
+        FolderGridOrganizer verifier = new FolderGridOrganizer(
+                mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+        verifier.updateRankAndPos(item, rank);
         mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
                 item.cellY);
+        updateItemLocationsInDatabaseBatch();
 
-        ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder());
-        items.add(rank, view);
-        mContent.arrangeChildren(items, items.size());
+        if (mContent.areViewsBound()) {
+            mContent.createAndAddViewForRank(item, rank);
+        }
         mItemsInvalidated = true;
     }
 
@@ -1264,13 +1295,7 @@
     }
 
     private View getViewForInfo(final WorkspaceItemInfo item) {
-        return mContent.iterateOverItems(new ItemOperator() {
-
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info == item;
-            }
-        });
+        return mContent.iterateOverItems((info, view) -> info == item);
     }
 
     @Override
@@ -1278,32 +1303,27 @@
         updateTextViewFocus();
     }
 
-    @Override
-    public void prepareAutoUpdate() {
-        close(false);
+    /**
+     * Utility methods to iterate over items of the view
+     */
+    public void iterateOverItems(ItemOperator op) {
+        mContent.iterateOverItems(op);
     }
 
-    public void onTitleChanged(CharSequence title) {
-    }
-
-    public ArrayList<View> getItemsInReadingOrder() {
+    /**
+     * Returns the sorted list of all the icons in the folder
+     */
+    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;
     }
 
     public List<BubbleTextView> getItemsOnPage(int page) {
-        ArrayList<View> allItems = getItemsInReadingOrder();
+        ArrayList<View> allItems = getIconsInReadingOrder();
         int lastPage = mContent.getPageCount() - 1;
         int totalItemsInFolder = allItems.size();
         int itemsPerPage = mContent.itemsPerPage();
@@ -1326,6 +1346,9 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
+                if (isEditingName()) {
+                    logEditFolderLabel();
+                }
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1482,15 +1505,6 @@
         return false;
     }
 
-    public static void setLocaleDependentFields(Resources res, boolean force) {
-        if (sDefaultFolderName == null || force) {
-            sDefaultFolderName = res.getString(R.string.folder_name);
-        }
-        if (sHintText == null || force) {
-            sHintText = res.getString(R.string.folder_hint_text);
-        }
-    }
-
     /**
      * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
      * rounded rect.
@@ -1512,4 +1526,39 @@
             super.draw(canvas);
         }
     }
+
+    private void logEditFolderLabel() {
+        LauncherLogProto.LauncherEvent ev = new LauncherLogProto.LauncherEvent();
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        action.type = LauncherLogProto.Action.Type.SOFT_KEYBOARD;
+        ev.action = action;
+
+        LauncherLogProto.Target edittext_target = new LauncherLogProto.Target();
+        edittext_target.type = LauncherLogProto.Target.Type.ITEM;
+        edittext_target.itemType = LauncherLogProto.ItemType.EDITTEXT;
+
+        LauncherLogProto.Target folder_target = new LauncherLogProto.Target();
+        folder_target.type = LauncherLogProto.Target.Type.CONTAINER;
+        folder_target.containerType = LauncherLogProto.ContainerType.FOLDER;
+        folder_target.pageIndex = mInfo.screenId;
+        folder_target.gridX = mInfo.cellX;
+        folder_target.gridY = mInfo.cellY;
+        folder_target.cardinality = mInfo.contents.size();
+
+        LauncherLogProto.Target parent_target = new LauncherLogProto.Target();
+        parent_target.type = LauncherLogProto.Target.Type.CONTAINER;
+        switch (mInfo.container) {
+            case CONTAINER_HOTSEAT:
+                parent_target.containerType = LauncherLogProto.ContainerType.HOTSEAT;
+                break;
+            case CONTAINER_DESKTOP:
+                parent_target.containerType = LauncherLogProto.ContainerType.WORKSPACE;
+                break;
+            default:
+                Log.e(TAG, String.format("Expected container to be either %s or %s but found %s.",
+                        CONTAINER_HOTSEAT, CONTAINER_DESKTOP, mInfo.container));
+        }
+        ev.srcTarget = new LauncherLogProto.Target[]{edittext_target, folder_target, parent_target};
+        mLauncher.getUserEventDispatcher().dispatchUserEvent(ev, null);
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 962f215..1310d37 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -59,6 +59,8 @@
  */
 public class FolderAnimationManager {
 
+    private static final int FOLDER_NAME_ALPHA_DURATION = 32;
+
     private Folder mFolder;
     private FolderPagedView mContent;
     private GradientDrawable mFolderBackground;
@@ -79,7 +81,7 @@
     private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
 
     private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
-
+    private final FolderGridOrganizer mPreviewVerifier;
 
     public FolderAnimationManager(Folder folder, boolean isOpening) {
         mFolder = folder;
@@ -91,6 +93,7 @@
 
         mContext = folder.getContext();
         mLauncher = folder.mLauncher;
+        mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
 
         mIsOpening = isOpening;
 
@@ -113,7 +116,7 @@
     public AnimatorSet getAnimator() {
         final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
         ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
-        final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems();
+        final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
 
         // Match position of the FolderIcon
         final Rect folderIconPos = new Rect();
@@ -129,11 +132,19 @@
                 * scaleRelativeToDragLayer;
         final float finalScale = 1f;
         float scale = mIsOpening ? initialScale : finalScale;
-        mFolder.setScaleX(scale);
-        mFolder.setScaleY(scale);
         mFolder.setPivotX(0);
         mFolder.setPivotY(0);
 
+        // Scale the contents of the folder.
+        mFolder.mContent.setScaleX(scale);
+        mFolder.mContent.setScaleY(scale);
+        mFolder.mContent.setPivotX(0);
+        mFolder.mContent.setPivotY(0);
+        mFolder.mFooter.setScaleX(scale);
+        mFolder.mFooter.setScaleY(scale);
+        mFolder.mFooter.setPivotX(0);
+        mFolder.mFooter.setPivotY(0);
+
         // We want to create a small X offset for the preview items, so that they follow their
         // expected path to their final locations. ie. an icon should not move right, if it's final
         // location is to its left. This value is arbitrarily defined.
@@ -142,14 +153,13 @@
             previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX);
         }
 
-        final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft())
-                * initialScale);
-        final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop())
-                * initialScale);
+        final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale);
+        final int paddingOffsetY = (int) (mContent.getPaddingTop() * initialScale);
 
-        int initialX = folderIconPos.left + mPreviewBackground.getOffsetX() - paddingOffsetX
-                - previewItemOffsetX;
-        int initialY = folderIconPos.top + mPreviewBackground.getOffsetY() - paddingOffsetY;
+        int initialX = folderIconPos.left + mFolder.getPaddingLeft()
+                + mPreviewBackground.getOffsetX() - paddingOffsetX - previewItemOffsetX;
+        int initialY = folderIconPos.top + mFolder.getPaddingTop()
+                + mPreviewBackground.getOffsetY() - paddingOffsetY;
         final float xDistance = initialX - lp.x;
         final float yDistance = initialY - lp.y;
 
@@ -163,11 +173,10 @@
 
         // Set up the reveal animation that clips the Folder.
         int totalOffsetX = paddingOffsetX + previewItemOffsetX;
-        Rect startRect = new Rect(
-                Math.round(totalOffsetX / initialScale),
-                Math.round(paddingOffsetY / initialScale),
-                Math.round((totalOffsetX + initialSize) / initialScale),
-                Math.round((paddingOffsetY + initialSize) / initialScale));
+        Rect startRect = new Rect(totalOffsetX,
+                paddingOffsetY,
+                Math.round((totalOffsetX + initialSize)),
+                Math.round((paddingOffsetY + initialSize)));
         Rect endRect = new Rect(0, 0, lp.width, lp.height);
         float finalRadius = ResourceUtils.pxFromDp(2, mContext.getResources().getDisplayMetrics());
 
@@ -188,17 +197,46 @@
 
         play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f));
         play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f));
-        play(a, getAnimator(mFolder, SCALE_PROPERTY, initialScale, finalScale));
+        play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale));
+        play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale));
         play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor));
         play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening));
         play(a, getShape().createRevealAnimator(
                 mFolder, startRect, endRect, finalRadius, !mIsOpening));
+        // Fade in the folder name, as the text can overlap the icons when grid size is small.
+        mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f);
+        play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1),
+                mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0,
+                mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION);
+
+        // Translate the footer so that it tracks the bottom of the content.
+        float normalHeight = mFolder.getContentAreaHeight();
+        float scaledHeight = normalHeight * initialScale;
+        float diff = normalHeight - scaledHeight;
+        play(a, getAnimator(mFolder.mFooter, View.TRANSLATION_Y, -diff, 0f));
 
         // Animate the elevation midway so that the shadow is not noticeable in the background.
         int midDuration = mDuration / 2;
         Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
         play(a, z, mIsOpening ? midDuration : 0, midDuration);
 
+
+        // Store clip variables
+        CellLayout cellLayout = mContent.getCurrentCellLayout();
+        boolean folderClipChildren = mFolder.getClipChildren();
+        boolean folderClipToPadding = mFolder.getClipToPadding();
+        boolean contentClipChildren = mContent.getClipChildren();
+        boolean contentClipToPadding = mContent.getClipToPadding();
+        boolean cellLayoutClipChildren = cellLayout.getClipChildren();
+        boolean cellLayoutClipPadding = cellLayout.getClipToPadding();
+
+        mFolder.setClipChildren(false);
+        mFolder.setClipToPadding(false);
+        mContent.setClipChildren(false);
+        mContent.setClipToPadding(false);
+        cellLayout.setClipChildren(false);
+        cellLayout.setClipToPadding(false);
+
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -206,8 +244,20 @@
                 mFolder.setTranslationX(0.0f);
                 mFolder.setTranslationY(0.0f);
                 mFolder.setTranslationZ(0.0f);
-                mFolder.setScaleX(1f);
-                mFolder.setScaleY(1f);
+                mFolder.mContent.setScaleX(1f);
+                mFolder.mContent.setScaleY(1f);
+                mFolder.mFooter.setScaleX(1f);
+                mFolder.mFooter.setScaleY(1f);
+                mFolder.mFooter.setTranslationX(0f);
+                mFolder.mFolderName.setAlpha(1f);
+
+                mFolder.setClipChildren(folderClipChildren);
+                mFolder.setClipToPadding(folderClipToPadding);
+                mContent.setClipChildren(contentClipChildren);
+                mContent.setClipToPadding(contentClipToPadding);
+                cellLayout.setClipChildren(cellLayoutClipChildren);
+                cellLayout.setClipToPadding(cellLayoutClipPadding);
+
             }
         });
 
@@ -226,15 +276,22 @@
     }
 
     /**
+     * Returns the list of "preview items" on {@param page}.
+     */
+    private List<BubbleTextView> getPreviewIconsOnPage(int page) {
+        return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
+                .previewItemsForPage(page, mFolder.getIconsInReadingOrder());
+    }
+
+    /**
      * Animate the items on the current page.
      */
     private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale,
             int previewItemOffsetX, int previewItemOffsetY) {
         ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
         boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
-        final List<BubbleTextView> itemsInPreview = isOnFirstPage
-                ? mFolderIcon.getPreviewItems()
-                : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage());
+        final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(
+                isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage());
         final int numItemsInPreview = itemsInPreview.size();
         final int numItemsInFirstPagePreview = isOnFirstPage
                 ? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW;
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
new file mode 100644
index 0000000..9d14a5f
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -0,0 +1,196 @@
+/*
+ * 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.folder;
+
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
+
+import android.graphics.Point;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for managing item positions in a folder based on rank
+ */
+public class FolderGridOrganizer {
+
+    private final Point mPoint = new Point();
+    private final int mMaxCountX;
+    private final int mMaxCountY;
+    private final int mMaxItemsPerPage;
+
+    private int mNumItemsInFolder;
+    private int mCountX;
+    private int mCountY;
+    private boolean mDisplayingUpperLeftQuadrant = false;
+
+    /**
+     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
+     */
+    public FolderGridOrganizer(InvariantDeviceProfile profile) {
+        mMaxCountX = profile.numFolderColumns;
+        mMaxCountY = profile.numFolderRows;
+        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
+    }
+
+    /**
+     * Updates the organizer with the provided folder info
+     */
+    public FolderGridOrganizer setFolderInfo(FolderInfo info) {
+        return setContentSize(info.contents.size());
+    }
+
+    /**
+     * Updates the organizer to reflect the content size
+     */
+    public FolderGridOrganizer setContentSize(int contentSize) {
+        if (contentSize != mNumItemsInFolder) {
+            calculateGridSize(contentSize);
+
+            mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW;
+            mNumItemsInFolder = contentSize;
+        }
+        return this;
+    }
+
+    public int getCountX() {
+        return mCountX;
+    }
+
+    public int getCountY() {
+        return mCountY;
+    }
+
+    public int getMaxItemsPerPage() {
+        return mMaxItemsPerPage;
+    }
+
+    /**
+     * Calculates the grid size such that {@param count} items can fit in the grid.
+     * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
+     * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
+     */
+    private void calculateGridSize(int count) {
+        boolean done;
+        int gridCountX = mCountX;
+        int gridCountY = mCountY;
+
+        if (count >= mMaxItemsPerPage) {
+            gridCountX = mMaxCountX;
+            gridCountY = mMaxCountY;
+            done = true;
+        } else {
+            done = false;
+        }
+
+        while (!done) {
+            int oldCountX = gridCountX;
+            int oldCountY = gridCountY;
+            if (gridCountX * gridCountY < count) {
+                // Current grid is too small, expand it
+                if ((gridCountX <= gridCountY || gridCountY == mMaxCountY)
+                        && gridCountX < mMaxCountX) {
+                    gridCountX++;
+                } else if (gridCountY < mMaxCountY) {
+                    gridCountY++;
+                }
+                if (gridCountY == 0) gridCountY++;
+            } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
+                gridCountY = Math.max(0, gridCountY - 1);
+            } else if ((gridCountX - 1) * gridCountY >= count) {
+                gridCountX = Math.max(0, gridCountX - 1);
+            }
+            done = gridCountX == oldCountX && gridCountY == oldCountY;
+        }
+
+        mCountX = gridCountX;
+        mCountY = gridCountY;
+    }
+
+    /**
+     * Updates the item's cellX, cellY and rank corresponding to the provided rank.
+     * @return true if there was any change
+     */
+    public boolean updateRankAndPos(ItemInfo item, int rank) {
+        Point pos = getPosForRank(rank);
+        if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) {
+            item.rank = rank;
+            item.cellX = pos.x;
+            item.cellY = pos.y;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the position of the item in the grid
+     */
+    public Point getPosForRank(int rank) {
+        int pagePos = rank % mMaxItemsPerPage;
+        mPoint.x = pagePos % mCountX;
+        mPoint.y = pagePos / mCountX;
+        return mPoint;
+    }
+
+    /**
+     * Returns the preview items for the provided pageNo using the full list of contents
+     */
+    public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) {
+        ArrayList<R> result = new ArrayList<>();
+        int itemsPerPage = mCountX * mCountY;
+        int start = itemsPerPage * page;
+        int end = Math.min(start + itemsPerPage, contents.size());
+
+        for (int i = start, rank = 0; i < end; i++, rank++) {
+            if (isItemInPreview(page, rank)) {
+                result.add((R) contents.get(i));
+            }
+
+            if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns whether the item with rank is in the default Folder icon preview.
+     */
+    public boolean isItemInPreview(int rank) {
+        return isItemInPreview(0, rank);
+    }
+
+    /**
+     * @param page The page the item is on.
+     * @param rank The rank of the item.
+     * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
+     */
+    public boolean isItemInPreview(int page, int rank) {
+        // First page items are laid out such that the first 4 items are always in the upper
+        // left quadrant. For all other pages, we need to check the row and col.
+        if (page > 0 || mDisplayingUpperLeftQuadrant) {
+            int col = rank % mCountX;
+            int row = rank / mCountX;
+            return col < 2 && row < 2;
+        }
+        return rank < MAX_NUM_ITEMS_IN_PREVIEW;
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 250169c..fd6d1e3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -58,22 +58,26 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.IconLabelDotView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 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}.
  */
-public class FolderIcon extends FrameLayout implements FolderListener {
+public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
 
     @Thunk Launcher mLauncher;
     @Thunk Folder mFolder;
@@ -95,11 +99,11 @@
     PreviewBackground mBackground = new PreviewBackground();
     private boolean mBackgroundIsVisible = true;
 
-    FolderIconPreviewVerifier mPreviewVerifier;
+    FolderGridOrganizer mPreviewVerifier;
     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;
 
@@ -107,6 +111,7 @@
 
     private Alarm mOpenAlarm = new Alarm();
 
+    private boolean mForceHideDot;
     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
     private FolderDotInfo mDotInfo = new FolderDotInfo();
     private DotRenderer mDotRenderer;
@@ -173,7 +178,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());
@@ -212,7 +217,7 @@
 
     private void setFolder(Folder folder) {
         mFolder = folder;
-        mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+        mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
         mPreviewVerifier.setFolderInfo(mFolder.getInfo());
         updatePreviewItems(false);
     }
@@ -230,11 +235,7 @@
     }
 
     public void addItem(WorkspaceItemInfo item) {
-        addItem(item, true);
-    }
-
-    public void addItem(WorkspaceItemInfo item, boolean animate) {
-        mInfo.add(item, animate);
+        mInfo.add(item, true);
     }
 
     public void removeItem(WorkspaceItemInfo item, boolean animate) {
@@ -259,7 +260,6 @@
     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
         public void onAlarm(Alarm alarm) {
             mFolder.beginExternalDrag();
-            mFolder.animateOpen();
         }
     };
 
@@ -294,8 +294,7 @@
     }
 
     private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect,
-            float scaleRelativeToDragLayer, int index,
-            boolean itemReturnedOnFailedDrop) {
+            float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
         item.cellX = -1;
         item.cellY = -1;
 
@@ -326,18 +325,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);
-                addItem(item, false);
+                List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
+                mInfo.add(item, index, false);
                 mCurrentPreviewItems.clear();
-                mCurrentPreviewItems.addAll(getPreviewItems());
+                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);
@@ -349,13 +347,13 @@
             }
 
             if (!itemAdded) {
-                addItem(item);
+                mInfo.add(item, index, true);
             }
 
             int[] center = new int[2];
             float scale = getLocalCenterForIndex(index, numItemsInPreview, center);
-            center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
-            center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
+            center[0] = Math.round(scaleRelativeToDragLayer * center[0]);
+            center[1] = Math.round(scaleRelativeToDragLayer * center[1]);
 
             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
                     center[1] - animateView.getMeasuredHeight() / 2);
@@ -372,12 +370,17 @@
 
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
-            postDelayed(new Runnable() {
-                public void run() {
-                    mPreviewItemManager.hidePreviewItem(finalIndex, false);
-                    mFolder.showItem(item);
-                    invalidate();
-                }
+
+            String[] suggestedNameOut = new String[1];
+            if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+                Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
+                        .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
+            }
+            postDelayed(() -> {
+                mPreviewItemManager.hidePreviewItem(finalIndex, false);
+                mFolder.showItem(item);
+                invalidate();
+                mFolder.showSuggestedTitle(suggestedNameOut[0]);
             }, DROP_IN_ANIMATION_DURATION);
         } else {
             addItem(item);
@@ -396,7 +399,8 @@
             item = (WorkspaceItemInfo) d.dragInfo;
         }
         mFolder.notifyDrop();
-        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(),
+        onDrop(item, d.dragView, null, 1.0f,
+                itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
                 itemReturnedOnFailedDrop);
     }
 
@@ -409,6 +413,20 @@
         return mPreviewLayoutRule;
     }
 
+    @Override
+    public void setForceHideDot(boolean forceHideDot) {
+        if (mForceHideDot == forceHideDot) {
+            return;
+        }
+        mForceHideDot = forceHideDot;
+
+        if (forceHideDot) {
+            invalidate();
+        } else if (hasDot()) {
+            animateDotScale(0, 1);
+        }
+    }
+
     /**
      * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
      * (the dot is being added or removed).
@@ -468,7 +486,8 @@
         mBackground.setInvalidateDelegate(this);
     }
 
-    public void setBackgroundVisible(boolean visible) {
+    @Override
+    public void setIconVisible(boolean visible) {
         mBackgroundIsVisible = visible;
         invalidate();
     }
@@ -493,8 +512,7 @@
             mBackground.drawBackground(canvas);
         }
 
-        if (mFolder == null) return;
-        if (mFolder.getItemCount() == 0 && !mAnimating) return;
+        if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
 
         final int saveCount = canvas.save();
         canvas.clipPath(mBackground.getClipPath());
@@ -509,7 +527,7 @@
     }
 
     public void drawDot(Canvas canvas) {
-        if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) {
+        if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
             Rect iconBounds = mDotParams.iconBounds;
             BubbleTextView.getIconBounds(this, iconBounds,
                     mLauncher.getWallpaperDeviceProfile().iconSizePx);
@@ -536,31 +554,10 @@
     }
 
     /**
-     * Returns the list of preview items displayed in the icon.
+     * Returns the list of items which should be visible in the preview
      */
-    public List<BubbleTextView> getPreviewItems() {
-        return getPreviewItemsOnPage(0);
-    }
-
-    /**
-     * Returns the list of "preview items" on {@param page}.
-     */
-    public List<BubbleTextView> getPreviewItemsOnPage(int page) {
-        mPreviewVerifier.setFolderInfo(mFolder.getInfo());
-
-        List<BubbleTextView> itemsToDisplay = new ArrayList<>();
-        List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page);
-        int numItems = itemsOnPage.size();
-        for (int rank = 0; rank < numItems; ++rank) {
-            if (mPreviewVerifier.isItemInPreview(page, rank)) {
-                itemsToDisplay.add(itemsOnPage.get(rank));
-            }
-
-            if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) {
-                break;
-            }
-        }
-        return itemsToDisplay;
+    public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
+        return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
     }
 
     @Override
@@ -578,11 +575,14 @@
     private void updatePreviewItems(boolean animate) {
         mPreviewItemManager.updatePreviewItems(animate);
         mCurrentPreviewItems.clear();
-        mCurrentPreviewItems.addAll(getPreviewItems());
+        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
@@ -605,7 +605,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/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
deleted file mode 100644
index 4c84e35..0000000
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ /dev/null
@@ -1,89 +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.folder;
-
-import android.util.Log;
-
-import com.android.launcher3.FolderInfo;
-import com.android.launcher3.InvariantDeviceProfile;
-
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
-
-/**
- * Verifies whether an item in a Folder is displayed in the FolderIcon preview.
- */
-public class FolderIconPreviewVerifier {
-
-    private static final String TAG = "FolderPreviewVerifier";
-
-    private final int mMaxGridCountX;
-    private final int mMaxGridCountY;
-    private final int mMaxItemsPerPage;
-    private final int[] mGridSize = new int[] { 1, 1 };
-
-    private int mNumItemsInFolder;
-    private int mGridCountX;
-    private boolean mDisplayingUpperLeftQuadrant = false;
-
-    /**
-     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
-     */
-    public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
-        mMaxGridCountX = profile.numFolderColumns;
-        mMaxGridCountY = profile.numFolderRows;
-        mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY;
-    }
-
-    public void setFolderInfo(FolderInfo info) {
-        int numItemsInFolder = info.contents.size();
-        if (numItemsInFolder != mNumItemsInFolder) {
-            FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
-                    mMaxGridCountY, mMaxItemsPerPage, mGridSize);
-            mGridCountX = mGridSize[0];
-
-            mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
-            mNumItemsInFolder = numItemsInFolder;
-        }
-    }
-
-    /**
-     * Returns whether the item with {@param rank} is in the default Folder icon preview.
-     */
-    public boolean isItemInPreview(int rank) {
-        return isItemInPreview(0, rank);
-    }
-
-    /**
-     * @param page The page the item is on.
-     * @param rank The rank of the item.
-     * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
-     */
-    public boolean isItemInPreview(int page, int rank) {
-        if (mGridSize[0] == 1) {
-            Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
-        }
-
-        // First page items are laid out such that the first 4 items are always in the upper
-        // left quadrant. For all other pages, we need to check the row and col.
-        if (page > 0 || mDisplayingUpperLeftQuadrant) {
-            int col = rank % mGridCountX;
-            int row = rank / mGridCountX;
-            return col < 2 && row < 2;
-        }
-        return rank < MAX_NUM_ITEMS_IN_PREVIEW;
-    }
-}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
new file mode 100644
index 0000000..0a1221e
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.folder;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.WorkspaceItemInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Locates provider for the folder name.
+ */
+public class FolderNameProvider {
+
+    /**
+     * Returns suggested folder name.
+     */
+    public CharSequence getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] suggestName) {
+        // Currently only run the algorithm on initial folder creation.
+        // For more than 2 items in the folder, the ranking algorithm for finding
+        // candidate folder name should be rewritten.
+        if (workspaceItemInfos.size() == 2) {
+            ComponentName cmp1 = workspaceItemInfos.get(0).getTargetComponent();
+            ComponentName cmp2 = workspaceItemInfos.get(1).getTargetComponent();
+
+            String pkgName0 = cmp1 == null ? "" : cmp1.getPackageName();
+            String pkgName1 = cmp2 == null ? "" : cmp2.getPackageName();
+            // If the two icons are from the same package,
+            // then assign the main icon's name
+            if (pkgName0.equals(pkgName1)) {
+                WorkspaceItemInfo wInfo0 = workspaceItemInfos.get(0);
+                WorkspaceItemInfo wInfo1 = workspaceItemInfos.get(1);
+                if (workspaceItemInfos.get(0).itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo0.title;
+                } else if (wInfo1.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                    suggestName[0] = wInfo1.title;
+                }
+                return suggestName[0];
+                // two icons are all shortcuts. Don't assign title
+            }
+        }
+        return suggestName[0];
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 57105e7..3b5fd59 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;
@@ -38,18 +38,22 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 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> {
 
@@ -68,17 +72,12 @@
 
     public final boolean mIsRtl;
 
-    private final LayoutInflater mInflater;
     private final ViewGroupFocusHelper mFocusIndicatorHelper;
 
     @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>();
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final int mMaxCountX;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final int mMaxCountY;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final int mMaxItemsPerPage;
+    private final FolderGridOrganizer mOrganizer;
+    private final ViewCache mViewCache;
 
     private int mAllocatedContentSize;
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -88,20 +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);
-        mMaxCountX = profile.numFolderColumns;
-        mMaxCountY = profile.numFolderRows;
-
-        mMaxItemsPerPage = mMaxCountX * mMaxCountY;
-
-        mInflater = LayoutInflater.from(context);
+        mOrganizer = new FolderGridOrganizer(profile);
 
         mIsRtl = Utilities.isRtl(getResources());
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
+        mViewCache = BaseActivity.fromContext(context).getViewCache();
     }
 
     public void setFolder(Folder folder) {
@@ -111,57 +110,13 @@
     }
 
     /**
-     * Calculates the grid size such that {@param count} items can fit in the grid.
-     * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while
-     * maintaining the restrictions of {@link #mMaxCountX} &amp; {@link #mMaxCountY}.
-     */
-    public static void calculateGridSize(int count, int countX, int countY, int maxCountX,
-            int maxCountY, int maxItemsPerPage, int[] out) {
-        boolean done;
-        int gridCountX = countX;
-        int gridCountY = countY;
-
-        if (count >= maxItemsPerPage) {
-            gridCountX = maxCountX;
-            gridCountY = maxCountY;
-            done = true;
-        } else {
-            done = false;
-        }
-
-        while (!done) {
-            int oldCountX = gridCountX;
-            int oldCountY = gridCountY;
-            if (gridCountX * gridCountY < count) {
-                // Current grid is too small, expand it
-                if ((gridCountX <= gridCountY || gridCountY == maxCountY)
-                        && gridCountX < maxCountX) {
-                    gridCountX++;
-                } else if (gridCountY < maxCountY) {
-                    gridCountY++;
-                }
-                if (gridCountY == 0) gridCountY++;
-            } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) {
-                gridCountY = Math.max(0, gridCountY - 1);
-            } else if ((gridCountX - 1) * gridCountY >= count) {
-                gridCountX = Math.max(0, gridCountX - 1);
-            }
-            done = gridCountX == oldCountX && gridCountY == oldCountY;
-        }
-
-        out[0] = gridCountX;
-        out[1] = gridCountY;
-    }
-
-    /**
      * Sets up the grid size such that {@param count} items can fit in the grid.
      */
-    public void setupContentDimensions(int count) {
+    private void setupContentDimensions(int count) {
         mAllocatedContentSize = count;
-        calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage,
-                sTmpArray);
-        mGridCountX = sTmpArray[0];
-        mGridCountY = sTmpArray[1];
+        mOrganizer.setContentSize(count);
+        mGridCountX = mOrganizer.getCountX();
+        mGridCountY = mOrganizer.getCountY();
 
         // Update grid size
         for (int i = getPageCount() - 1; i >= 0; i--) {
@@ -178,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, icons.size(), false);
-    }
-
-    public void allocateSpaceForRank(int rank) {
-        ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder());
-        views.add(rank, null);
-        arrangeChildren(views, views.size(), false);
+        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 / mMaxItemsPerPage);
-        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;
     }
 
@@ -215,31 +185,33 @@
      * related attributes. It assumes that {@param item} is already attached to the view.
      */
     public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
-        int pagePos = rank % mMaxItemsPerPage;
-        int pageNo = rank / mMaxItemsPerPage;
-
-        item.rank = rank;
-        item.cellX = pagePos % mGridCountX;
-        item.cellY = pagePos / mGridCountX;
+        int pageNo = rank / mOrganizer.getMaxItemsPerPage();
 
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
-        lp.cellX = item.cellX;
-        lp.cellY = item.cellY;
+        lp.setXY(mOrganizer.getPosForRank(rank));
         getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
     }
 
     @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;
     }
 
@@ -254,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);
@@ -295,37 +267,28 @@
      * page.
      *
      * @param list the ordered list of children.
-     * @param itemCount if greater than the total children count, empty spaces are left
-     * at the end, otherwise it is ignored.
-     *
      */
-    public void arrangeChildren(ArrayList<View> list, int itemCount) {
-        arrangeChildren(list, itemCount, true);
-    }
-
     @SuppressLint("RtlHardcoded")
-    private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) {
+    public void arrangeChildren(List<View> list) {
+        int itemCount = list.size();
         ArrayList<CellLayout> pages = new ArrayList<>();
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout page = (CellLayout) getChildAt(i);
             page.removeAllViews();
             pages.add(page);
         }
+        mOrganizer.setFolderInfo(mFolder.getInfo());
         setupContentDimensions(itemCount);
 
         Iterator<CellLayout> pageItr = pages.iterator();
         CellLayout currentPage = null;
 
         int position = 0;
-        int newX, newY, rank;
+        int rank = 0;
 
-        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
-                Launcher.getLauncher(getContext()).getDeviceProfile().inv);
-        verifier.setFolderInfo(mFolder.getInfo());
-        rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
-            if (currentPage == null || position >= mMaxItemsPerPage) {
+            if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) {
                 // Next page
                 if (pageItr.hasNext()) {
                     currentPage = pageItr.next();
@@ -337,28 +300,16 @@
 
             if (v != null) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
-                newX = position % mGridCountX;
-                newY = position / mGridCountX;
                 ItemInfo info = (ItemInfo) v.getTag();
-                if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
-                    info.cellX = newX;
-                    info.cellY = newY;
-                    info.rank = rank;
-                    if (saveChanges) {
-                        mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info,
-                                mFolder.mInfo.id, 0, info.cellX, info.cellY);
-                    }
-                }
-                lp.cellX = info.cellX;
-                lp.cellY = info.cellY;
+                lp.setXY(mOrganizer.getPosForRank(rank));
                 currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
 
-                if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) {
+                if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) {
                     ((BubbleTextView) v).verifyHighRes();
                 }
             }
 
-            rank ++;
+            rank++;
             position++;
         }
 
@@ -391,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 * mMaxItemsPerPage;
-    }
-
     /**
      * @return the rank of the cell nearest to the provided pixel position.
      */
@@ -412,31 +353,28 @@
             sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1;
         }
         return Math.min(mAllocatedContentSize - 1,
-                pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]);
+                pageIndex * mOrganizer.getMaxItemsPerPage()
+                        + sTmpArray[1] * mGridCountX + sTmpArray[0]);
     }
 
     public View getFirstItem() {
-        if (getChildCount() < 1) {
-            return null;
-        }
-        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
-        if (mGridCountX > 0) {
-            return currContainer.getChildAt(0, 0);
-        } else {
-            return currContainer.getChildAt(0);
-        }
+        return getViewInCurrentPage(c -> 0);
     }
 
     public View getLastItem() {
+        return getViewInCurrentPage(c -> c.getChildCount() - 1);
+    }
+
+    private View getViewInCurrentPage(ToIntFunction<ShortcutAndWidgetContainer> rankProvider) {
         if (getChildCount() < 1) {
             return null;
         }
-        ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets();
-        int lastRank = currContainer.getChildCount() - 1;
+        ShortcutAndWidgetContainer container = getCurrentCellLayout().getShortcutsAndWidgets();
+        int rank = rankProvider.applyAsInt(container);
         if (mGridCountX > 0) {
-            return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX);
+            return container.getChildAt(rank % mGridCountX, rank / mGridCountX);
         } else {
-            return currContainer.getChildAt(lastRank);
+            return container.getChildAt(rank);
         }
     }
 
@@ -517,7 +455,7 @@
     }
 
     public boolean rankOnCurrentPage(int rank) {
-        int p = rank / mMaxItemsPerPage;
+        int p = rank / mOrganizer.getMaxItemsPerPage();
         return p == getNextPage();
     }
 
@@ -563,15 +501,16 @@
 
         // Animation only happens on the current page.
         int pageToAnimate = getNextPage();
+        int maxItemsPerPage = mOrganizer.getMaxItemsPerPage();
 
-        int pageT = target / mMaxItemsPerPage;
-        int pagePosT = target % mMaxItemsPerPage;
+        int pageT = target / maxItemsPerPage;
+        int pagePosT = target % maxItemsPerPage;
 
         if (pageT != pageToAnimate) {
             Log.e(TAG, "Cannot animate when the target cell is invisible");
         }
-        int pagePosE = empty % mMaxItemsPerPage;
-        int pageE = empty / mMaxItemsPerPage;
+        int pagePosE = empty % maxItemsPerPage;
+        int pageE = empty / maxItemsPerPage;
 
         int startPos, endPos;
         int moveStart, moveEnd;
@@ -588,7 +527,7 @@
             if (pageE < pageToAnimate) {
                 moveStart = empty;
                 // Instantly move the first item in the current page.
-                moveEnd = pageToAnimate * mMaxItemsPerPage;
+                moveEnd = pageToAnimate * maxItemsPerPage;
                 // Animate the 2nd item in the current page, as the first item was already moved to
                 // the last page.
                 startPos = 0;
@@ -606,10 +545,10 @@
                 // Move the items immediately.
                 moveStart = empty;
                 // Instantly move the last item in the current page.
-                moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1;
+                moveEnd = (pageToAnimate + 1) * maxItemsPerPage - 1;
 
                 // Animations start with the second last item in the page
-                startPos = mMaxItemsPerPage - 1;
+                startPos = maxItemsPerPage - 1;
             } else {
                 moveStart = moveEnd = -1;
                 startPos = pagePosE;
@@ -621,8 +560,8 @@
         // Instant moving views.
         while (moveStart != moveEnd) {
             int rankToMove = moveStart + direction;
-            int p = rankToMove / mMaxItemsPerPage;
-            int pagePos = rankToMove % mMaxItemsPerPage;
+            int p = rankToMove / maxItemsPerPage;
+            int pagePos = rankToMove % maxItemsPerPage;
             int x = pagePos % mGridCountX;
             int y = pagePos / mGridCountX;
 
@@ -667,9 +606,6 @@
         for (int i = startPos; i != endPos; i += direction) {
             int nextPos = i + direction;
             View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX);
-            if (v != null) {
-                ((ItemInfo) v.getTag()).rank -= direction;
-            }
             if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
                     REORDER_ANIMATION_DURATION, delay, true, true)) {
                 delay += delayAmount;
@@ -679,6 +615,6 @@
     }
 
     public int itemsPerPage() {
-        return mMaxItemsPerPage;
+        return mOrganizer.getMaxItemsPerPage();
     }
 }
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 49763ba..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 com.android.launcher3.BubbleTextView;
-import com.android.launcher3.WorkspaceItemInfo;
+import androidx.annotation.NonNull;
+
+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 androidx.annotation.NonNull;
+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.getPreviewItemsOnPage(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 7eb4015..f579451 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
@@ -25,16 +27,15 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BitmapRenderer;
-import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
 
@@ -87,6 +88,9 @@
             Rect bounds = getDrawableBounds(d);
             destCanvas.translate(blurSizeOutline / 2 - bounds.left,
                     blurSizeOutline / 2 - bounds.top);
+            if (d instanceof FastBitmapDrawable) {
+                ((FastBitmapDrawable) d).setScale(1);
+            }
             d.draw(destCanvas);
         } else {
             final Rect clipRect = mTempRect;
@@ -153,7 +157,7 @@
         }
 
         mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview);
-        new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback);
+        UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback);
     }
 
     protected static Rect getDrawableBounds(Drawable d) {
@@ -191,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 c9566cb..837301f 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.graphics;
 
 import static com.android.launcher3.graphics.IconShape.getShapePath;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -31,6 +32,8 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.R;
@@ -38,16 +41,13 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import androidx.annotation.UiThread;
-
 /**
  * Factory for creating new drawables.
  */
 public class DrawableFactory implements ResourceBasedOverride {
 
     public static final MainThreadInitializedObject<DrawableFactory> INSTANCE =
-            new MainThreadInitializedObject<>(c -> Overrides.getObject(DrawableFactory.class,
-                        c.getApplicationContext(), R.string.drawable_factory_class));
+            forOverride(DrawableFactory.class, R.string.drawable_factory_class);
 
     protected final UserHandle mMyUser = Process.myUserHandle();
     protected final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>();
@@ -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/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index efd39ee..71b4366 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.res.XmlResourceParser;
@@ -17,8 +19,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.UiThreadHelper;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -180,10 +180,10 @@
             throw new FileNotFoundException(e.getMessage());
         }
 
-        LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper());
         try {
             return openPipeHelper(uri, MIME_TYPE_PNG, null,
-                    executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER);
+                    UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)),
+                    BITMAP_WRITER);
         } catch (Exception e) {
             throw new FileNotFoundException(e.getMessage());
         }
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
new file mode 100644
index 0000000..d707403
--- /dev/null
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -0,0 +1,70 @@
+/*
+ * 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.graphics;
+
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * View scrim which draws behind overview (recent apps).
+ */
+public class OverviewScrim extends Scrim {
+
+    private @NonNull View mStableScrimmedView;
+    // Might be higher up if mStableScrimmedView is invisible.
+    private @Nullable View mCurrentScrimmedView;
+
+    public OverviewScrim(View view) {
+        super(view);
+        mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
+
+        onExtractedColorsChanged(mWallpaperColorInfo);
+    }
+
+    public void onInsetsChanged(Rect insets) {
+        mStableScrimmedView = (OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0
+                ? mLauncher.getHotseat()
+                : mLauncher.getOverviewPanel();
+    }
+
+    public void updateCurrentScrimmedView(ViewGroup root) {
+        // Find the lowest view that is at or above the view we want to show the scrim behind.
+        mCurrentScrimmedView = mStableScrimmedView;
+        int currentIndex = root.indexOfChild(mCurrentScrimmedView);
+        final int childCount = root.getChildCount();
+        while (mCurrentScrimmedView.getVisibility() != VISIBLE && currentIndex < childCount) {
+            currentIndex++;
+            mCurrentScrimmedView = root.getChildAt(currentIndex);
+        }
+    }
+
+    /**
+     * @return The view to draw the scrim behind.
+     */
+    public View getScrimmedView() {
+        return mCurrentScrimmedView;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
new file mode 100644
index 0000000..5c14f8d
--- /dev/null
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -0,0 +1,97 @@
+/*
+ * 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.graphics;
+
+import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+
+import android.graphics.Canvas;
+import android.util.Property;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.WallpaperColorInfo;
+
+/**
+ * Contains general scrim properties such as wallpaper-extracted color that subclasses can use.
+ */
+public class Scrim implements View.OnAttachStateChangeListener,
+        WallpaperColorInfo.OnChangeListener {
+
+    public static Property<Scrim, Float> SCRIM_PROGRESS =
+            new Property<Scrim, Float>(Float.TYPE, "scrimProgress") {
+                @Override
+                public Float get(Scrim scrim) {
+                    return scrim.mScrimProgress;
+                }
+
+                @Override
+                public void set(Scrim scrim, Float value) {
+                    scrim.setScrimProgress(value);
+                }
+            };
+
+    protected final Launcher mLauncher;
+    protected final WallpaperColorInfo mWallpaperColorInfo;
+    protected final View mRoot;
+
+    protected float mScrimProgress;
+    protected int mScrimColor;
+    protected int mScrimAlpha = 0;
+
+    public Scrim(View view) {
+        mRoot = view;
+        mLauncher = Launcher.getLauncher(view.getContext());
+        mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+
+        view.addOnAttachStateChangeListener(this);
+    }
+
+    public void draw(Canvas canvas) {
+        canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
+    }
+
+    private void setScrimProgress(float progress) {
+        if (mScrimProgress != progress) {
+            mScrimProgress = progress;
+            mScrimAlpha = Math.round(255 * mScrimProgress);
+            invalidate();
+        }
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View view) {
+        mWallpaperColorInfo.addOnChangeListener(this);
+        onExtractedColorsChanged(mWallpaperColorInfo);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View view) {
+        mWallpaperColorInfo.removeOnChangeListener(this);
+    }
+
+    @Override
+    public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
+        mScrimColor = wallpaperColorInfo.getMainColor();
+        if (mScrimAlpha > 0) {
+            invalidate();
+        }
+    }
+
+    public void invalidate() {
+        mRoot.invalidate();
+    }
+}
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index c0aa75f..15ff207 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -40,34 +40,19 @@
 import android.util.Property;
 import android.view.View;
 
+import androidx.core.graphics.ColorUtils;
+
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.Themes;
 
-import androidx.core.graphics.ColorUtils;
-
 /**
  * View scrim which draws behind hotseat and workspace
  */
-public class WorkspaceAndHotseatScrim implements
-        View.OnAttachStateChangeListener, WallpaperColorInfo.OnChangeListener {
-
-    public static Property<WorkspaceAndHotseatScrim, Float> SCRIM_PROGRESS =
-            new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "scrimProgress") {
-                @Override
-                public Float get(WorkspaceAndHotseatScrim scrim) {
-                    return scrim.mScrimProgress;
-                }
-
-                @Override
-                public void set(WorkspaceAndHotseatScrim scrim, Float value) {
-                    scrim.setScrimProgress(value);
-                }
-            };
+public class WorkspaceAndHotseatScrim extends Scrim {
 
     public static Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS =
             new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") {
@@ -117,9 +102,6 @@
     private static final int ALPHA_MASK_WIDTH_DP = 2;
 
     private final Rect mHighlightRect = new Rect();
-    private final Launcher mLauncher;
-    private final WallpaperColorInfo mWallpaperColorInfo;
-    private final View mRoot;
 
     private Workspace mWorkspace;
 
@@ -132,11 +114,6 @@
 
     private final Drawable mTopScrim;
 
-    private int mFullScrimColor;
-
-    private float mScrimProgress;
-    private int mScrimAlpha = 0;
-
     private float mSysUiProgress = 1;
     private boolean mHideSysUiScrim;
 
@@ -144,9 +121,7 @@
     private float mSysUiAnimMultiplier = 1;
 
     public WorkspaceAndHotseatScrim(View view) {
-        mRoot = view;
-        mLauncher = Launcher.getLauncher(view.getContext());
-        mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher);
+        super(view);
 
         mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
@@ -154,7 +129,6 @@
         mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
         mHideSysUiScrim = mTopScrim == null;
 
-        view.addOnAttachStateChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
     }
 
@@ -176,7 +150,7 @@
                 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
             }
 
-            canvas.drawColor(setColorAlphaBound(mFullScrimColor, mScrimAlpha));
+            super.draw(canvas);
             canvas.restore();
         }
 
@@ -190,11 +164,8 @@
                 mSysUiAnimMultiplier = 0;
                 reapplySysUiAlphaNoInvalidate();
 
-                ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, 1);
-                anim.setAutoCancel(true);
-                anim.setDuration(600);
-                anim.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
-                anim.start();
+                animateToSysuiMultiplier(1, 600,
+                        mLauncher.getWindow().getTransitionBackgroundFadeDuration());
                 mAnimateScrimOnNextDraw = false;
             }
 
@@ -207,24 +178,27 @@
         }
     }
 
-    public void onInsetsChanged(Rect insets) {
-        mDrawTopScrim = mTopScrim != null && insets.top > 0;
-        mDrawBottomScrim = mBottomMask != null &&
-                !mLauncher.getDeviceProfile().isVerticalBarLayout();
+    public void animateToSysuiMultiplier(float toMultiplier, long duration,
+            long startDelay) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier);
+        anim.setAutoCancel(true);
+        anim.setDuration(duration);
+        anim.setStartDelay(startDelay);
+        anim.start();
     }
 
-    private void setScrimProgress(float progress) {
-        if (mScrimProgress != progress) {
-            mScrimProgress = progress;
-            mScrimAlpha = Math.round(255 * mScrimProgress);
-            invalidate();
-        }
+    /**
+     * Determines whether to draw the top and/or bottom scrim based on new insets.
+     */
+    public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
+        mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0;
+        mDrawBottomScrim = allowSysuiScrims && mBottomMask != null
+                && !mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
     @Override
     public void onViewAttachedToWindow(View view) {
-        mWallpaperColorInfo.addOnChangeListener(this);
-        onExtractedColorsChanged(mWallpaperColorInfo);
+        super.onViewAttachedToWindow(view);
 
         if (mTopScrim != null) {
             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
@@ -235,7 +209,7 @@
 
     @Override
     public void onViewDetachedFromWindow(View view) {
-        mWallpaperColorInfo.removeOnChangeListener(this);
+        super.onViewDetachedFromWindow(view);
         if (mTopScrim != null) {
             mRoot.getContext().unregisterReceiver(mReceiver);
         }
@@ -248,10 +222,7 @@
         mBottomMaskPaint.setColor(ColorUtils.compositeColors(DARK_SCRIM_COLOR,
                 wallpaperColorInfo.getMainColor()));
         reapplySysUiAlpha();
-        mFullScrimColor = wallpaperColorInfo.getMainColor();
-        if (mScrimAlpha > 0) {
-            invalidate();
-        }
+        super.onExtractedColorsChanged(wallpaperColorInfo);
     }
 
     public void setSize(int w, int h) {
@@ -291,10 +262,6 @@
         }
     }
 
-    public void invalidate() {
-        mRoot.invalidate();
-    }
-
     public Bitmap createDitheredAlphaMask() {
         DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
         int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
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 648445e..9c46260 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.icons;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -29,17 +32,18 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.IconProvider;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.WorkspaceItemInfo;
 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;
@@ -50,8 +54,6 @@
 
 import java.util.function.Supplier;
 
-import androidx.annotation.NonNull;
-
 /**
  * Cache of application icons.  Icons can be made from any thread.
  */
@@ -59,8 +61,6 @@
 
     private static final String TAG = "Launcher.IconCache";
 
-    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
 
@@ -72,14 +72,14 @@
     private int mPendingIconRequestCount = 0;
 
     public IconCache(Context context, InvariantDeviceProfile inv) {
-        super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(),
+        super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
-        mComponentWithLabelCachingLogic = new ComponentCachingLogic(context);
-        mLauncherActivityInfoCachingLogic = new LauncherActivtiyCachingLogic(this);
+        mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
+        mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
-        mIconProvider = IconProvider.newInstance(context);
+        mIconProvider = IconProvider.INSTANCE.get(context);
     }
 
     @Override
@@ -123,7 +123,7 @@
             final ItemInfoWithIcon info) {
         Preconditions.assertUIThread();
         if (mPendingIconRequestCount <= 0) {
-            LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND);
+            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
         }
         mPendingIconRequestCount ++;
 
@@ -135,7 +135,7 @@
                 } else if (info instanceof PackageItemInfo) {
                     getTitleAndIconForApp((PackageItemInfo) info, false);
                 }
-                mMainThreadExecutor.execute(() -> {
+                MAIN_EXECUTOR.execute(() -> {
                     caller.reapplyItemInfo(info);
                     onEnd();
                 });
@@ -148,7 +148,7 @@
     private void onIconRequestEnd() {
         mPendingIconRequestCount --;
         if (mPendingIconRequestCount <= 0) {
-            LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
     }
 
@@ -194,7 +194,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);
     }
 
@@ -237,7 +237,8 @@
 
     @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/LauncherActivtiyCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
similarity index 67%
rename from src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java
rename to src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
index 7c99633..f9a94da 100644
--- a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java
+++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
@@ -20,14 +20,23 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.UserHandle;
 
+import com.android.launcher3.IconProvider;
+import com.android.launcher3.R;
 import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.util.ResourceBasedOverride;
 
-public class LauncherActivtiyCachingLogic implements CachingLogic<LauncherActivityInfo> {
+/**
+ * Caching logic for LauncherActivityInfo.
+ */
+public class LauncherActivityCachingLogic
+        implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride {
 
-    private final IconCache mCache;
-
-    public LauncherActivtiyCachingLogic(IconCache cache) {
-        mCache = cache;
+    /**
+     * Creates and returns a new instance
+     */
+    public static LauncherActivityCachingLogic newInstance(Context context) {
+        return Overrides.getObject(LauncherActivityCachingLogic.class, context,
+                R.string.launcher_activity_logic_class);
     }
 
     @Override
@@ -49,8 +58,10 @@
     public void loadIcon(Context context, LauncherActivityInfo object,
             BitmapInfo target) {
         LauncherIcons li = LauncherIcons.obtain(context);
-        li.createBadgedIconBitmap(mCache.getFullResIcon(object),
+        li.createBadgedIconBitmap(
+                IconProvider.INSTANCE.get(context)
+                        .getIcon(object, li.mFillResIconDpi, true /* flattenDrawable */),
                 object.getUser(), object.getApplicationInfo().targetSdkVersion).applyTo(target);
         li.recycle();
     }
-}
\ No newline at end of file
+}
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/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java
index 365e8f2..067bdfd 100644
--- a/src/com/android/launcher3/logging/DumpTargetWrapper.java
+++ b/src/com/android/launcher3/logging/DumpTargetWrapper.java
@@ -15,17 +15,22 @@
  */
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+
+import android.content.ComponentName;
 import android.os.Process;
 import android.text.TextUtils;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.model.nano.LauncherDumpProto;
 import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType;
 import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget;
 import com.android.launcher3.model.nano.LauncherDumpProto.ItemType;
 import com.android.launcher3.model.nano.LauncherDumpProto.UserType;
+import com.android.launcher3.util.ShortcutUtil;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,20 +78,23 @@
     public DumpTarget newItemTarget(ItemInfo info) {
         DumpTarget dt = new DumpTarget();
         dt.type = DumpTarget.Type.ITEM;
-
+        if (info == null) {
+            return dt;
+        }
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 dt.itemType = ItemType.APP_ICON;
                 break;
-            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
-                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
-                break;
             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                 dt.itemType = ItemType.WIDGET;
                 break;
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+            case ITEM_TYPE_DEEP_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 dt.itemType = ItemType.SHORTCUT;
                 break;
+            default:
+                dt.itemType = ItemType.UNKNOWN_ITEMTYPE;
+                break;
         }
         return dt;
     }
@@ -120,6 +128,9 @@
     }
 
     private static String getItemStr(DumpTarget t) {
+        if (t == null) {
+            return "";
+        }
         String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class);
         if (!TextUtils.isEmpty(t.packageName)) {
             typeStr += ", package=" + t.packageName;
@@ -132,8 +143,15 @@
     }
 
     public DumpTarget writeToDumpTarget(ItemInfo info) {
-        node.component = info.getTargetComponent() == null? "":
-                info.getTargetComponent().flattenToString();
+        if (info == null) {
+            return node;
+        }
+        if (ShortcutUtil.isDeepShortcut(info)) {
+            node.component = ((WorkspaceItemInfo) info).getDeepShortcutId();
+        } else {
+            ComponentName cmp = info.getTargetComponent();
+            node.component = cmp == null ? "" : cmp.flattenToString();
+        }
         node.packageName = info.getTargetComponent() == null? "":
                 info.getTargetComponent().getPackageName();
         if (info instanceof LauncherAppWidgetInfo) {
diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java
index f20f365..3ecfb23 100644
--- a/src/com/android/launcher3/logging/EventLogArray.java
+++ b/src/com/android/launcher3/logging/EventLogArray.java
@@ -16,11 +16,13 @@
 package com.android.launcher3.logging;
 
 
+import android.util.Log;
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Locale;
+import java.util.Random;
 
 /**
  * A utility class to record and log events. Events are stored in a fixed size array and old logs
@@ -37,6 +39,7 @@
     private final String name;
     private final EventEntry[] logs;
     private int nextIndex;
+    private int mLogId;
 
     public EventLogArray(String name, int size) {
         this.name = name;
@@ -52,10 +55,6 @@
         addLog(TYPE_INTEGER, event, extras);
     }
 
-    public void addLog(String event, float extras) {
-        addLog(TYPE_FLOAT, event, extras);
-    }
-
     public void addLog(String event, boolean extras) {
         addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0);
     }
@@ -65,7 +64,7 @@
         int last = (nextIndex + logs.length - 1) % logs.length;
         int secondLast = (nextIndex + logs.length - 2) % logs.length;
         if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) {
-            logs[last].update(type, event, extras);
+            logs[last].update(type, event, extras, mLogId);
             logs[secondLast].duplicateCount++;
             return;
         }
@@ -73,7 +72,7 @@
         if (logs[nextIndex] == null) {
             logs[nextIndex] = new EventEntry();
         }
-        logs[nextIndex].update(type, event, extras);
+        logs[nextIndex].update(type, event, extras, mLogId);
         nextIndex = (nextIndex + 1) % logs.length;
     }
 
@@ -113,10 +112,18 @@
             if (log.duplicateCount > 0) {
                 msg.append(" & ").append(log.duplicateCount).append(" similar events");
             }
+            msg.append(" traceId: ").append(log.traceId);
             writer.println(msg);
         }
     }
 
+    /** Returns a 3 digit random number between 100-999 */
+    public int generateAndSetLogId() {
+        Random r = new Random();
+        mLogId = r.nextInt(900) + 100;
+        return mLogId;
+    }
+
     private boolean isEntrySame(EventEntry entry, int type, String event) {
         return entry != null && entry.type == type && entry.event.equals(event);
     }
@@ -129,11 +136,13 @@
         private float extras;
         private long time;
         private int duplicateCount;
+        private int traceId;
 
-        public void update(int type, String event, float extras) {
+        public void update(int type, String event, float extras, int traceId) {
             this.type = type;
             this.event = event;
             this.extras = extras;
+            this.traceId = traceId;
             time = System.currentTimeMillis();
             duplicateCount = 0;
         }
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index f7f8ef1..923a89b 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -1,13 +1,14 @@
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.IOUtils;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -29,8 +30,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,9 +91,8 @@
     private static Handler getHandler() {
         synchronized (DATE_FORMAT) {
             if (sHandler == null) {
-                HandlerThread thread = new HandlerThread("file-logger");
-                thread.start();
-                sHandler = new Handler(thread.getLooper(), new LogWriterCallback());
+                sHandler = new Handler(createAndStartNewLooper("file-logger"),
+                        new LogWriterCallback());
             }
         }
         return sHandler;
@@ -131,7 +130,7 @@
         private PrintWriter mCurrentWriter = null;
 
         private void closeWriter() {
-            Utilities.closeSilently(mCurrentWriter);
+            IOUtils.closeSilently(mCurrentWriter);
             mCurrentWriter = null;
         }
 
@@ -219,7 +218,7 @@
             } catch (Exception e) {
                 // ignore
             } finally {
-                Utilities.closeSilently(in);
+                IOUtils.closeSilently(in);
             }
         }
     }
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index 9b75b43..925b7ba 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,6 +44,7 @@
 public class LoggerUtils {
     private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
     private static final String UNKNOWN = "UNKNOWN";
+    private static final int DEFAULT_PREDICTED_RANK = -100;
 
     public static String getFieldName(int value, Class c) {
         SparseArray<String> cache;
@@ -90,7 +91,7 @@
     }
 
     public static String getTargetStr(Target t) {
-        if (t == null){
+        if (t == null) {
             return "";
         }
         String str = "";
@@ -108,7 +109,7 @@
                         t.containerType == NAVBAR) {
                     str += " id=" + t.pageIndex;
                 } else if (t.containerType == ContainerType.FOLDER) {
-                    str += " grid(" + t.gridX + "," + t.gridY + ")";
+                    str += "[PageIndex=" + t.pageIndex + ", grid(" + t.gridX + "," + t.gridY + ")]";
                 }
                 break;
             default:
@@ -137,17 +138,16 @@
         if (t.intentHash != 0) {
             typeStr += ", intentHash=" + t.intentHash;
         }
-        if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0) &&
-                t.itemType != ItemType.TASK) {
+        if (t.itemType == ItemType.FOLDER_ICON) {
+            typeStr += ", grid(" + t.gridX + "," + t.gridY + ")";
+        } else if ((t.packageNameHash != 0 || t.componentHash != 0 || t.intentHash != 0)
+                && t.itemType != ItemType.TASK) {
             typeStr += ", predictiveRank=" + t.predictedRank + ", grid(" + t.gridX + "," + t.gridY
-                    + "), span(" + t.spanX + "," + t.spanY
-                    + "), pageIdx=" + t.pageIndex;
-
+                    + "), span(" + t.spanX + "," + t.spanY + "), pageIdx=" + t.pageIndex;
         }
         if (t.searchQueryLength != 0) {
             typeStr += ", searchQueryLength=" + t.searchQueryLength;
         }
-
         if (t.itemType == ItemType.TASK) {
             typeStr += ", pageIdx=" + t.pageIndex;
         }
@@ -168,17 +168,17 @@
 
     public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
         Target t = newTarget(Target.Type.ITEM);
-
         switch (info.itemType) {
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                 t.itemType = (instantAppResolver != null && info instanceof AppInfo
-                        && instantAppResolver.isInstantApp(((AppInfo) info)) )
+                        && instantAppResolver.isInstantApp(((AppInfo) info)))
                         ? ItemType.WEB_APP
                         : ItemType.APP_ICON;
-                t.predictedRank = -100; // Never assigned
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 t.itemType = ItemType.SHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                 t.itemType = ItemType.FOLDER_ICON;
@@ -188,6 +188,7 @@
                 break;
             case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                 t.itemType = ItemType.DEEPSHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
                 break;
         }
         return t;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9b9543e..cad95b0 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -40,5 +40,7 @@
 
     public void logAppLaunch(View v, Intent intent) { }
     public void logTaskLaunch(View v, ComponentKey key) { }
+    public void logTaskDismiss(View v, ComponentKey key) { }
+    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
     public void verify() {}     // TODO: should move into robo tests
 }
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
index 647f255..b02a050 100644
--- a/src/com/android/launcher3/logging/StatsLogUtils.java
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -1,9 +1,12 @@
 package com.android.launcher3.logging;
 
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.DEFAULT_CONTAINERTYPE;
+
 import android.view.View;
 import android.view.ViewParent;
 
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 import androidx.annotation.Nullable;
@@ -64,4 +67,20 @@
         }
         return null;
     }
+
+    public static int getContainerTypeFromState(int state) {
+        int containerType = DEFAULT_CONTAINERTYPE;
+        switch (state) {
+            case StatsLogUtils.LAUNCHER_STATE_ALLAPPS:
+                containerType = ContainerType.ALLAPPS;
+                break;
+            case StatsLogUtils.LAUNCHER_STATE_HOME:
+                containerType = ContainerType.WORKSPACE;
+                break;
+            case StatsLogUtils.LAUNCHER_STATE_OVERVIEW:
+                containerType = ContainerType.OVERVIEW;
+                break;
+        }
+        return containerType;
+    }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index bd785a1..99906fe 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -26,6 +26,8 @@
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
 
+import static java.util.Optional.ofNullable;
+
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -35,6 +37,7 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DropTarget;
@@ -58,7 +61,7 @@
 /**
  * Manages the creation of {@link LauncherEvent}.
  * To debug this class, execute following command before side loading a new apk.
- *
+ * <p>
  * $ adb shell setprop log.tag.UserEvent VERBOSE
  */
 public class UserEventDispatcher implements ResourceBasedOverride {
@@ -94,19 +97,26 @@
 
     /**
      * Fills in the container data on the given event if the given view is not null.
+     *
      * @return whether container data was added.
      */
-    public static boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
+    public boolean fillInLogContainerData(LauncherLogProto.LauncherEvent event, @Nullable View v) {
         // Fill in grid(x,y), pageIndex of the child and container type of the parent
         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
             return false;
         }
-        ItemInfo itemInfo = (ItemInfo) v.getTag();
-        provider.fillInLogContainerData(v, itemInfo, event.srcTarget[0], event.srcTarget[1]);
+        final ItemInfo itemInfo = (ItemInfo) v.getTag();
+        final Target target = event.srcTarget[0];
+        final Target targetParent = event.srcTarget[1];
+        onFillInLogContainerData(itemInfo, target, targetParent);
+        provider.fillInLogContainerData(v, itemInfo, target, targetParent);
         return true;
     }
 
+    protected void onFillInLogContainerData(
+            @NonNull ItemInfo itemInfo, @NonNull Target target, @NonNull Target targetParent) { }
+
     private boolean mSessionStarted;
     private long mElapsedContainerMillis;
     private long mElapsedSessionMillis;
@@ -115,6 +125,7 @@
     protected InstantAppResolver mInstantAppResolver;
     private boolean mAppOrTaskLaunch;
     private UserEventDelegate mDelegate;
+    private boolean mPreviousHomeGesture;
 
     //                      APP_ICON    SHORTCUT    WIDGET
     // --------------------------------------------------------------
@@ -138,7 +149,11 @@
         mAppOrTaskLaunch = true;
     }
 
-    public void logActionTip(int actionType, int viewType) { }
+    /**
+     * Dummy method.
+     */
+    public void logActionTip(int actionType, int viewType) {
+    }
 
     @Deprecated
     public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
@@ -183,7 +198,15 @@
 
     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
         logActionCommand(command, newContainerTarget(srcContainerType),
-                dstContainerType >=0 ? newContainerTarget(dstContainerType) : null);
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
+    }
+
+    public void logActionCommand(int command, int srcContainerType, int dstContainerType,
+            int pageIndex) {
+        Target srcTarget = newContainerTarget(srcContainerType);
+        srcTarget.pageIndex = pageIndex;
+        logActionCommand(command, srcTarget,
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
     }
 
     public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
@@ -232,7 +255,7 @@
     }
 
     public void logActionOnControl(int action, int controlType, int parentContainer,
-                                   int grandParentContainer){
+            int grandParentContainer) {
         LauncherEvent event = newLauncherEvent(newTouchAction(action),
                 newControlTarget(controlType),
                 newContainerTarget(parentContainer),
@@ -241,11 +264,11 @@
     }
 
     public void logActionOnControl(int action, int controlType, @Nullable View controlInContainer,
-                                   int parentContainerType) {
+            int parentContainerType) {
         final LauncherEvent event = (controlInContainer == null && parentContainerType < 0)
                 ? newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL))
                 : newLauncherEvent(newTouchAction(action), newTarget(Target.Type.CONTROL),
-                        newTarget(Target.Type.CONTAINER));
+                newTarget(Target.Type.CONTAINER));
         event.srcTarget[0].controlType = controlType;
         if (controlInContainer != null) {
             fillInLogContainerData(event, controlInContainer);
@@ -292,9 +315,9 @@
      * (1) WORKSPACE: if the launcher is the foreground activity
      * (2) APP: if another app was the foreground activity
      */
-    public void logStateChangeAction(int action, int dir, int downX, int downY, int srcChildTargetType,
-                                     int srcParentContainerType, int dstContainerType,
-                                     int pageIndex) {
+    public void logStateChangeAction(int action, int dir, int downX, int downY,
+            int srcChildTargetType, int srcParentContainerType, int dstContainerType,
+            int pageIndex) {
         LauncherEvent event;
         if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
             event = newLauncherEvent(newTouchAction(action),
@@ -317,9 +340,25 @@
     }
 
     public void logActionOnItem(int action, int dir, int itemType) {
+        logActionOnItem(action, dir, itemType, null, null);
+    }
+
+    /**
+     * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it.
+     *
+     * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action
+     * @param dir         ENUM value of {@link LauncherLogProto.Action.Direction} Action
+     * @param itemType    ENUM value of {@link LauncherLogProto.ItemType}
+     * @param gridX       Nullable X coordinate of item's position on the workspace grid
+     * @param gridY       Nullable Y coordinate of item's position on the workspace grid
+     */
+    public void logActionOnItem(int touchAction, int dir, int itemType,
+            @Nullable Integer gridX, @Nullable Integer gridY) {
         Target itemTarget = newTarget(Target.Type.ITEM);
         itemTarget.itemType = itemType;
-        LauncherEvent event = newLauncherEvent(newTouchAction(action), itemTarget);
+        ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value);
+        ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value);
+        LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget);
         event.action.dir = dir;
         dispatchUserEvent(event, null);
     }
@@ -342,7 +381,7 @@
         LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP),
                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
                 newTarget(Target.Type.CONTAINER));
-        event.destTarget = new Target[] {
+        event.destTarget = new Target[]{
                 newItemTarget(dragObj.originalDragInfo, mInstantAppResolver),
                 newDropTarget(dropTargetAsView)
         };
@@ -364,14 +403,10 @@
         int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
         Action action = newCommandAction(actionTouch);
         action.command = Action.Command.BACK;
-        action.dir = isButton
-                ? Action.Direction.NONE
-                : gestureSwipeLeft
-                        ? Action.Direction.LEFT
-                        : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton
-                ? LauncherLogProto.ControlType.BACK_BUTTON
-                : LauncherLogProto.ControlType.BACK_GESTURE);
+        action.dir = isButton ? Action.Direction.NONE :
+                gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
+        Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
+                LauncherLogProto.ControlType.BACK_GESTURE);
         target.spanX = downX;
         target.spanY = downY;
         target.cardinality = completed ? 1 : 0;
@@ -382,6 +417,7 @@
 
     /**
      * Currently logs following containers: workspace, allapps, widget tray.
+     *
      * @param reason
      */
     public final void resetElapsedContainerMillis(String reason) {
@@ -399,18 +435,35 @@
         mElapsedContainerMillis = SystemClock.uptimeMillis();
     }
 
+    public final void setPreviousHomeGesture(boolean homeGesture) {
+        mPreviousHomeGesture = homeGesture;
+    }
+
+    public final boolean isPreviousHomeGesture() {
+        return mPreviousHomeGesture;
+    }
+
     public final void resetActionDurationMillis() {
         mActionDurationMillis = SystemClock.uptimeMillis();
     }
 
     public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
         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) {
@@ -425,7 +478,7 @@
                 ev.elapsedSessionMillis,
                 ev.actionDurationMillis);
         log += "\n\n";
-        Log.d(TAG, log);
+        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 ed0d470..b8c583c 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -16,11 +16,12 @@
 package com.android.launcher3.model;
 
 import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.UserHandle;
 import android.util.LongSparseArray;
 import android.util.Pair;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -28,12 +29,14 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -73,6 +76,11 @@
                     if (shortcutExists(dataModel, item.getIntent(), item.user)) {
                         continue;
                     }
+
+                    // b/139663018 Short-circuit this logic if the icon is a system app
+                    if (PackageManagerHelper.isSystemApp(app.getContext(), item.getIntent())) {
+                        continue;
+                    }
                 }
 
                 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
@@ -85,6 +93,10 @@
                 }
             }
 
+            PackageInstallerCompat packageInstaller =
+                    PackageInstallerCompat.getInstance(app.getContext());
+            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(app.getContext());
+
             for (ItemInfo item : filteredItems) {
                 // Find appropriate space for the item.
                 int[] coords = findSpaceForItem(app, dataModel, workspaceScreens,
@@ -101,6 +113,50 @@
                     throw new RuntimeException("Unexpected info type");
                 }
 
+                if (item instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) item).isPromise()) {
+                    WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) item;
+                    String packageName = item.getTargetComponent() != null
+                            ? item.getTargetComponent().getPackageName() : null;
+                    if (packageName == null) {
+                        continue;
+                    }
+                    SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user,
+                            packageName);
+                    List<LauncherActivityInfo> activities = launcherApps
+                            .getActivityList(packageName, item.user);
+                    boolean hasActivity = activities != null && !activities.isEmpty();
+
+                    if (sessionInfo == null) {
+                        if (!hasActivity) {
+                            // Session was cancelled, do not add.
+                            continue;
+                        }
+                    } else {
+                        workspaceInfo.setInstallProgress((int) sessionInfo.getProgress());
+                    }
+
+                    if (hasActivity) {
+                        // App was installed while launcher was in the background,
+                        // or app was already installed for another user.
+                        itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
+                                .makeWorkspaceItem();
+
+                        if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
+                            // We need this additional check here since we treat all auto added
+                            // workspace items as promise icons. At this point we now have the
+                            // correct intent to compare against existing workspace icons.
+                            // Icon already exists on the workspace and should not be auto-added.
+                            continue;
+                        }
+
+                        WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
+                        wii.title = "";
+                        wii.applyFrom(app.getIconCache().getDefaultIcon(item.user));
+                        app.getIconCache().getTitleAndIcon(wii,
+                                ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
+                    }
+                }
+
                 // Add the shortcut to the db
                 getModelWriter().addItemToDatabase(itemInfo,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
@@ -162,7 +218,7 @@
             intentWithoutPkg = intent.toUri(0);
         }
 
-        boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent);
+        boolean isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent);
         synchronized (dataModel) {
             for (ItemInfo item : dataModel.itemsIdMap) {
                 if (item instanceof WorkspaceItemInfo) {
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
similarity index 69%
rename from src/com/android/launcher3/AllAppsList.java
rename to src/com/android/launcher3/model/AllAppsList.java
index 733f295..3873a17 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -14,25 +14,37 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.model;
+
+import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
+import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.os.LocaleList;
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 
+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.util.FlagOp;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.SafeCloseable;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.function.Consumer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -42,29 +54,40 @@
  * Stores the list of all applications for the all apps view.
  */
 public class AllAppsList {
+
     private static final String TAG = "AllAppsList";
+    private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
+
 
     public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
 
     /** The list off all apps. */
     public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
-    /** The list of apps that have been added since the last notify() call. */
-    public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER);
-    /** The list of apps that have been removed since the last notify() call. */
-    public ArrayList<AppInfo> removed = new ArrayList<>();
-    /** The list of apps that have been modified since the last notify() call. */
-    public ArrayList<AppInfo> modified = new ArrayList<>();
 
     private IconCache mIconCache;
-
     private AppFilter mAppFilter;
 
+    private boolean mDataChanged = false;
+    private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER;
+
+    private AlphabeticIndexCompat mIndex;
+
     /**
      * Boring constructor.
      */
     public AllAppsList(IconCache iconCache, AppFilter appFilter) {
         mIconCache = iconCache;
         mAppFilter = appFilter;
+        mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
+    }
+
+    /**
+     * Returns true if there have been any changes since last call.
+     */
+    public boolean getAndResetChangeFlag() {
+        boolean result = mDataChanged;
+        mDataChanged = false;
+        return result;
     }
 
     /**
@@ -81,44 +104,60 @@
             return;
         }
         mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+        info.sectionName = mIndex.computeSectionName(info.title);
 
         data.add(info);
-        added.add(info);
+        mDataChanged = true;
     }
 
     public void addPromiseApp(Context context,
                               PackageInstallerCompat.PackageInstallInfo installInfo) {
         ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context)
-                .getApplicationInfo(installInfo.packageName, 0, Process.myUserHandle());
+                .getApplicationInfo(installInfo.packageName, 0, installInfo.user);
         // only if not yet installed
         if (applicationInfo == null) {
             PromiseAppInfo info = new PromiseAppInfo(installInfo);
             mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
+            info.sectionName = mIndex.computeSectionName(info.title);
+
             data.add(info);
-            added.add(info);
+            mDataChanged = true;
         }
     }
 
-    public void removePromiseApp(AppInfo appInfo) {
-        // the <em>removed</em> list is handled by the caller
-        // so not adding it here
-        data.remove(appInfo);
+    public PromiseAppInfo updatePromiseInstallInfo(PackageInstallInfo installInfo) {
+        UserHandle user = Process.myUserHandle();
+        for (int i=0; i < data.size(); i++) {
+            final AppInfo appInfo = data.get(i);
+            final ComponentName tgtComp = appInfo.getTargetComponent();
+            if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName)
+                    && appInfo.user.equals(user)
+                    && appInfo instanceof PromiseAppInfo) {
+                final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
+                if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
+                    promiseAppInfo.level = installInfo.progress;
+                    return promiseAppInfo;
+                } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
+                    removeApp(i);
+                }
+            }
+        }
+        return null;
+    }
+
+    private void removeApp(int index) {
+        AppInfo removed = data.remove(index);
+        if (removed != null) {
+            mDataChanged = true;
+            mRemoveListener.accept(removed);
+        }
     }
 
     public void clear() {
         data.clear();
-        // TODO: do we clear these too?
-        added.clear();
-        removed.clear();
-        modified.clear();
-    }
-
-    public int size() {
-        return data.size();
-    }
-
-    public AppInfo get(int index) {
-        return data.get(index);
+        mDataChanged = false;
+        // Reset the index as locales might have changed
+        mIndex = new AlphabeticIndexCompat(LocaleList.getDefault());
     }
 
     /**
@@ -142,8 +181,7 @@
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
             if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) {
-                removed.add(info);
-                data.remove(i);
+                removeApp(i);
             }
         }
     }
@@ -157,17 +195,17 @@
             AppInfo info = data.get(i);
             if (matcher.matches(info, info.componentName)) {
                 info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
-                modified.add(info);
+                mDataChanged = true;
             }
         }
     }
 
-    public void updateIconsAndLabels(HashSet<String> packages, UserHandle user,
-            ArrayList<AppInfo> outUpdates) {
+    public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) {
         for (AppInfo info : data) {
             if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) {
                 mIconCache.updateTitleAndIcon(info);
-                outUpdates.add(info);
+                info.sectionName = mIndex.computeSectionName(info.title);
+                mDataChanged = true;
             }
         }
     }
@@ -188,8 +226,7 @@
                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
                     if (!findActivity(matches, applicationInfo.componentName)) {
                         Log.w(TAG, "Changing shortcut target due to app component name change.");
-                        removed.add(applicationInfo);
-                        data.remove(i);
+                        removeApp(i);
                     }
                 }
             }
@@ -202,7 +239,9 @@
                     add(new AppInfo(context, info, user), info);
                 } else {
                     mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
-                    modified.add(applicationInfo);
+                    applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
+
+                    mDataChanged = true;
                 }
             }
         } else {
@@ -211,15 +250,13 @@
                 final AppInfo applicationInfo = data.get(i);
                 if (user.equals(applicationInfo.user)
                         && packageName.equals(applicationInfo.componentName.getPackageName())) {
-                    removed.add(applicationInfo);
                     mIconCache.remove(applicationInfo.componentName, user);
-                    data.remove(i);
+                    removeApp(i);
                 }
             }
         }
     }
 
-
     /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
@@ -247,4 +284,16 @@
         }
         return null;
     }
+
+    public AppInfo[] copyData() {
+        AppInfo[] result = data.toArray(EMPTY_ARRAY);
+        Arrays.sort(result, COMPONENT_KEY_COMPARATOR);
+        return result;
+    }
+
+    public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) {
+        mRemoveListener = removeListener;
+
+        return () -> mRemoveListener = NO_OP_CONSUMER;
+    }
 }
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
index 1613d47..13ab033 100644
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ b/src/com/android/launcher3/model/AppLaunchTracker.java
@@ -15,18 +15,18 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.util.ResourceBasedOverride.Overrides.getObject;
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.content.ComponentName;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import androidx.annotation.Nullable;
-
 /**
  * Callback for receiving various app launch events
  */
@@ -43,8 +43,7 @@
 
 
     public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
-            new MainThreadInitializedObject<>(c ->
-                    getObject(AppLaunchTracker.class, c, R.string.app_launch_tracker_class));
+            forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
 
     public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
             @Nullable String container) { }
@@ -52,5 +51,8 @@
     public void onStartApp(ComponentName componentName, UserHandle user,
             @Nullable String container) { }
 
+    public void onDismissApp(ComponentName componentName, UserHandle user,
+             @Nullable String container){}
+
     public void onReturnedToHome() { }
 }
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 97cf267..0a4f005 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,21 +16,21 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.os.Looper;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 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.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
@@ -65,7 +65,7 @@
 
     public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
-        mUiExecutor = new MainThreadExecutor();
+        mUiExecutor = MAIN_EXECUTOR;
         mApp = app;
         mBgDataModel = dataModel;
         mBgAllAppsList = allAppsList;
@@ -279,9 +279,8 @@
 
     public void bindAllApps() {
         // shallow copy
-        @SuppressWarnings("unchecked")
-        ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
-        executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor);
+        AppInfo[] apps = mBgAllAppsList.copyData();
+        executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor);
     }
 
     public abstract void bindWidgets();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index eea3d8c..e12633b 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -17,12 +17,12 @@
 
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,6 +30,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -95,12 +96,7 @@
 
     public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
         if (!updatedShortcuts.isEmpty()) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindWorkspaceItemsChanged(updatedShortcuts);
-                }
-            });
+            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
         }
     }
 
@@ -113,23 +109,20 @@
     public void bindUpdatedWidgets(BgDataModel dataModel) {
         final ArrayList<WidgetListRowEntry> widgets =
                 dataModel.widgetsModel.getWidgetsList(mApp.getContext());
-        scheduleCallbackTask(new CallbackTask() {
-            @Override
-            public void execute(Callbacks callbacks) {
-                callbacks.bindAllWidgets(widgets);
-            }
-        });
+        scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
     }
 
     public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) {
         getModelWriter().deleteItemsFromDatabase(matcher);
 
         // Call the components-removed callback
-        scheduleCallbackTask(new CallbackTask() {
-            @Override
-            public void execute(Callbacks callbacks) {
-                callbacks.bindWorkspaceComponentsRemoved(matcher);
-            }
-        });
+        scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher));
+    }
+
+    public void bindApplicationsIfNeeded() {
+        if (mAllAppsList.getAndResetChangeFlag()) {
+            AppInfo[] apps = mAllAppsList.copyData();
+            scheduleCallbackTask(c -> c.bindAllApplications(apps));
+        }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8f0cd08..0e20270 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -22,11 +22,13 @@
 import android.util.Log;
 import android.util.MutableInt;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PromiseAppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
@@ -40,6 +42,10 @@
 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.ViewOnDrawExecutor;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
 import com.google.protobuf.nano.MessageNano;
 
 import java.io.FileDescriptor;
@@ -49,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -391,4 +398,30 @@
             }
         }
     }
+
+    public interface Callbacks {
+        void rebindModel();
+
+        int getCurrentWorkspaceScreen();
+        void clearPendingBinds();
+        void startBinding();
+        void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
+        void bindScreens(IntArray orderedScreenIds);
+        void finishFirstPageBind(ViewOnDrawExecutor executor);
+        void finishBindingItems(int pageBoundFirst);
+        void preAddApps();
+        void bindAppsAdded(IntArray newScreens,
+                ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
+        void bindPromiseAppProgressUpdated(PromiseAppInfo app);
+        void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+        void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
+        void bindRestoreItemsChange(HashSet<ItemInfo> updates);
+        void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
+        void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets);
+        void onPageBoundSynchronously(int page);
+        void executeOnNextDraw(ViewOnDrawExecutor executor);
+        void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+
+        void bindAllApplications(AppInfo[] apps);
+    }
 }
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 7852444..c1c8be3 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -18,14 +18,10 @@
 import android.content.ComponentName;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 
 import java.util.ArrayList;
@@ -53,9 +49,9 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         IconCache iconCache = app.getIconCache();
 
-        final ArrayList<AppInfo> updatedApps = new ArrayList<>();
 
         ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+
         synchronized (dataModel) {
             for (ItemInfo info : dataModel.itemsIdMap) {
                 if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) {
@@ -69,18 +65,10 @@
                     }
                 }
             }
-            apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
+            apps.updateIconsAndLabels(mPackages, mUser);
         }
         bindUpdatedWorkspaceItems(updatedShortcuts);
-
-        if (!updatedApps.isEmpty()) {
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindAppsAddedOrUpdated(updatedApps);
-                }
-            });
-        }
+        bindApplicationsIfNeeded();
     }
 
     public boolean isValidShortcut(WorkspaceItemInfo si) {
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index b8dbd08..3f7ba21 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -35,6 +35,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;
@@ -43,6 +44,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
@@ -69,7 +72,7 @@
 
     private final MultiHashMap<String, String> mPackagesForInstaller;
 
-    public FirstScreenBroadcast(HashMap<String, SessionInfo> sessionInfoForPackage) {
+    public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
         mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
     }
 
@@ -78,11 +81,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..783e908 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -34,10 +34,12 @@
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.function.Consumer;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -970,8 +972,9 @@
                 .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
             validPackages.add(info.packageName);
         }
-        validPackages.addAll(PackageInstallerCompat.getInstance(context)
-                .updateAndGetActiveSessionCache().keySet());
+        PackageInstallerCompat.getInstance(context)
+                .updateAndGetActiveSessionCache().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 1a03b77..1c39d1f 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -227,7 +227,7 @@
             if (!TextUtils.isEmpty(title)) {
                 info.title = Utilities.trim(title);
             }
-        } else if  (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) {
+        } else if (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) {
             if (TextUtils.isEmpty(info.title)) {
                 info.title = getTitle();
             }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 0138572..50e1d56 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,6 +20,8 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -31,7 +33,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
-import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -39,7 +40,6 @@
 import android.util.LongSparseArray;
 import android.util.MutableInt;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
@@ -49,29 +49,32 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
 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;
-import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherActivtiyCachingLogic;
+import com.android.launcher3.icons.LauncherActivityCachingLogic;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
+import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IOUtils;
 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;
@@ -196,7 +199,7 @@
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
             updateHandler.updateIcons(allActivityList,
-                    new LauncherActivtiyCachingLogic(mApp.getIconCache()),
+                    LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                     mApp.getModel()::onPackageIconsUpdated);
 
             // Take a break
@@ -226,9 +229,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");
@@ -281,8 +285,9 @@
         synchronized (mBgDataModel) {
             mBgDataModel.clear();
 
-            final HashMap<String, SessionInfo> installingPkgs =
+            final HashMap<PackageUserKey, SessionInfo> installingPkgs =
                     mPackageInstaller.updateAndGetActiveSessionCache();
+            final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
             mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
@@ -317,9 +322,9 @@
 
                     // We can only query for shortcuts when the user is unlocked.
                     if (userUnlocked) {
-                        List<ShortcutInfo> pinnedShortcuts =
+                        DeepShortcutManager.QueryResult pinnedShortcuts =
                                 mShortcutManager.queryForPinnedShortcuts(null, user);
-                        if (mShortcutManager.wasLastCallSuccess()) {
+                        if (pinnedShortcuts.wasSuccess()) {
                             for (ShortcutInfo shortcut : pinnedShortcuts) {
                                 shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
                                         shortcut);
@@ -384,7 +389,9 @@
                             boolean validTarget = TextUtils.isEmpty(targetPkg) ||
                                     mLauncherApps.isPackageEnabledForProfile(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.
 
@@ -419,9 +426,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,
@@ -531,12 +539,13 @@
                                 info.spanX = 1;
                                 info.spanY = 1;
                                 info.runtimeStatusFlags |= disabledState;
-                                if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
+                                if (isSafeMode && !isSystemApp(context, intent)) {
                                     info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
                                 }
 
                                 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 {
@@ -579,10 +588,19 @@
 
                             int appWidgetId = c.getInt(appWidgetIdIndex);
                             String savedProvider = c.getString(appWidgetProviderIndex);
+                            final ComponentName component;
 
-                            final ComponentName component =
-                                    ComponentName.unflattenFromString(savedProvider);
-
+                            boolean isSearchWidget = (c.getInt(optionsIndex)
+                                    & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0;
+                            if (isSearchWidget) {
+                                component  = QsbContainerView.getSearchComponentName(context);
+                                if (component == null) {
+                                    c.markDeleted("Discarding SearchWidget without packagename ");
+                                    continue;
+                                }
+                            } else {
+                                component = ComponentName.unflattenFromString(savedProvider);
+                            }
                             final boolean isIdValid = !c.hasRestoreFlag(
                                     LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
                             final boolean wasProviderReady = !c.hasRestoreFlag(
@@ -592,9 +610,7 @@
                                 widgetProvidersMap = mAppWidgetManager.getAllProvidersMap();
                             }
                             final AppWidgetProviderInfo provider = widgetProvidersMap.get(
-                                    new ComponentKey(
-                                            ComponentName.unflattenFromString(savedProvider),
-                                            c.user));
+                                    new ComponentKey(component, c.user));
 
                             final boolean isProviderReady = isValidProvider(provider);
                             if (!isSafeMode && !customWidget &&
@@ -630,8 +646,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);
@@ -658,6 +676,7 @@
                                 c.applyCommonProperties(appWidgetInfo);
                                 appWidgetInfo.spanX = c.getInt(spanXIndex);
                                 appWidgetInfo.spanY = c.getInt(spanYIndex);
+                                appWidgetInfo.options = c.getInt(optionsIndex);
                                 appWidgetInfo.user = c.user;
 
                                 if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
@@ -703,7 +722,7 @@
                     }
                 }
             } finally {
-                Utilities.closeSilently(c);
+                IOUtils.closeSilently(c);
             }
 
             // Break early if we've stopped loading
@@ -743,8 +762,8 @@
             }
 
             // Sort the folder items, update ranks, and make sure all preview items are high res.
-            FolderIconPreviewVerifier verifier =
-                    new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
+            FolderGridOrganizer verifier =
+                    new FolderGridOrganizer(mApp.getInvariantDeviceProfile());
             for (FolderInfo folder : mBgDataModel.folders) {
                 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                 verifier.setFolderInfo(folder);
@@ -771,7 +790,7 @@
                         new SdCardAvailableReceiver(mApp, pendingPackages),
                         new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
                         null,
-                        new Handler(LauncherModel.getWorkerLooper()));
+                        MODEL_EXECUTOR.getHandler());
             }
         }
     }
@@ -829,7 +848,7 @@
             }
         }
 
-        mBgAllAppsList.added = new ArrayList<>();
+        mBgAllAppsList.getAndResetChangeFlag();
         return allActivityList;
     }
 
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index b353810..2bd6cd4 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 4ce2f4b..bdf3a69 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -31,23 +33,25 @@
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.WorkspaceItemInfo;
 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 com.android.launcher3.util.LooperExecutor;
 
 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.
@@ -61,7 +65,6 @@
     private final BgDataModel mBgDataModel;
     private final Handler mUiHandler;
 
-    private final Executor mWorkerExecutor;
     private final boolean mHasVerticalHotseat;
     private final boolean mVerifyChanges;
 
@@ -74,7 +77,6 @@
         mContext = context;
         mModel = model;
         mBgDataModel = dataModel;
-        mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
         mHasVerticalHotseat = hasVerticalHotseat;
         mVerifyChanges = verifyChanges;
         mUiHandler = new Handler(Looper.getMainLooper());
@@ -101,7 +103,7 @@
      */
     public void addOrMoveItemInDatabase(ItemInfo item,
             int container, int screenId, int cellX, int cellY) {
-        if (item.container == ItemInfo.NO_ID) {
+        if (item.id == ItemInfo.NO_ID) {
             // From all apps
             addItemToDatabase(item, container, screenId, cellX, cellY);
         } else {
@@ -194,7 +196,7 @@
         item.spanX = spanX;
         item.spanY = spanY;
 
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, () ->
+        ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () ->
                 new ContentWriter(mContext)
                         .put(Favorites.CONTAINER, item.container)
                         .put(Favorites.CELLX, item.cellX)
@@ -209,7 +211,7 @@
      * Update an item to the database in a specified container.
      */
     public void updateItemInDatabase(ItemInfo item) {
-        mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> {
+        ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> {
             ContentWriter writer = new ContentWriter(mContext);
             item.onAddToDatabase(writer);
             return writer;
@@ -229,7 +231,7 @@
 
         ModelVerifier verifier = new ModelVerifier();
         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        mWorkerExecutor.execute(() -> {
+        ((Executor) MODEL_EXECUTOR).execute(() -> {
             // Write the item on background thread, as some properties might have been updated in
             // the background.
             final ContentWriter writer = new ContentWriter(mContext);
@@ -263,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);
@@ -333,14 +338,14 @@
         if (mPreparingToUndo) {
             mDeleteRunnables.add(r);
         } else {
-            mWorkerExecutor.execute(r);
+            ((Executor) MODEL_EXECUTOR).execute(r);
         }
     }
 
     public void commitDelete() {
         mPreparingToUndo = false;
         for (Runnable runnable : mDeleteRunnables) {
-            mWorkerExecutor.execute(runnable);
+            ((Executor) MODEL_EXECUTOR).execute(runnable);
         }
         mDeleteRunnables.clear();
     }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 5f6d128..802cbc7 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -18,22 +18,19 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.Process;
 
-import com.android.launcher3.AllAppsList;
 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.LauncherModel.Callbacks;
+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.util.InstantAppResolver;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
@@ -56,7 +53,7 @@
                 ApplicationInfo ai = app.getContext()
                         .getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
                 if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) {
-                    app.getModel().onPackageAdded(ai.packageName, Process.myUserHandle());
+                    app.getModel().onPackageAdded(ai.packageName, mInstallInfo.user);
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Ignore
@@ -66,41 +63,11 @@
         }
 
         synchronized (apps) {
-            PromiseAppInfo updated = null;
-            final ArrayList<AppInfo> removed = new ArrayList<>();
-            for (int i=0; i < apps.size(); i++) {
-                final AppInfo appInfo = apps.get(i);
-                final ComponentName tgtComp = appInfo.getTargetComponent();
-                if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) {
-                    if (appInfo instanceof PromiseAppInfo) {
-                        final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo;
-                        if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) {
-                            promiseAppInfo.level = mInstallInfo.progress;
-                            updated = promiseAppInfo;
-                        } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
-                            apps.removePromiseApp(appInfo);
-                            removed.add(appInfo);
-                        }
-                    }
-                }
-            }
+            PromiseAppInfo updated = apps.updatePromiseInstallInfo(mInstallInfo);
             if (updated != null) {
-                final PromiseAppInfo updatedPromiseApp = updated;
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp);
-                    }
-                });
+                scheduleCallbackTask(c -> c.bindPromiseAppProgressUpdated(updated));
             }
-            if (!removed.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindAppInfosRemoved(removed);
-                    }
-                });
-            }
+            bindApplicationsIfNeeded();
         }
 
         synchronized (dataModel) {
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index baeaa94..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,21 @@
 
     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
     protected String dumpProperties() {
         return super.dumpProperties() + " packageName=" + packageName;
     }
+
+    @Override
+    public PackageItemInfo clone() {
+        return new PackageItemInfo(this);
+    }
 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index c37ed99..d6ebaaf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -23,31 +25,29 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.AllAppsList;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.InstallShortcutReceiver;
 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.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings.Favorites;
 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;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -55,6 +55,9 @@
 import java.util.HashSet;
 import java.util.List;
 
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
+
 /**
  * Handles updates due to changes in package manager (app installed/updated/removed)
  * or when a user availability changes.
@@ -85,6 +88,10 @@
 
     @Override
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " +
+                    Arrays.toString(mPackages));
+        }
         final Context context = app.getContext();
         final IconCache iconCache = app.getIconCache();
 
@@ -93,13 +100,15 @@
         FlagOp flagOp = FlagOp.NO_OP;
         final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
         ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser);
+        final HashSet<ComponentName> removedComponents = new HashSet<>();
+
         switch (mOp) {
             case OP_ADD: {
                 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) {
-                        appsList.removePackage(packages[i], Process.myUserHandle());
+                        appsList.removePackage(packages[i], mUser);
                     }
                     appsList.addPackage(context, packages[i], mUser);
 
@@ -112,17 +121,21 @@
                 break;
             }
             case OP_UPDATE:
-                for (int i = 0; i < N; i++) {
-                    if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
-                    iconCache.updateIconsForPkg(packages[i], mUser);
-                    appsList.updatePackage(context, packages[i], mUser);
-                    app.getWidgetCache().removePackage(packages[i], mUser);
+                try (SafeCloseable t =
+                             appsList.trackRemoves(a -> removedComponents.add(a.componentName))) {
+                    for (int i = 0; i < N; i++) {
+                        if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
+                        iconCache.updateIconsForPkg(packages[i], mUser);
+                        appsList.updatePackage(context, packages[i], mUser);
+                        app.getWidgetCache().removePackage(packages[i], mUser);
+                    }
                 }
                 // Since package was just updated, the target must be available now.
                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 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
@@ -153,23 +166,7 @@
                 break;
         }
 
-        final ArrayList<AppInfo> addedOrModified = new ArrayList<>();
-        addedOrModified.addAll(appsList.added);
-        appsList.added.clear();
-        addedOrModified.addAll(appsList.modified);
-        appsList.modified.clear();
-        if (!addedOrModified.isEmpty()) {
-            scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified));
-        }
-
-        final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed);
-        appsList.removed.clear();
-        final HashSet<ComponentName> removedComponents = new HashSet<>();
-        if (mOp == OP_UPDATE) {
-            for (AppInfo ai : removedApps) {
-                removedComponents.add(ai.componentName);
-            }
-        }
+        bindApplicationsIfNeeded();
 
         final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>();
 
@@ -227,8 +224,7 @@
                                     isTargetValid = LauncherAppsCompat.getInstance(context)
                                             .isActivityEnabledForProfile(cn, mUser);
                                 }
-                                if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)
-                                        && !isTargetValid) {
+                                if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                     if (updateWorkspaceItemIntent(context, si, packageName)) {
                                         infoUpdated = true;
                                     } else if (si.hasPromiseIconUi()) {
@@ -296,12 +292,7 @@
             }
 
             if (!widgets.isEmpty()) {
-                scheduleCallbackTask(new CallbackTask() {
-                    @Override
-                    public void execute(Callbacks callbacks) {
-                        callbacks.bindWidgetsRestored(widgets);
-                    }
-                });
+                scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
             }
         }
 
@@ -332,16 +323,6 @@
             InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
         }
 
-        if (!removedApps.isEmpty()) {
-            // Remove corresponding apps from All-Apps
-            scheduleCallbackTask(new CallbackTask() {
-                @Override
-                public void execute(Callbacks callbacks) {
-                    callbacks.bindAppInfosRemoved(removedApps);
-                }
-            });
-        }
-
         if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 8528228..6c358b1 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -19,7 +19,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -65,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/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 2cb256e..4b773d7 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -21,7 +21,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -37,7 +36,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 
 /**
  * Task to handle changing of lock state of the user
@@ -58,9 +56,9 @@
 
         HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
         if (isUserUnlocked) {
-            List<ShortcutInfo> shortcuts =
+            DeepShortcutManager.QueryResult shortcuts =
                     deepShortcutManager.queryForPinnedShortcuts(null, mUser);
-            if (deepShortcutManager.wasLastCallSuccess()) {
+            if (shortcuts.wasSuccess()) {
                 for (ShortcutInfo shortcut : shortcuts) {
                     pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
                 }
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 32410a6..021fb30 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.SingleAxisSwipeDetector.HORIZONTAL;
+
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Color;
@@ -28,13 +30,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
 
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
-
 /**
  * Utility class to manage notification UI
  */
@@ -49,7 +49,7 @@
     private final TextView mHeaderCount;
     private final NotificationMainView mMainView;
     private final NotificationFooterLayout mFooter;
-    private final SwipeDetector mSwipeDetector;
+    private final SingleAxisSwipeDetector mSwipeDetector;
     private final View mIconView;
 
     private final View mHeader;
@@ -74,8 +74,8 @@
         mHeader = container.findViewById(R.id.header);
         mDivider = container.findViewById(R.id.divider);
 
-        mSwipeDetector = new SwipeDetector(mContext, mMainView, HORIZONTAL);
-        mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+        mSwipeDetector = new SingleAxisSwipeDetector(mContext, mMainView, HORIZONTAL);
+        mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false);
         mMainView.setSwipeDetector(mSwipeDetector);
         mFooter.setContainer(this);
     }
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index 5050457..a1917ec 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -17,13 +17,17 @@
 package com.android.launcher3.notification;
 
 import android.app.Notification;
+import android.app.Person;
 import android.service.notification.StatusBarNotification;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.NonNull;
-
 /**
  * The key data associated with the notification, used to determine what to include
  * in dots and dummy popup views before they are populated.
@@ -33,20 +37,27 @@
 public class NotificationKeyData {
     public final String notificationKey;
     public final String shortcutId;
+    @NonNull
+    public final String[] personKeysFromNotification;
     public int count;
 
-    private NotificationKeyData(String notificationKey, String shortcutId, int count) {
+    private NotificationKeyData(String notificationKey, String shortcutId, int count,
+            String[] personKeysFromNotification) {
         this.notificationKey = notificationKey;
         this.shortcutId = shortcutId;
         this.count = Math.max(1, count);
+        this.personKeysFromNotification = personKeysFromNotification;
     }
 
     public static NotificationKeyData fromNotification(StatusBarNotification sbn) {
         Notification notif = sbn.getNotification();
-        return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number);
+        return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number,
+                extractPersonKeyOnly(notif.extras.getParcelableArrayList(
+                        Notification.EXTRA_PEOPLE_LIST)));
     }
 
-    public static List<String> extractKeysOnly(@NonNull List<NotificationKeyData> notificationKeys) {
+    public static List<String> extractKeysOnly(
+            @NonNull List<NotificationKeyData> notificationKeys) {
         List<String> keysOnly = new ArrayList<>(notificationKeys.size());
         for (NotificationKeyData notificationKeyData : notificationKeys) {
             keysOnly.add(notificationKeyData.notificationKey);
@@ -54,6 +65,14 @@
         return keysOnly;
     }
 
+    private static String[] extractPersonKeyOnly(@Nullable ArrayList<Person> people) {
+        if (people == null || people.isEmpty()) {
+            return Utilities.EMPTY_STRING_ARRAY;
+        }
+        return people.stream().filter(person -> person.getKey() != null)
+                .map(Person::getKey).sorted().toArray(String[]::new);
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof NotificationKeyData)) {
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index e57a051..10378ee 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.annotation.TargetApi;
@@ -31,7 +32,8 @@
 import android.util.Log;
 import android.util.Pair;
 
-import com.android.launcher3.LauncherModel;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.SecureSettingsObserver;
@@ -43,8 +45,6 @@
 import java.util.List;
 import java.util.Map;
 
-import androidx.annotation.Nullable;
-
 /**
  * A {@link NotificationListenerService} that sends updates to its
  * {@link NotificationsChangedListener} when notifications are posted or canceled,
@@ -141,7 +141,7 @@
 
     public NotificationListener() {
         super();
-        mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback);
+        mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback);
         mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback);
         sNotificationListenerInstance = this;
     }
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 78627ec..b67adbb 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -38,8 +38,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Themes;
 
@@ -48,7 +49,7 @@
  * e.g. icon + title + text.
  */
 @TargetApi(Build.VERSION_CODES.N)
-public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener {
+public class NotificationMainView extends FrameLayout implements SingleAxisSwipeDetector.Listener {
 
     private static FloatProperty<NotificationMainView> CONTENT_TRANSLATION =
             new FloatProperty<NotificationMainView>("contentTranslation") {
@@ -75,7 +76,7 @@
     private TextView mTextView;
     private View mIconView;
 
-    private SwipeDetector mSwipeDetector;
+    private SingleAxisSwipeDetector mSwipeDetector;
 
     public NotificationMainView(Context context) {
         this(context, null, 0);
@@ -107,7 +108,7 @@
         mIconView = findViewById(R.id.popup_item_icon);
     }
 
-    public void setSwipeDetector(SwipeDetector swipeDetector) {
+    public void setSwipeDetector(SingleAxisSwipeDetector swipeDetector) {
         mSwipeDetector = swipeDetector;
     }
 
@@ -173,7 +174,7 @@
                 LauncherLogProto.ItemType.NOTIFICATION);
     }
 
-    // SwipeDetector.Listener's
+    // SingleAxisSwipeDetector.Listener's
     @Override
     public void onDragStart(boolean start) { }
 
@@ -187,7 +188,7 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
+    public void onDragEnd(float velocity) {
         final boolean willExit;
         final float endTranslation;
         final float startTranslation = mTextAndBackground.getTranslationX();
@@ -195,7 +196,7 @@
         if (!canChildBeDismissed()) {
             willExit = false;
             endTranslation = 0;
-        } else if (fling) {
+        } else if (mSwipeDetector.isFling(velocity)) {
             willExit = true;
             endTranslation = velocity < 0 ? - getWidth() : getWidth();
         } else if (Math.abs(startTranslation) > getWidth() / 2) {
@@ -206,7 +207,7 @@
             endTranslation = 0;
         }
 
-        long duration = SwipeDetector.calculateDuration(velocity,
+        long duration = BaseSwipeDetector.calculateDuration(velocity,
                 (endTranslation - startTranslation) / getWidth());
 
         mContentTranslateAnimator.removeAllListeners();
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 28000b9..98f7fd8 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -360,10 +360,14 @@
         final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
 
         // Rectangular reveal.
+        mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
         final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
                 .createRevealAnimator(this, false);
         revealAnim.setDuration(revealDuration);
         revealAnim.setInterpolator(revealInterpolator);
+        // Clip the popup to the initial outline while the notification dot and arrow animate.
+        revealAnim.start();
+        revealAnim.pause();
 
         ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1);
         fadeIn.setDuration(revealDuration + arrowDuration);
@@ -399,7 +403,6 @@
         if (!mIsOpen) {
             return;
         }
-        mEndRect.setEmpty();
         if (getOutlineProvider() instanceof RevealOutlineAnimation) {
             ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
         }
@@ -471,9 +474,6 @@
 
         mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth,
                 arrowCenterY);
-        if (mEndRect.isEmpty()) {
-            mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
-        }
 
         return new RoundedRectRevealOutlineProvider
                 (arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 9719a18..e8ac1d4 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -51,9 +52,7 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
@@ -65,13 +64,13 @@
 import com.android.launcher3.notification.NotificationItemView;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 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;
+import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.ArrayList;
@@ -169,7 +168,10 @@
     }
 
     public OnClickListener getItemClickListener() {
-        return ItemClickHandler.INSTANCE;
+        return (view) -> {
+            ItemClickHandler.INSTANCE.onClick(view);
+            close(true);
+        };
     }
 
     @Override
@@ -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.
@@ -201,7 +206,7 @@
             return null;
         }
         ItemInfo itemInfo = (ItemInfo) icon.getTag();
-        if (!DeepShortcutManager.supportsShortcuts(itemInfo)) {
+        if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
             return null;
         }
 
@@ -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),
@@ -300,14 +308,13 @@
         }
 
         mLauncher.getDragController().addDragListener(this);
-        mOriginalIcon.forceHideDot(true);
+        mOriginalIcon.setForceHideDot(true);
 
         // All views are added. Animate layout from now on.
         setLayoutTransition(new LayoutTransition());
 
         // Load the shortcuts on a background thread and update the container as it animates.
-        final Looper workerLooper = LauncherModel.getWorkerLooper();
-        new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
                 this, mShortcuts, notificationKeys));
     }
@@ -449,11 +456,6 @@
 
             @Override
             public boolean shouldStartDrag(double distanceDragged) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_DRAG_TAG,
-                            "createPreDragCondition().shouldStartDrag " + distanceDragged + ", "
-                                    + mStartDragThreshold);
-                }
                 return distanceDragged > mStartDragThreshold;
             }
 
@@ -568,14 +570,17 @@
     protected void onCreateCloseAnimation(AnimatorSet anim) {
         // Animate original icon's text back in.
         anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
-        mOriginalIcon.forceHideDot(false);
+        mOriginalIcon.setForceHideDot(false);
     }
 
     @Override
     protected void closeComplete() {
+        PopupContainerWithArrow openPopup = getOpen(mLauncher);
+        if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
+            mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+            mOriginalIcon.setForceHideDot(false);
+        }
         super.closeComplete();
-        mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
-        mOriginalIcon.forceHideDot(false);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 2d301ac..4612b2a 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -29,17 +29,22 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * Provides data for the popup menu that appears after long-clicking on apps.
@@ -129,7 +134,8 @@
         for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
             DotInfo prevDot = updatedDots.get(packageUserKey);
             DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
-            if (prevDot == null) {
+            if (prevDot == null
+                    || prevDot.getNotificationCount() != newDot.getNotificationCount()) {
                 updatedDots.put(packageUserKey, newDot);
             } else {
                 // No need to update the dot if it already existed (no visual change).
@@ -155,7 +161,7 @@
     }
 
     public int getShortcutCountForItem(ItemInfo info) {
-        if (!DeepShortcutManager.supportsShortcuts(info)) {
+        if (!ShortcutUtil.supportsDeepShortcuts(info)) {
             return 0;
         }
         ComponentName component = info.getTargetComponent();
@@ -167,17 +173,26 @@
         return count == null ? 0 : count;
     }
 
-    public DotInfo getDotInfoForItem(ItemInfo info) {
-        if (!DeepShortcutManager.supportsShortcuts(info)) {
+    public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) {
+        if (!ShortcutUtil.supportsShortcuts(info)) {
             return null;
         }
-
-        return mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
+        DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
+        if (dotInfo == null) {
+            return null;
+        }
+        List<NotificationKeyData> notifications = getNotificationsForItem(
+                info, dotInfo.getNotificationKeys());
+        if (notifications.isEmpty()) {
+            return null;
+        }
+        return dotInfo;
     }
 
     public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
         DotInfo dotInfo = getDotInfoForItem(info);
-        return dotInfo == null ? Collections.EMPTY_LIST : dotInfo.getNotificationKeys();
+        return dotInfo == null ? Collections.EMPTY_LIST
+                : getNotificationsForItem(info, dotInfo.getNotificationKeys());
     }
 
     /** This makes a potentially expensive binder call and should be run on a background thread. */
@@ -226,6 +241,27 @@
         return null;
     }
 
+    /**
+     * Returns a list of notifications that are relevant to given ItemInfo.
+     */
+    public static @NonNull List<NotificationKeyData> getNotificationsForItem(
+            @NonNull ItemInfo info, @NonNull List<NotificationKeyData> notifications) {
+        String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info);
+        if (shortcutId == null) {
+            return notifications;
+        }
+        String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info);
+        return notifications.stream().filter((NotificationKeyData notification) -> {
+                    if (notification.shortcutId != null) {
+                        return notification.shortcutId.equals(shortcutId);
+                    }
+                    if (notification.personKeysFromNotification.length != 0) {
+                        return Arrays.equals(notification.personKeysFromNotification, personKeys);
+                    }
+                    return false;
+                }).collect(Collectors.toList());
+    }
+
     public interface PopupDataChangeListener {
 
         PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { };
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 41ab4df..5a5fbab 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -29,11 +29,12 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
     private static final String TAG = "RemoteActionShortcut";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE;
 
     private final RemoteAction mAction;
 
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 563f3b3..a87b7b8 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,14 +1,11 @@
 package com.android.launcher3.popup;
 
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.View;
@@ -20,23 +17,30 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetsBottomSheet;
 
 import java.util.List;
-
 /**
  * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
  * onClickListener that depends on the item that the shortcut services.
  *
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
+ * @param <T>
  */
-public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
+public abstract class SystemShortcut<T extends BaseDraggingActivity>
+        extends ItemInfo {
     private final int mIconResId;
     private final int mLabelResId;
     private final Icon mIcon;
@@ -131,6 +135,7 @@
         @Override
         public View.OnClickListener getOnClickListener(final Launcher launcher,
                 final ItemInfo itemInfo) {
+            if (itemInfo.getTargetComponent() == null) return null;
             final List<WidgetItem> widgets =
                     launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
                             itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
@@ -201,6 +206,27 @@
         }
     }
 
+    public static class DismissPrediction extends SystemShortcut<Launcher> {
+        public DismissPrediction() {
+            super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label);
+        }
+
+        @Override
+        public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) {
+            if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
+            if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null;
+            return (view) -> {
+                PopupContainerWithArrow.closeAllOpenViews(activity);
+                activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                        ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
+                AppLaunchTracker.INSTANCE.get(view.getContext())
+                        .onDismissApp(itemInfo.getTargetComponent(),
+                                itemInfo.user,
+                                AppLaunchTracker.CONTAINER_PREDICTIONS);
+            };
+        }
+    }
+
     protected static void dismissTaskMenuView(BaseDraggingActivity activity) {
         AbstractFloatingView.closeOpenViews(activity, true,
             AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java
index 516fafa..dfcc2f8 100644
--- a/src/com/android/launcher3/popup/SystemShortcutFactory.java
+++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java
@@ -15,6 +15,10 @@
  */
 package com.android.launcher3.popup;
 
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
@@ -24,13 +28,10 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.NonNull;
-
 public class SystemShortcutFactory implements ResourceBasedOverride {
 
     public static final MainThreadInitializedObject<SystemShortcutFactory> INSTANCE =
-            new MainThreadInitializedObject<>(c -> Overrides.getObject(
-                    SystemShortcutFactory.class, c, R.string.system_shortcut_factory_class));
+            forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class);
 
     /** Note that these are in order of priority. */
     private final SystemShortcut[] mAllShortcuts;
@@ -38,7 +39,9 @@
     @SuppressWarnings("unused")
     public SystemShortcutFactory() {
         this(new SystemShortcut.AppInfo(),
-                new SystemShortcut.Widgets(), new SystemShortcut.Install());
+                new SystemShortcut.Widgets(),
+                new SystemShortcut.Install(),
+                new SystemShortcut.DismissPrediction());
     }
 
     protected SystemShortcutFactory(SystemShortcut... shortcuts) {
@@ -52,6 +55,7 @@
                 systemShortcuts.add(systemShortcut);
             }
         }
+
         return systemShortcuts;
     }
 }
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
index 7b62f53..970a03e 100644
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ b/src/com/android/launcher3/provider/ImportDataTask.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -50,6 +49,7 @@
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.PackageManagerHelper;
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -223,7 +223,7 @@
                     case Favorites.ITEM_TYPE_SHORTCUT:
                     case Favorites.ITEM_TYPE_APPLICATION: {
                         intent = Intent.parseUri(c.getString(intentIndex), 0);
-                        if (Utilities.isLauncherAppTarget(intent)) {
+                        if (PackageManagerHelper.isLauncherAppTarget(intent)) {
                             type = Favorites.ITEM_TYPE_APPLICATION;
                         } else {
                             values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 3c0c5fd..fb33551 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.provider;
 
-import static com.android.launcher3.Utilities.getIntArrayFromString;
-import static com.android.launcher3.Utilities.getStringFromIntArray;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
 import android.app.backup.BackupManager;
@@ -40,6 +38,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
 
 import java.io.InvalidObjectException;
@@ -173,12 +172,6 @@
         values.put(Favorites.PROFILE_ID, newProfileId);
         db.update(Favorites.TABLE_NAME, values, "profileId = ?",
                 new String[]{Long.toString(oldProfileId)});
-
-        // Change default value of the column.
-        db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
-        Favorites.addTableToDb(db, newProfileId, false);
-        db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
-        dropTable(db, "favorites_old");
     }
 
 
@@ -246,8 +239,8 @@
         SharedPreferences prefs = Utilities.getPrefs(context);
         if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
             AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
-                    getIntArrayFromString(prefs.getString(APPWIDGET_OLD_IDS, "")),
-                    getIntArrayFromString(prefs.getString(APPWIDGET_IDS, "")));
+                    IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
+                    IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray());
         } else {
             FileLog.d(TAG, "No app widget ids to restore.");
         }
@@ -259,8 +252,8 @@
     public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds,
             @NonNull int[] newIds) {
         Utilities.getPrefs(context).edit()
-                .putString(APPWIDGET_OLD_IDS, getStringFromIntArray(oldIds))
-                .putString(APPWIDGET_IDS, getStringFromIntArray(newIds))
+                .putString(APPWIDGET_OLD_IDS, IntArray.wrap(oldIds).toConcatString())
+                .putString(APPWIDGET_IDS, IntArray.wrap(newIds).toConcatString())
                 .commit();
     }
 
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index 857ea05..0eb4285 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -32,12 +32,16 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
@@ -55,6 +59,74 @@
  */
 public class QsbContainerView extends FrameLayout {
 
+    public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME";
+
+    /**
+     * Returns the package name for user configured search provider or from searchManager
+     * @param context
+     * @return String
+     */
+    @Nullable
+    public static String getSearchWidgetPackageName(@NonNull Context context) {
+        String providerPkg = Settings.Global.getString(context.getContentResolver(),
+                SEARCH_PROVIDER_SETTINGS_KEY);
+        if (providerPkg == null) {
+            SearchManager searchManager = context.getSystemService(SearchManager.class);
+            ComponentName componentName = searchManager.getGlobalSearchActivity();
+            if (componentName != null) {
+                providerPkg = searchManager.getGlobalSearchActivity().getPackageName();
+            }
+        }
+        return providerPkg;
+    }
+
+    /**
+     * returns it's AppWidgetProviderInfo using package name from getSearchWidgetPackageName
+     * @param context
+     * @return AppWidgetProviderInfo
+     */
+    @Nullable
+    public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) {
+        String providerPkg = getSearchWidgetPackageName(context);
+        if (providerPkg == null) {
+            return null;
+        }
+
+        AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
+        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+        for (AppWidgetProviderInfo info :
+                appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)) {
+            if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
+                if ((info.widgetCategory
+                        & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
+                    return info;
+                } else if (defaultWidgetForSearchPackage == null) {
+                    defaultWidgetForSearchPackage = info;
+                }
+            }
+        }
+        return defaultWidgetForSearchPackage;
+    }
+
+    /**
+     * returns componentName for searchWidget if package name is known.
+     */
+    @Nullable
+    public static ComponentName getSearchComponentName(@NonNull  Context context) {
+        AppWidgetProviderInfo providerInfo =
+                QsbContainerView.getSearchWidgetProviderInfo(context);
+        if (providerInfo != null) {
+            return providerInfo.provider;
+        } else {
+            String pkgName = QsbContainerView.getSearchWidgetPackageName(context);
+            if (pkgName != null) {
+                //we don't know the class name yet. we'll put the package name as placeholder
+                return new ComponentName(pkgName, pkgName);
+            }
+            return null;
+        }
+    }
+
     public QsbContainerView(Context context) {
         super(context);
     }
@@ -101,7 +173,7 @@
 
         protected QsbWidgetHost createHost() {
             return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID,
-                    (c) -> new QsbWidgetHostView(c));
+                    (c) -> new QsbWidgetHostView(c), this::rebindFragment);
         }
 
         private FrameLayout mWrapper;
@@ -111,9 +183,9 @@
                 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
             mWrapper = new FrameLayout(getContext());
-
             // Only add the view when enabled
             if (isQsbEnabled()) {
+                mQsbWidgetHost.startListening();
                 mWrapper.addView(createQsb(mWrapper));
             }
             return mWrapper;
@@ -155,7 +227,8 @@
             }
 
             if (isWidgetBound) {
-                mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, mWidgetInfo);
+                mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId,
+                        mWidgetInfo);
                 mQsb.setId(R.id.qsb_widget);
 
                 if (!isInPreviewMode()) {
@@ -163,7 +236,6 @@
                             .getAppWidgetOptions(widgetId), opts)) {
                         mQsb.updateAppWidgetOptions(opts);
                     }
-                    mQsbWidgetHost.startListening();
                 }
                 return mQsb;
             }
@@ -246,43 +318,32 @@
             return v;
         }
 
+
         /**
          * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}
-         * provided by the same package which is set to be global search activity.
+         * provided by the package from getSearchProviderPackageName
          * If widgetCategory is not supported, or no such widget is found, returns the first widget
          * provided by the package.
          */
         protected AppWidgetProviderInfo getSearchWidgetProvider() {
-            SearchManager searchManager =
-                    (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE);
-            ComponentName searchComponent = searchManager.getGlobalSearchActivity();
-            if (searchComponent == null) return null;
-            String providerPkg = searchComponent.getPackageName();
-
-            AppWidgetProviderInfo defaultWidgetForSearchPackage = null;
-
-            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
-            for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) {
-                if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) {
-                    if ((info.widgetCategory
-                            & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) {
-                        return info;
-                    } else if (defaultWidgetForSearchPackage == null) {
-                        defaultWidgetForSearchPackage = info;
-                    }
-                }
-            }
-            return defaultWidgetForSearchPackage;
+            return getSearchWidgetProviderInfo(getContext());
         }
     }
 
     public static class QsbWidgetHost extends AppWidgetHost {
 
         private final WidgetViewFactory mViewFactory;
+        private final WidgetProvidersUpdateCallback mWidgetsUpdateCallback;
 
-        public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
+        public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory,
+                WidgetProvidersUpdateCallback widgetProvidersUpdateCallback) {
             super(context, hostId);
             mViewFactory = viewFactory;
+            mWidgetsUpdateCallback = widgetProvidersUpdateCallback;
+        }
+
+        public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) {
+            this(context, hostId, viewFactory, null);
         }
 
         @Override
@@ -290,6 +351,14 @@
                 Context context, int appWidgetId, AppWidgetProviderInfo appWidget) {
             return mViewFactory.newView(context);
         }
+
+        @Override
+        protected void onProvidersChanged() {
+            super.onProvidersChanged();
+            if (mWidgetsUpdateCallback != null) {
+                mWidgetsUpdateCallback.onProvidersUpdated();
+            }
+        }
     }
 
     public interface WidgetViewFactory {
@@ -298,6 +367,17 @@
     }
 
     /**
+     * Callback interface for packages list update.
+     */
+    @FunctionalInterface
+    public interface WidgetProvidersUpdateCallback {
+        /**
+         * Gets called when widget providers list changes
+         */
+        void onProvidersUpdated();
+    }
+
+    /**
      * Returns true if {@param original} contains all entries defined in {@param updates} and
      * have the same value.
      * The comparison uses {@link Object#equals(Object)} to compare the values.
@@ -316,4 +396,5 @@
         }
         return true;
     }
+
 }
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 18b6094..8d2db78 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.settings;
 
+import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
+
 import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
@@ -32,14 +34,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 
-import com.android.launcher3.LauncherFiles;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.GridOptionsProvider;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.SecureSettingsObserver;
-
+import androidx.annotation.NonNull;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragment;
 import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback;
@@ -48,6 +43,14 @@
 import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.GridOptionsProvider;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.SecureSettingsObserver;
+
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
  */
@@ -248,6 +251,8 @@
                 if (highlighter != null) {
                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
                     mPreferenceHighlighted = true;
+                } else {
+                    requestAccessibilityFocus(getListView());
                 }
             }
         }
@@ -268,6 +273,15 @@
             return position >= 0 ? new PreferenceHighlighter(list, position) : null;
         }
 
+        private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
+            rv.post(() -> {
+                if (!rv.hasFocus() && rv.getChildCount() > 0) {
+                    rv.getChildAt(0)
+                            .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
+                }
+            });
+        }
+
         @Override
         public void onDestroy() {
             if (mNotificationDotsObserver != null) {
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index c6370c5..a23cd6d 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -15,6 +15,8 @@
  */
 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;
@@ -22,8 +24,7 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
-import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 
 import java.lang.ref.WeakReference;
 
@@ -94,16 +95,12 @@
     private static class Scheduler implements Runnable {
 
         private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null);
-        private MainThreadExecutor mMainThreadExecutor;
 
         public void schedule(InternalStateHandler handler) {
             synchronized (this) {
                 mPendingHandler = new WeakReference<>(handler);
-                if (mMainThreadExecutor == null) {
-                    mMainThreadExecutor = new MainThreadExecutor();
-                }
             }
-            mMainThreadExecutor.execute(this);
+            MAIN_EXECUTOR.execute(this);
         }
 
         @Override
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d2e1961..c6de9ca 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,19 +15,27 @@
  */
 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 com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.LinkedList;
 import java.util.concurrent.ExecutionException;
 
 public class TestInformationHandler implements ResourceBasedOverride {
@@ -41,6 +49,7 @@
     protected DeviceProfile mDeviceProfile;
     protected LauncherAppState mLauncherAppState;
     protected Launcher mLauncher;
+    private static LinkedList mLeaks;
 
     public void init(Context context) {
         mContext = context;
@@ -74,6 +83,11 @@
                 break;
             }
 
+            case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
+                break;
+            }
+
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
                 TestProtocol.sDebugTracing = true;
                 break;
@@ -83,20 +97,20 @@
                 break;
 
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
-                new MainThreadExecutor().execute(() ->
+                MAIN_EXECUTOR.execute(() ->
                         mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
                                 AllAppsStore.DEFER_UPDATES_TEST));
                 break;
 
             case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
-                new MainThreadExecutor().execute(() ->
+                MAIN_EXECUTOR.execute(() ->
                         mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
                                 AllAppsStore.DEFER_UPDATES_TEST));
                 break;
 
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
                 try {
-                    final int deferUpdatesFlags = new MainThreadExecutor().submit(() ->
+                    final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
                             mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                             deferUpdatesFlags);
@@ -107,7 +121,49 @@
                 }
                 break;
             }
+
+            case TestProtocol.REQUEST_TOTAL_PSS_KB: {
+                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_ICON_HEIGHT: {
+                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        mDeviceProfile.allAppsCellHeightPx);
+                break;
+            }
         }
         return response;
     }
+
+    protected boolean isLauncherInitialized() {
+        final LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
+        return model.getCallback() == null || model.isModelLoaded();
+    }
 }
+
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 6ffc2d9..07ddbdc 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -66,16 +66,25 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
+    public static final String REQUEST_ICON_HEIGHT =
+            "icon-height";
+    public static final String REQUEST_HOTSEAT_TOP = "hotseat-top";
+    public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
     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_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
+    public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+    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 boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
-    public static final String NO_ALLAPPS_EVENT_TAG = "b/133867119";
-    public static final String NO_DRAG_TAG = "b/133009122";
-    public static final String NO_START_TAG = "b/132900132";
-    public static final String NO_START_TASK_TAG = "b/133765434";
-    public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
+
+    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 NO_CONTEXT_MENU = "b/141770616";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 6f53140..f40f976 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -22,16 +22,15 @@
 import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
@@ -43,7 +42,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-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;
@@ -55,9 +53,7 @@
  * TouchController for handling state changes
  */
 public abstract class AbstractStateChangeTouchController
-        implements TouchController, SwipeDetector.Listener {
-
-    private static final String TAG = "ASCTouchController";
+        implements TouchController, SingleAxisSwipeDetector.Listener {
 
     // Progress after which the transition is assumed to be a success in case user does not fling
     public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
@@ -69,10 +65,11 @@
     protected final long ATOMIC_DURATION = getAtomicDuration();
 
     protected final Launcher mLauncher;
-    protected final SwipeDetector mDetector;
-    protected final SwipeDetector.Direction mSwipeDirection;
+    protected final SingleAxisSwipeDetector mDetector;
+    protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
 
     private boolean mNoIntercept;
+    private boolean mIsLogContainerSet;
     protected int mStartContainerType;
 
     protected LauncherState mStartState;
@@ -104,9 +101,9 @@
 
     private float mAtomicComponentsStartProgress;
 
-    public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
+    public AbstractStateChangeTouchController(Launcher l, SingleAxisSwipeDetector.Direction dir) {
         mLauncher = l;
-        mDetector = new SwipeDetector(l, this, dir);
+        mDetector = new SingleAxisSwipeDetector(l, this, dir);
         mSwipeDirection = dir;
     }
 
@@ -118,9 +115,6 @@
 
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 1 " + ev);
-        }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
@@ -133,7 +127,7 @@
             boolean ignoreSlopWhenSettling = false;
 
             if (mCurrentAnimation != null) {
-                directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH;
                 ignoreSlopWhenSettling = true;
             } else {
                 directionsToDetectScroll = getSwipeDirection();
@@ -150,9 +144,6 @@
             return false;
         }
 
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 2 ");
-        }
         onControllerTouchEvent(ev);
         return mDetector.isDraggingOrSettling();
     }
@@ -161,10 +152,10 @@
         LauncherState fromState = mLauncher.getStateManager().getState();
         int swipeDirection = 0;
         if (getTargetState(fromState, true /* isDragTowardPositive */) != fromState) {
-            swipeDirection |= SwipeDetector.DIRECTION_POSITIVE;
+            swipeDirection |= SingleAxisSwipeDetector.DIRECTION_POSITIVE;
         }
         if (getTargetState(fromState, false /* isDragTowardPositive */) != fromState) {
-            swipeDirection |= SwipeDetector.DIRECTION_NEGATIVE;
+            swipeDirection |= SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
         }
         return swipeDirection;
     }
@@ -190,7 +181,7 @@
     /**
      * Returns the container that the touch started from when leaving NORMAL state.
      */
-    protected abstract int getLogContainerTypeForNormalState();
+    protected abstract int getLogContainerTypeForNormalState(MotionEvent ev);
 
     private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
         LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
@@ -240,21 +231,9 @@
 
     @Override
     public void onDragStart(boolean start) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 1 " + start);
-        }
         mStartState = mLauncher.getStateManager().getState();
-        if (mStartState == ALL_APPS) {
-            mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
-        } else if (mStartState == NORMAL) {
-            mStartContainerType = getLogContainerTypeForNormalState();
-        } else if (mStartState == OVERVIEW){
-            mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
-        }
+        mIsLogContainerSet = false;
         if (mCurrentAnimation == null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 2");
-            }
             mFromState = mStartState;
             mToState = null;
             cancelAnimationControllers();
@@ -301,6 +280,21 @@
         return true;
     }
 
+    @Override
+    public boolean onDrag(float displacement, MotionEvent ev) {
+        if (!mIsLogContainerSet) {
+            if (mStartState == ALL_APPS) {
+                mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
+            } else if (mStartState == NORMAL) {
+                mStartContainerType = getLogContainerTypeForNormalState(ev);
+            } else if (mStartState == OVERVIEW) {
+                mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+            }
+            mIsLogContainerSet = true;
+        }
+        return onDrag(displacement);
+    }
+
     protected void updateProgress(float fraction) {
         mCurrentAnimation.setPlayFraction(fraction);
         if (mAtomicComponentsController != null) {
@@ -375,10 +369,8 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragEnd");
-        }
+    public void onDragEnd(float velocity) {
+        boolean fling = mDetector.isFling(velocity);
         final int logAction = fling ? Touch.FLING : Touch.SWIPE;
 
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -413,27 +405,24 @@
                 duration = 0;
                 startProgress = 1;
             } else {
-                startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
-                duration = SwipeDetector.calculateDuration(velocity,
+                startProgress = Utilities.boundToRange(progress
+                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+                duration = BaseSwipeDetector.calculateDuration(velocity,
                         endProgress - Math.max(progress, 0)) * durationMultiplier;
             }
         } else {
             // Let the state manager know that the animation didn't go to the target state,
             // but don't cancel ourselves (we already clean up when the animation completes).
-            Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
-            mCurrentAnimation.setOnCancelRunnable(null);
-            mCurrentAnimation.dispatchOnCancel();
-            mCurrentAnimation.setOnCancelRunnable(onCancel);
+            mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
 
             endProgress = 0;
             if (progress <= 0) {
                 duration = 0;
                 startProgress = 0;
             } else {
-                startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
-                duration = SwipeDetector.calculateDuration(velocity,
+                startProgress = Utilities.boundToRange(progress
+                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+                duration = BaseSwipeDetector.calculateDuration(velocity,
                         Math.min(progress, 1) - endProgress) * durationMultiplier;
             }
         }
@@ -515,9 +504,6 @@
     }
 
     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 1");
-        }
         if (mAtomicComponentsController != null) {
             mAtomicComponentsController.getAnimationPlayer().end();
             mAtomicComponentsController = null;
@@ -531,18 +517,18 @@
             shouldGoToTargetState = !reachedTarget;
         }
         if (shouldGoToTargetState) {
-            if (targetState != mStartState) {
-                logReachedState(logAction, targetState);
-            }
-            mLauncher.getStateManager().goToState(targetState, false /* animated */);
-
-            if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-                android.util.Log.e(
-                        TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 2");
-            }
+            goToTargetState(targetState, logAction);
         }
     }
 
+    protected void goToTargetState(LauncherState targetState, int logAction) {
+        if (targetState != mStartState) {
+            logReachedState(logAction, targetState);
+        }
+        mLauncher.getStateManager().goToState(targetState, false /* animated */);
+        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
+    }
+
     private void logReachedState(int logAction, LauncherState targetState) {
         // Transition complete. log the action
         mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
@@ -563,9 +549,6 @@
     }
 
     private void cancelAnimationControllers() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "cancelAnimationControllers");
-        }
         mCurrentAnimation = null;
         cancelAtomicComponentsController();
         mDetector.finishedScrolling();
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
new file mode 100644
index 0000000..12ca5ee
--- /dev/null
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -0,0 +1,268 @@
+/*
+ * 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.touch;
+
+import static android.view.MotionEvent.INVALID_POINTER_ID;
+
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Scroll/drag/swipe gesture detector.
+ *
+ * Definition of swipe is different from android system in that this detector handles
+ * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
+ * swipe action happens.
+ *
+ * @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
+ */
+public abstract class BaseSwipeDetector {
+
+    private static final boolean DBG = false;
+    private static final String TAG = "BaseSwipeDetector";
+    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 static final PointF sTempPoint = new PointF();
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    protected final boolean mIsRtl;
+    protected final float mTouchSlop;
+    protected final float mMaxVelocity;
+
+    private int mActivePointerId = INVALID_POINTER_ID;
+    private VelocityTracker mVelocityTracker;
+    private PointF mLastDisplacement = new PointF();
+    private PointF mDisplacement = new PointF();
+    protected PointF mSubtractDisplacement = new PointF();
+    private ScrollState mState = ScrollState.IDLE;
+
+    protected boolean mIgnoreSlopWhenSettling;
+
+    private enum ScrollState {
+        IDLE,
+        DRAGGING,      // onDragStart, onDrag
+        SETTLING       // onDragEnd
+    }
+
+    protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+        mTouchSlop = config.getScaledTouchSlop();
+        mMaxVelocity = config.getScaledMaximumFlingVelocity();
+        mIsRtl = isRtl;
+    }
+
+    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() {
+        return (int) mDownPos.x;
+    }
+
+    public int getDownY() {
+        return (int) mDownPos.y;
+    }
+    /**
+     * There's no touch and there's no animation.
+     */
+    public boolean isIdleState() {
+        return mState == ScrollState.IDLE;
+    }
+
+    public boolean isSettlingState() {
+        return mState == ScrollState.SETTLING;
+    }
+
+    public boolean isDraggingState() {
+        return mState == ScrollState.DRAGGING;
+    }
+
+    public boolean isDraggingOrSettling() {
+        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+    }
+
+    public void finishedScrolling() {
+        setState(ScrollState.IDLE);
+    }
+
+    public boolean isFling(float velocity) {
+        return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+        int actionMasked = ev.getActionMasked();
+        if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+            mVelocityTracker.clear();
+        }
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+                mActivePointerId = ev.getPointerId(0);
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                mLastDisplacement.set(0, 0);
+                mDisplacement.set(0, 0);
+
+                if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+                    setState(ScrollState.DRAGGING);
+                }
+                break;
+            //case MotionEvent.ACTION_POINTER_DOWN:
+            case MotionEvent.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 MotionEvent.ACTION_MOVE:
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER_ID) {
+                    break;
+                }
+                mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x,
+                        ev.getY(pointerIndex) - mDownPos.y);
+                if (mIsRtl) {
+                    mDisplacement.x = -mDisplacement.x;
+                }
+
+                // handle state and listener calls.
+                if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) {
+                    setState(ScrollState.DRAGGING);
+                }
+                if (mState == ScrollState.DRAGGING) {
+                    reportDragging(ev);
+                }
+                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                // These are synthetic events and there is no need to update internal values.
+                if (mState == ScrollState.DRAGGING) {
+                    setState(ScrollState.SETTLING);
+                }
+                mVelocityTracker.recycle();
+                mVelocityTracker = null;
+                break;
+            default:
+                break;
+        }
+        return true;
+    }
+
+    //------------------- 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 void initializeDragging() {
+        if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
+            mSubtractDisplacement.set(0, 0);
+        } else {
+            mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
+            mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
+        }
+    }
+
+    protected abstract boolean shouldScrollStart(PointF displacement);
+
+    private void reportDragStart(boolean recatch) {
+        reportDragStartInternal(recatch);
+        if (DBG) {
+            Log.d(TAG, "onDragStart recatch:" + recatch);
+        }
+    }
+
+    protected abstract void reportDragStartInternal(boolean recatch);
+
+    private void reportDragging(MotionEvent event) {
+        if (mDisplacement != mLastDisplacement) {
+            if (DBG) {
+                Log.d(TAG, String.format("onDrag disp=%s", mDisplacement));
+            }
+
+            mLastDisplacement.set(mDisplacement);
+            sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x,
+                    mDisplacement.y - mSubtractDisplacement.y);
+            reportDraggingInternal(sTempPoint, event);
+        }
+    }
+
+    protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event);
+
+    private void reportDragEnd() {
+        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
+        PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000,
+                mVelocityTracker.getYVelocity() / 1000);
+        if (mIsRtl) {
+            velocity.x = -velocity.x;
+        }
+        if (DBG) {
+            Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s",
+                    mDisplacement, velocity));
+        }
+
+        reportDragEndInternal(velocity);
+    }
+
+    protected abstract void reportDragEndInternal(PointF velocity);
+}
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 0000000..944391e
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+    public static final int DIRECTION_UP = 1 << 0;
+    // Note that this will track left instead of right in RTL.
+    public static final int DIRECTION_RIGHT = 1 << 1;
+    public static final int DIRECTION_DOWN = 1 << 2;
+    // Note that this will track right instead of left in RTL.
+    public static final int DIRECTION_LEFT = 1 << 3;
+
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
+
+    private int mScrollDirections;
+
+    public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+        this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+    }
+
+    @VisibleForTesting
+    protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            boolean isRtl) {
+        super(config, isRtl);
+        mListener = l;
+    }
+
+    public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+        mScrollDirections = scrollDirectionFlags;
+        mIgnoreSlopWhenSettling = ignoreSlop;
+    }
+
+    @Override
+    protected boolean shouldScrollStart(PointF displacement) {
+        // Check if the client is interested in scroll in current direction.
+        boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+                && displacement.y <= -mTouchSlop;
+        boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+                && displacement.x >= mTouchSlop;
+        boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+                && displacement.y >= mTouchSlop;
+        boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+                && displacement.x <= -mTouchSlop;
+        return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+    }
+
+    @Override
+    protected void reportDragStartInternal(boolean recatch) {
+        mListener.onDragStart(!recatch);
+    }
+
+    @Override
+    protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+        mListener.onDrag(displacement, event);
+    }
+
+    @Override
+    protected void reportDragEndInternal(PointF velocity) {
+        mListener.onDragEnd(velocity);
+    }
+
+    /** Listener to receive updates on the swipe. */
+    public interface Listener {
+        /** @param start whether this was the original drag start, as opposed to a recatch. */
+        void onDragStart(boolean start);
+
+        boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+        void onDragEnd(PointF velocity);
+    }
+}
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 85f763d..03493a5 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -25,9 +25,15 @@
 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;
 import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Toast;
@@ -43,11 +49,12 @@
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 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.AppWidgetManagerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.widget.PendingAppWidgetHostView;
@@ -58,6 +65,8 @@
  */
 public class ItemClickHandler {
 
+    private static final String TAG = ItemClickHandler.class.getSimpleName();
+
     /**
      * Instance used for click handling on items
      */
@@ -68,28 +77,12 @@
     }
 
     private static void onClick(View v, String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "onClick 1");
-        }
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
-        if (v.getWindowToken() == null) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 2");
-            }
-            return;
-        }
+        if (v.getWindowToken() == null) return;
 
         Launcher launcher = Launcher.getLauncher(v.getContext());
-        if (!launcher.getWorkspace().isFinishedSwitchingState()) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 3");
-            }
-            return;
-        }
+        if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
 
         Object tag = v.getTag();
         if (tag instanceof WorkspaceItemInfo) {
@@ -99,10 +92,6 @@
                 onClickFolderIcon(v);
             }
         } else if (tag instanceof AppInfo) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 4");
-            }
             startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
                     sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
         } else if (tag instanceof LauncherAppWidgetInfo) {
@@ -166,6 +155,8 @@
             startMarketIntentForPackage(v, launcher, packageName);
             return;
         }
+        UserHandle user = v.getTag() instanceof ItemInfo
+                ? ((ItemInfo) v.getTag()).user : Process.myUserHandle();
         new AlertDialog.Builder(launcher)
                 .setTitle(R.string.abandoned_promises_title)
                 .setMessage(R.string.abandoned_promise_explanation)
@@ -173,12 +164,28 @@
                         (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
                 .setNeutralButton(R.string.abandoned_clean_this,
                         (d, i) -> launcher.getWorkspace()
-                                .removeAbandonedPromise(packageName, Process.myUserHandle()))
+                                .removeAbandonedPromise(packageName, user))
                 .create().show();
     }
 
     private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
         ItemInfo item = (ItemInfo) v.getTag();
+        if (Utilities.ATLEAST_Q) {
+            PackageInstallerCompat pkgInstaller = PackageInstallerCompat.getInstance(launcher);
+            SessionInfo sessionInfo = pkgInstaller.getActiveSessionInfo(item.user, packageName);
+            if (sessionInfo != null) {
+                LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
+                try {
+                    launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
+                            launcher.getActivityLaunchOptionsAsBundle(v));
+                    return;
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
+                }
+            }
+        }
+
+        // Fallback to using custom market intent.
         Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
         launcher.startActivitySafely(v, intent, item, null);
     }
@@ -234,10 +241,6 @@
 
     private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startAppShortcutOrInfoActivity");
-        }
         Intent intent;
         if (item instanceof PromiseAppInfo) {
             PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index babbcdd..86d2b39 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -17,10 +17,12 @@
 
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
+
 import static com.android.launcher3.LauncherState.ALL_APPS;
 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;
 
@@ -32,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.
@@ -60,7 +65,7 @@
         if (info.container >= 0) {
             Folder folder = Folder.getOpen(launcher);
             if (folder != null) {
-                if (!folder.getItemsInReadingOrder().contains(v)) {
+                if (!folder.getIconsInReadingOrder().contains(v)) {
                     folder.close(true);
                 } else {
                     folder.startDrag(v, dragOptions);
@@ -74,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/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
new file mode 100644
index 0000000..f2ebc45
--- /dev/null
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -0,0 +1,190 @@
+/*
+ * 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.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
+ */
+public class SingleAxisSwipeDetector extends BaseSwipeDetector {
+
+    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;
+
+    public static final Direction VERTICAL = new Direction() {
+
+        @Override
+        boolean isPositive(float displacement) {
+            // Up
+            return displacement < 0;
+        }
+
+        @Override
+        boolean isNegative(float displacement) {
+            // Down
+            return displacement > 0;
+        }
+
+        @Override
+        float extractDirection(PointF direction) {
+            return direction.y;
+        }
+
+        @Override
+        boolean canScrollStart(PointF displacement, float touchSlop) {
+            return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+        }
+
+    };
+
+    public static final Direction HORIZONTAL = new Direction() {
+
+        @Override
+        boolean isPositive(float displacement) {
+            // Right
+            return displacement > 0;
+        }
+
+        @Override
+        boolean isNegative(float displacement) {
+            // Left
+            return displacement < 0;
+        }
+
+        @Override
+        float extractDirection(PointF direction) {
+            return direction.x;
+        }
+
+        @Override
+        boolean canScrollStart(PointF displacement, float touchSlop) {
+            return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+        }
+    };
+
+    private final Direction mDir;
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
+
+    private int mScrollDirections;
+
+    public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
+            @NonNull Direction dir) {
+        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+    }
+
+    @VisibleForTesting
+    protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            @NonNull Direction dir, boolean isRtl) {
+        super(config, isRtl);
+        mListener = l;
+        mDir = dir;
+    }
+
+    public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+        mScrollDirections = scrollDirectionFlags;
+        mIgnoreSlopWhenSettling = ignoreSlop;
+    }
+
+    public int getScrollDirections() {
+        return mScrollDirections;
+    }
+
+    /**
+     * 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(mDir.extractDirection(mSubtractDisplacement));
+    }
+
+    @Override
+    protected boolean shouldScrollStart(PointF displacement) {
+        // Reject cases where the angle or slop condition is not met.
+        if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+            return false;
+        }
+
+        // Check if the client is interested in scroll in current direction.
+        float displacementComponent = mDir.extractDirection(displacement);
+        return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent);
+    }
+
+    private boolean canScrollNegative(float displacement) {
+        return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement);
+    }
+
+    private boolean canScrollPositive(float displacement) {
+        return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement);
+    }
+
+    @Override
+    protected void reportDragStartInternal(boolean recatch) {
+        mListener.onDragStart(!recatch);
+    }
+
+    @Override
+    protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+        mListener.onDrag(mDir.extractDirection(displacement), event);
+    }
+
+    @Override
+    protected void reportDragEndInternal(PointF velocity) {
+        float velocityComponent = mDir.extractDirection(velocity);
+        mListener.onDragEnd(velocityComponent);
+    }
+
+    /** Listener to receive updates on the swipe. */
+    public interface Listener {
+        /** @param start whether this was the original drag start, as opposed to a recatch. */
+        void onDragStart(boolean start);
+
+        // TODO remove
+        boolean onDrag(float displacement);
+
+        default boolean onDrag(float displacement, MotionEvent event) {
+            return onDrag(displacement);
+        }
+
+        void onDragEnd(float velocity);
+    }
+
+    public abstract static class Direction {
+
+        abstract boolean isPositive(float displacement);
+
+        abstract boolean isNegative(float displacement);
+
+        /** Returns the part of the given {@link PointF} that is relevant to this direction. */
+        abstract float extractDirection(PointF point);
+
+        /** Reject cases where the angle or slop condition is not met. */
+        abstract boolean canScrollStart(PointF displacement, float touchSlop);
+
+    }
+}
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
deleted file mode 100644
index 3d45404..0000000
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ /dev/null
@@ -1,424 +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.touch;
-
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-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;
-
-/**
- * One dimensional scroll/drag/swipe gesture detector.
- *
- * Definition of swipe is different from android system in that this detector handles
- * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before
- * swipe action happens
- */
-public class SwipeDetector {
-
-    private static final boolean DBG = false;
-    private static final String TAG = "SwipeDetector";
-
-    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
-        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
-            return ev.getY(pointerIndex) - refPoint.y;
-        }
-
-        @Override
-        float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
-            return Math.abs(ev.getX(pointerIndex) - downPos.x);
-        }
-
-        @Override
-        float getVelocity(VelocityTracker tracker, boolean isRtl) {
-            return tracker.getYVelocity();
-        }
-
-        @Override
-        boolean isPositive(float displacement) {
-            // Up
-            return displacement < 0;
-        }
-
-        @Override
-        boolean isNegative(float displacement) {
-            // Down
-            return displacement > 0;
-        }
-    };
-
-    public static final Direction HORIZONTAL = new Direction() {
-
-        @Override
-        float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint, boolean isRtl) {
-            float displacement = ev.getX(pointerIndex) - refPoint.x;
-            if (isRtl) {
-                displacement = -displacement;
-            }
-            return displacement;
-        }
-
-        @Override
-        float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) {
-            return Math.abs(ev.getY(pointerIndex) - downPos.y);
-        }
-
-        @Override
-        float getVelocity(VelocityTracker tracker, boolean isRtl) {
-            float velocity = tracker.getXVelocity();
-            if (isRtl) {
-                velocity = -velocity;
-            }
-            return velocity;
-        }
-
-        @Override
-        boolean isPositive(float displacement) {
-            // Right
-            return displacement > 0;
-        }
-
-        @Override
-        boolean isNegative(float displacement) {
-            // Left
-            return displacement < 0;
-        }
-    };
-
-    //------------------- 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 (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- start: " + 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) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- 1: " + newState);
-                }
-                reportDragStart(false /* recatch */);
-            } else if (mState == ScrollState.SETTLING) {
-                reportDragStart(true /* recatch */);
-            }
-        }
-        if (newState == ScrollState.SETTLING) {
-            reportDragEnd();
-        }
-
-        mState = newState;
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                    "setState: " + newState + " @ " + android.util.Log.getStackTraceString(
-                            new Throwable()));
-        }
-    }
-
-    public boolean isDraggingOrSettling() {
-        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
-    }
-
-    public int getDownX() {
-        return (int) mDownPos.x;
-    }
-
-    public int getDownY() {
-        return (int) mDownPos.y;
-    }
-    /**
-     * There's no touch and there's no animation.
-     */
-    public boolean isIdleState() {
-        return mState == ScrollState.IDLE;
-    }
-
-    public boolean isSettlingState() {
-        return mState == ScrollState.SETTLING;
-    }
-
-    public boolean isDraggingState() {
-        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 void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
-        mScrollConditions = scrollDirectionFlags;
-        mIgnoreSlopWhenSettling = ignoreSlop;
-    }
-
-    public int getScrollDirections() {
-        return mScrollConditions;
-    }
-
-    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.
-        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
-                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
-            return true;
-        }
-        return false;
-    }
-
-    public boolean onTouchEvent(MotionEvent ev) {
-        int actionMasked = ev.getActionMasked();
-        if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
-            mVelocityTracker.clear();
-        }
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(ev);
-
-        switch (actionMasked) {
-            case MotionEvent.ACTION_DOWN:
-                mActivePointerId = ev.getPointerId(0);
-                mDownPos.set(ev.getX(), ev.getY());
-                mLastPos.set(mDownPos);
-                mLastDisplacement = 0;
-                mDisplacement = 0;
-
-                if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
-                    setState(ScrollState.DRAGGING);
-                }
-                break;
-            //case MotionEvent.ACTION_POINTER_DOWN:
-            case MotionEvent.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 MotionEvent.ACTION_MOVE:
-                int pointerIndex = ev.findPointerIndex(mActivePointerId);
-                if (pointerIndex == INVALID_POINTER_ID) {
-                    break;
-                }
-                mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 1");
-                }
-
-                // handle state and listener calls.
-                if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
-                    if (TestProtocol.sDebugTracing) {
-                        Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 2");
-                    }
-                    setState(ScrollState.DRAGGING);
-                }
-                if (mState == ScrollState.DRAGGING) {
-                    reportDragging(ev);
-                }
-                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                // These are synthetic events and there is no need to update internal values.
-                if (mState == ScrollState.DRAGGING) {
-                    setState(ScrollState.SETTLING);
-                }
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
-                break;
-            default:
-                break;
-        }
-        return true;
-    }
-
-    public void finishedScrolling() {
-        setState(ScrollState.IDLE);
-    }
-
-    private boolean reportDragStart(boolean recatch) {
-        mListener.onDragStart(!recatch);
-        if (DBG) {
-            Log.d(TAG, "onDragStart recatch:" + recatch);
-        }
-        return true;
-    }
-
-    private void initializeDragging() {
-        if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
-            mSubtractDisplacement = 0;
-        }
-        if (mDisplacement > 0) {
-            mSubtractDisplacement = mTouchSlop;
-        } else {
-            mSubtractDisplacement = -mTouchSlop;
-        }
-    }
-
-    /**
-     * 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) {
-        if (mDisplacement != mLastDisplacement) {
-            if (DBG) {
-                Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
-            }
-
-            mLastDisplacement = mDisplacement;
-            return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
-        }
-        return true;
-    }
-
-    private void reportDragEnd() {
-        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
-        float velocity = mDir.getVelocity(mVelocityTracker, mIsRtl) / 1000;
-        if (DBG) {
-            Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f",
-                    mDisplacement, velocity));
-        }
-
-        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));
-        }
-        return duration;
-    }
-}
diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java
deleted file mode 100644
index 3fcda90..0000000
--- a/src/com/android/launcher3/touch/TouchEventTranslator.java
+++ /dev/null
@@ -1,283 +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.launcher3.touch;
-
-import android.graphics.PointF;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
-
-import java.util.function.Consumer;
-
-/**
- * To minimize the size of the MotionEvent, historic events are not copied and passed via the
- * listener.
- */
-public class TouchEventTranslator {
-
-    private static final String TAG = "TouchEventTranslator";
-    private static final boolean DEBUG = false;
-
-    private class DownState {
-        long timeStamp;
-        float downX;
-        float downY;
-        public DownState(long timeStamp, float downX, float downY) {
-            this.timeStamp = timeStamp;
-            this.downX = downX;
-            this.downY = downY;
-        }
-    };
-    private final DownState ZERO = new DownState(0, 0f, 0f);
-
-    private final Consumer<MotionEvent> mListener;
-
-    private final SparseArray<DownState> mDownEvents;
-    private final SparseArray<PointF> mFingers;
-
-    private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
-
-    public TouchEventTranslator(Consumer<MotionEvent> listener) {
-        mDownEvents = new SparseArray<>();
-        mFingers = new SparseArray<>();
-        mCache = new SparseArray<>();
-
-        mListener = listener;
-    }
-
-    public void reset() {
-        mDownEvents.clear();
-        mFingers.clear();
-    }
-
-    public float getDownX() {
-        return mDownEvents.get(0).downX;
-    }
-
-    public float getDownY() {
-        return mDownEvents.get(0).downY;
-    }
-
-    public void setDownParameters(int idx, MotionEvent e) {
-        DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
-        mDownEvents.append(idx, ev);
-    }
-
-    public void dispatchDownEvents(MotionEvent ev) {
-        for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
-            int pid = ev.getPointerId(i);
-            put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
-        }
-    }
-
-    public void processMotionEvent(MotionEvent ev) {
-        if (DEBUG) {
-            printSamples(TAG + " processMotionEvent", ev);
-        }
-        int index = ev.getActionIndex();
-        float x = ev.getX(index);
-        float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_POINTER_DOWN:
-                int pid = ev.getPointerId(index);
-                if(mFingers.get(pid, null) != null) {
-                    for(int i=0; i < ev.getPointerCount(); i++) {
-                        pid = ev.getPointerId(i);
-                        position(pid, x, y);
-                    }
-                    generateEvent(ev.getAction(), ev);
-                } else {
-                    put(pid, index, x, y, ev);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                for(int i=0; i < ev.getPointerCount(); i++) {
-                    pid = ev.getPointerId(i);
-                    position(pid, x, y);
-                }
-                generateEvent(ev.getAction(), ev);
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-            case MotionEvent.ACTION_UP:
-                pid = ev.getPointerId(index);
-                lift(pid, index, x, y, ev);
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                cancel(ev);
-                break;
-            default:
-                Log.v(TAG, "Didn't process ");
-                printSamples(TAG, ev);
-
-        }
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
-        return put(id, index, x, y, ev.getEventTime(), ev);
-    }
-
-    private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
-        checkFingerExistence(id, false);
-        boolean isInitialDown = (mFingers.size() == 0);
-
-        mFingers.put(id, new PointF(x, y));
-        int n = mFingers.size();
-
-        if (mCache.get(n) == null) {
-            PointerProperties[] properties = new PointerProperties[n];
-            PointerCoords[] coords = new PointerCoords[n];
-            for (int i = 0; i < n; i++) {
-                properties[i] = new PointerProperties();
-                coords[i] = new PointerCoords();
-            }
-            mCache.put(n, new Pair(properties, coords));
-        }
-
-        int action;
-        if (isInitialDown) {
-            action = MotionEvent.ACTION_DOWN;
-        } else {
-            action = MotionEvent.ACTION_POINTER_DOWN;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ms, ev);
-        return this;
-    }
-
-    public TouchEventTranslator position(int id, float x, float y) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        boolean isFinalUp = (mFingers.size() == 1);
-        int action;
-        if (isFinalUp) {
-            action = MotionEvent.ACTION_UP;
-        } else {
-            action = MotionEvent.ACTION_POINTER_UP;
-            // Set the id of the changed pointer.
-            action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
-        }
-        generateEvent(action, ev);
-        mFingers.remove(id);
-        return this;
-    }
-
-    private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
-        checkFingerExistence(id, true);
-        mFingers.get(id).set(x, y);
-        return lift(id, index, ev);
-    }
-
-    public TouchEventTranslator cancel(MotionEvent ev) {
-        generateEvent(MotionEvent.ACTION_CANCEL, ev);
-        mFingers.clear();
-        return this;
-    }
-
-    private void checkFingerExistence(int id, boolean shouldExist) {
-        if (shouldExist != (mFingers.get(id, null) != null)) {
-            throw new IllegalArgumentException(
-                    shouldExist ? "Finger does not exist" : "Finger already exists");
-        }
-    }
-
-
-    /**
-     * Used to debug MotionEvents being sent/received.
-     */
-    public void printSamples(String msg, MotionEvent ev) {
-        System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
-        final int pointerCount = ev.getPointerCount();
-        System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
-        System.out.printf(" t=%d:", ev.getEventTime());
-        for (int p = 0; p < pointerCount; p++) {
-            System.out.printf("  id=%d: (%f,%f)",
-                    ev.getPointerId(p), ev.getX(p), ev.getY(p));
-        }
-        System.out.println();
-    }
-
-    private void generateEvent(int action, MotionEvent ev) {
-        generateEvent(action, ev.getEventTime(), ev);
-    }
-
-    private void generateEvent(int action, long ms, MotionEvent ev) {
-        Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
-        MotionEvent event = MotionEvent.obtain(
-                mDownEvents.get(0).timeStamp,
-                ms,
-                action,
-                state.first.length,
-                state.first,
-                state.second,
-                ev.getMetaState(),
-                ev.getButtonState() /* buttonState */,
-                ev.getXPrecision() /* xPrecision */,
-                ev.getYPrecision() /* yPrecision */,
-                ev.getDeviceId(),
-                ev.getEdgeFlags(),
-                ev.getSource(),
-                ev.getFlags() /* flags */);
-        if (DEBUG) {
-            printSamples(TAG + " generateEvent", event);
-        }
-        if (event.getPointerId(event.getActionIndex()) < 0) {
-            printSamples(TAG + "generateEvent", event);
-            throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
-        }
-        mListener.accept(event);
-        event.recycle();
-    }
-
-    /**
-     * Returns the description of the fingers' state expected by MotionEvent.
-     */
-    private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
-        int nFingers = mFingers.size();
-
-        Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
-        PointerProperties[] properties = result.first;
-        PointerCoords[] coordinates = result.second;
-
-        int index = 0;
-        for (int i = 0; i < mFingers.size(); i++) {
-            int id = mFingers.keyAt(i);
-            PointF location = mFingers.get(id);
-
-            PointerProperties property = properties[i];
-            property.id = id;
-            property.toolType = MotionEvent.TOOL_TYPE_FINGER;
-            properties[index] = property;
-
-            PointerCoords coordinate = coordinates[i];
-            coordinate.x = location.x;
-            coordinate.y = location.y;
-            coordinate.pressure = 1.0f;
-            coordinates[index] = coordinate;
-
-            index++;
-        }
-        return mCache.get(nFingers);
-    }
-}
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 12d35e9..0f81520 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -16,20 +16,15 @@
  * limitations under the License.
  */
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 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.os.Handler;
 import android.util.Log;
-import android.view.Display;
-import android.view.WindowManager;
-
-import com.android.launcher3.MainThreadExecutor;
 
 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, new Handler(UiThreadHelper.getBackgroundLooper()));
     }
 
     @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));
@@ -122,18 +106,15 @@
         if (mCallback != null) {
             Consumer<Context> callback = mCallback;
             mCallback = null;
-            new MainThreadExecutor().execute(() -> callback.accept(mContext));
+            MAIN_EXECUTOR.execute(() -> callback.accept(mContext));
         }
     }
 
-    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
new file mode 100644
index 0000000..8529d50
--- /dev/null
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -0,0 +1,174 @@
+/*
+ * 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.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+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;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to cache properties of default display to avoid a system RPC on every call.
+ */
+public class DefaultDisplay implements DisplayListener {
+
+    public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
+            new MainThreadInitializedObject<>(DefaultDisplay::new);
+
+    private static final String TAG = "DefaultDisplay";
+
+    public static final int CHANGE_SIZE = 1 << 0;
+    public static final int CHANGE_ROTATION = 1 << 1;
+    public static final int CHANGE_FRAME_DELAY = 1 << 2;
+
+    private final Context mContext;
+    private final int mId;
+    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+    private final Handler mChangeHandler;
+    private Info mInfo;
+
+    private DefaultDisplay(Context context) {
+        mContext = context;
+        mInfo = new Info(context);
+        mId = mInfo.id;
+        mChangeHandler = new Handler(this::onChange);
+
+        context.getSystemService(DisplayManager.class)
+                .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+    }
+
+    @Override
+    public final void onDisplayAdded(int displayId) {  }
+
+    @Override
+    public final void onDisplayRemoved(int displayId) { }
+
+    @Override
+    public final void onDisplayChanged(int displayId) {
+        if (displayId != mId) {
+            return;
+        }
+
+        Info oldInfo = mInfo;
+        Info info = new Info(mContext);
+
+        int change = 0;
+        if (info.hasDifferentSize(oldInfo)) {
+            change |= CHANGE_SIZE;
+        }
+        if (oldInfo.rotation != info.rotation) {
+            change |= CHANGE_ROTATION;
+        }
+        if (info.singleFrameMs != oldInfo.singleFrameMs) {
+            change |= CHANGE_FRAME_DELAY;
+        }
+
+        if (change != 0) {
+            mInfo = info;
+            mChangeHandler.sendEmptyMessage(change);
+        }
+    }
+
+    public static int getSingleFrameMs(Context context) {
+        return INSTANCE.get(context).getInfo().singleFrameMs;
+    }
+
+    public Info getInfo() {
+        return mInfo;
+    }
+
+    public void addChangeListener(DisplayInfoChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeChangeListener(DisplayInfoChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private boolean onChange(Message msg) {
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
+        }
+        return true;
+    }
+
+    public static class Info {
+
+        public final int id;
+        public final int rotation;
+        public final int singleFrameMs;
+
+        public final Point realSize;
+        public final Point smallestSize;
+        public final Point largestSize;
+
+        public final DisplayMetrics metrics;
+
+        private Info(Context context) {
+            Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+
+            id = display.getDisplayId();
+            rotation = display.getRotation();
+
+            float refreshRate = display.getRefreshRate();
+            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
+
+            realSize = new Point();
+            smallestSize = new Point();
+            largestSize = new Point();
+            display.getRealSize(realSize);
+            display.getCurrentSizeRange(smallestSize, largestSize);
+
+            metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+        }
+
+        private boolean hasDifferentSize(Info info) {
+            if (!realSize.equals(info.realSize)
+                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
+                Log.d(TAG, String.format("Display size changed from %s to %s",
+                        info.realSize, realSize));
+                return true;
+            }
+
+            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
+                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+                        smallestSize, largestSize, info.smallestSize, info.largestSize));
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Interface for listening for display changes
+     */
+    public interface DisplayInfoChangeListener {
+
+        void onDisplayInfoChanged(Info info, int flags);
+    }
+}
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
new file mode 100644
index 0000000..4d5ee49
--- /dev/null
+++ b/src/com/android/launcher3/util/Executors.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 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.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Various different executors used in Launcher
+ */
+public class Executors {
+
+    // These values are same as that in {@link AsyncTask}.
+    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+    private static final int KEEP_ALIVE = 1;
+
+    /**
+     * An {@link Executor} to be used with async task with no limit on the queue size.
+     */
+    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
+            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
+
+    /**
+     * Returns the executor for running tasks on the main thread.
+     */
+    public static final LooperExecutor MAIN_EXECUTOR =
+            new LooperExecutor(Looper.getMainLooper());
+
+    /**
+     * A background executor for using time sensitive actions where user is waiting for response.
+     */
+    public static final LooperExecutor UI_HELPER_EXECUTOR =
+            new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper"));
+
+    /**
+     * Utility method to get a started handler thread statically
+     */
+    public static Looper createAndStartNewLooper(String name) {
+        return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    /**
+     * Utility method to get a started handler thread statically with the provided priority
+     */
+    public static Looper createAndStartNewLooper(String name, int priority) {
+        HandlerThread thread = new HandlerThread(name, priority);
+        thread.start();
+        return thread.getLooper();
+    }
+
+    /**
+     * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with
+     * foreground priority.
+     * Think before using
+     */
+    public static Looper createAndStartNewForegroundLooper(String name) {
+        return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND);
+    }
+
+    /**
+     * Executor used for running Launcher model related tasks (eg loading icons or updated db)
+     */
+    public static final LooperExecutor MODEL_EXECUTOR =
+            new LooperExecutor(createAndStartNewLooper("launcher-loader"));
+}
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index f95f74d..4a4a5ca 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -17,10 +17,13 @@
 package com.android.launcher3.util;
 
 import android.content.Context;
+import android.util.Log;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -35,6 +38,7 @@
 public class IOUtils {
 
     private static final int BUF_SIZE = 0x1000; // 4K
+    private static final String TAG = "IOUtils";
 
     public static byte[] toByteArray(File file) throws IOException {
         try (InputStream in = new FileInputStream(file)) {
@@ -77,4 +81,16 @@
         }
         return file.getAbsolutePath();
     }
+
+    public static void closeSilently(Closeable c) {
+        if (c != null) {
+            try {
+                c.close();
+            } catch (IOException e) {
+                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    Log.d(TAG, "Error closing", e);
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index d2a551f..7252f7a 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.util;
 
 import java.util.Arrays;
+import java.util.StringTokenizer;
 
 /**
  * Copy of the platform hidden implementation of android.util.IntArray.
@@ -248,6 +249,17 @@
         return b.toString();
     }
 
+    public static IntArray fromConcatString(String concatString) {
+        StringTokenizer tokenizer = new StringTokenizer(concatString, ",");
+        int[] array = new int[tokenizer.countTokens()];
+        int count = 0;
+        while (tokenizer.hasMoreTokens()) {
+            array[count] = Integer.parseInt(tokenizer.nextToken().trim());
+            count++;
+        }
+        return new IntArray(array, array.length);
+    }
+
     /**
      * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
      *
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
index cc07469..8ac600f 100644
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ b/src/com/android/launcher3/util/LooperExecutor.java
@@ -16,7 +16,9 @@
 package com.android.launcher3.util;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Process;
 
 import java.util.List;
 import java.util.concurrent.AbstractExecutorService;
@@ -47,6 +49,13 @@
     }
 
     /**
+     * Same as execute, but never runs the action inline.
+     */
+    public void post(Runnable runnable) {
+        mHandler.post(runnable);
+    }
+
+    /**
      * Not supported and throws an exception when used.
      */
     @Override
@@ -79,7 +88,31 @@
      */
     @Override
     @Deprecated
-    public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+    public boolean awaitTermination(long l, TimeUnit timeUnit) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * Returns the thread for this executor
+     */
+    public Thread getThread() {
+        return mHandler.getLooper().getThread();
+    }
+
+    /**
+     * Returns the looper for this executor
+     */
+    public Looper getLooper() {
+        return mHandler.getLooper();
+    }
+
+    /**
+     * Set the priority of a thread, based on Linux priorities.
+     * @param priority Linux priority level, from -20 for highest scheduling priority
+     *                to 19 for lowest scheduling priority.
+     * @see Process#setThreadPriority(int, int)
+     */
+    public void setThreadPriority(int priority) {
+        Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
+    }
 }
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 2ee0328..fe9c2c4 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -15,15 +15,17 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.os.Looper;
 
-import com.android.launcher3.MainThreadExecutor;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.concurrent.ExecutionException;
 
-import androidx.annotation.VisibleForTesting;
-
 /**
  * Utility class for defining singletons which are initiated on main thread.
  */
@@ -42,7 +44,7 @@
                 mValue = mProvider.get(context.getApplicationContext());
             } else {
                 try {
-                    return new MainThreadExecutor().submit(() -> get(context)).get();
+                    return MAIN_EXECUTOR.submit(() -> get(context)).get();
                 } catch (InterruptedException|ExecutionException e) {
                     throw new RuntimeException(e);
                 }
@@ -60,6 +62,14 @@
         mValue = value;
     }
 
+    /**
+     * Initializes a provider based on resource overrides
+     */
+    public static <T extends ResourceBasedOverride> MainThreadInitializedObject<T> forOverride(
+            Class<T> clazz, int resourceId) {
+        return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
+    }
+
     public interface ObjectProvider<T> {
 
         T get(Context context);
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7d3a941..e97adb5 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -24,9 +24,11 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
@@ -35,6 +37,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.widget.Toast;
 
 import com.android.launcher3.AppInfo;
@@ -220,4 +223,76 @@
         packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL);
         return packageFilter;
     }
+
+    public static boolean isSystemApp(Context context, Intent intent) {
+        PackageManager pm = context.getPackageManager();
+        ComponentName cn = intent.getComponent();
+        String packageName = null;
+        if (cn == null) {
+            ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if ((info != null) && (info.activityInfo != null)) {
+                packageName = info.activityInfo.packageName;
+            }
+        } else {
+            packageName = cn.getPackageName();
+        }
+        if (packageName == null) {
+            packageName = intent.getPackage();
+        }
+        if (packageName != null) {
+            try {
+                PackageInfo info = pm.getPackageInfo(packageName, 0);
+                return (info != null) && (info.applicationInfo != null) &&
+                        ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+            } catch (NameNotFoundException e) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Finds a system apk which had a broadcast receiver listening to a particular action.
+     * @param action intent action used to find the apk
+     * @return a pair of apk package name and the resources.
+     */
+    public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
+        final Intent intent = new Intent(action);
+        for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
+            if (info.activityInfo != null &&
+                    (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                final String packageName = info.activityInfo.packageName;
+                try {
+                    final Resources res = pm.getResourcesForApplication(packageName);
+                    return Pair.create(packageName, res);
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Failed to find resources for " + packageName);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the intent is a valid launch intent for a launcher activity of an app.
+     * This is used to identify shortcuts which are different from the ones exposed by the
+     * applications' manifest file.
+     *
+     * @param launchIntent The intent that will be launched when the shortcut is clicked.
+     */
+    public static boolean isLauncherAppTarget(Intent launchIntent) {
+        if (launchIntent != null
+                && Intent.ACTION_MAIN.equals(launchIntent.getAction())
+                && launchIntent.getComponent() != null
+                && launchIntent.getCategories() != null
+                && launchIntent.getCategories().size() == 1
+                && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+                && TextUtils.isEmpty(launchIntent.getDataString())) {
+            // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE.
+            Bundle extras = launchIntent.getExtras();
+            return extras == null || extras.keySet().isEmpty();
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java
index 1ce2822..f243ca6 100644
--- a/src/com/android/launcher3/util/PackageUserKey.java
+++ b/src/com/android/launcher3/util/PackageUserKey.java
@@ -3,8 +3,9 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
 
 import java.util.Arrays;
 
@@ -15,7 +16,9 @@
     public UserHandle mUser;
     private int mHashCode;
 
+    @Nullable
     public static PackageUserKey fromItemInfo(ItemInfo info) {
+        if (info.getTargetComponent() == null) return null;
         return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user);
     }
 
@@ -27,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});
@@ -38,7 +41,8 @@
      * @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not.
      */
     public boolean updateFromItemInfo(ItemInfo info) {
-        if (DeepShortcutManager.supportsShortcuts(info)) {
+        if (info.getTargetComponent() == null) return false;
+        if (ShortcutUtil.supportsShortcuts(info)) {
             update(info.getTargetComponent().getPackageName(), info.user);
             return true;
         }
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index 7ab0d31..ed66422 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.os.Looper;
 
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.config.FeatureFlags;
 
 /**
@@ -33,7 +34,7 @@
     }
 
     public static void assertWorkerThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
             throw new IllegalStateException();
         }
     }
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/util/SafeCloseable.java
new file mode 100644
index 0000000..ba8ee04
--- /dev/null
+++ b/src/com/android/launcher3/util/SafeCloseable.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Extension of closeable which does not throw an exception
+ */
+public interface SafeCloseable extends AutoCloseable {
+
+    @Override
+    void close();
+}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
new file mode 100644
index 0000000..af99713
--- /dev/null
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.launcher3.ItemInfo;
+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.shortcuts.ShortcutKey;
+
+public class ShortcutUtil {
+    /**
+     * Returns true when we should show shortcut menu for the item.
+     */
+    public static boolean supportsShortcuts(ItemInfo info) {
+        return isActive(info) && (isApp(info) || isPinnedShortcut(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);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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 isActive(ItemInfo info) {
+        boolean isLoading = info instanceof WorkspaceItemInfo
+                && ((WorkspaceItemInfo) info).hasPromiseIconUi();
+        return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS;
+    }
+
+    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;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index cc442f9..f8d1632 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -15,14 +15,13 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.app.Activity;
 import android.content.Context;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.view.inputmethod.InputMethodManager;
 
 /**
@@ -30,25 +29,15 @@
  */
 public class UiThreadHelper {
 
-    private static HandlerThread sHandlerThread;
     private static Handler sHandler;
 
     private static final int MSG_HIDE_KEYBOARD = 1;
     private static final int MSG_SET_ORIENTATION = 2;
     private static final int MSG_RUN_COMMAND = 3;
 
-    public static Looper getBackgroundLooper() {
-        if (sHandlerThread == null) {
-            sHandlerThread =
-                    new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
-            sHandlerThread.start();
-        }
-        return sHandlerThread.getLooper();
-    }
-
     private static Handler getHandler(Context context) {
         if (sHandler == null) {
-            sHandler = new Handler(getBackgroundLooper(),
+            sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(),
                     new UiCallbacks(context.getApplicationContext()));
         }
         return sHandler;
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
new file mode 100644
index 0000000..04741a1
--- /dev/null
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.os.VibrationEffect.createPredefined;
+import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Build;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+
+/**
+ * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+public class VibratorWrapper {
+
+    public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
+            new MainThreadInitializedObject<>(VibratorWrapper::new);
+
+    private static final VibrationEffect EFFECT_CLICK =
+            createPredefined(VibrationEffect.EFFECT_CLICK);
+
+    /**
+     * Haptic when entering overview.
+     */
+    public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
+
+    private final Vibrator mVibrator;
+    private final boolean mHasVibrator;
+
+    private boolean mIsHapticFeedbackEnabled;
+
+    public VibratorWrapper(Context context) {
+        mVibrator = context.getSystemService(Vibrator.class);
+        mHasVibrator = mVibrator.hasVibrator();
+        if (mHasVibrator) {
+            final ContentResolver resolver = context.getContentResolver();
+            mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+            final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    mIsHapticFeedbackEnabled = isHapticFeedbackEnabled(resolver);
+                }
+            };
+            resolver.registerContentObserver(Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED),
+                    false /* notifyForDescendents */, observer);
+        } else {
+            mIsHapticFeedbackEnabled = false;
+        }
+    }
+
+    private boolean isHapticFeedbackEnabled(ContentResolver resolver) {
+        return Settings.System.getInt(resolver, HAPTIC_FEEDBACK_ENABLED, 0) == 1;
+    }
+
+    /** Vibrates with the given effect if haptic feedback is available and enabled. */
+    public void vibrate(VibrationEffect vibrationEffect) {
+        if (mHasVibrator && mIsHapticFeedbackEnabled) {
+            UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index acce308..5a131c8 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,13 +16,14 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.os.Process;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -54,7 +55,9 @@
             mLoadAnimationCompleted = true;
         }
 
-        attachObserver();
+        if (mAttachedView.isAttachedToWindow()) {
+            attachObserver();
+        }
     }
 
     private void attachObserver() {
@@ -66,7 +69,7 @@
     @Override
     public void execute(Runnable command) {
         mTasks.add(command);
-        LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
     }
 
     @Override
@@ -108,7 +111,7 @@
         if (mLauncher != null) {
             mLauncher.clearPendingExecutor(this);
         }
-        LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT);
+        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
     }
 
     protected boolean isCompleted() {
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 8af048d..5b33f18 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -21,12 +21,12 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.launcher3.util.ViewPool.Reusable;
-
 import androidx.annotation.AnyThread;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.launcher3.util.ViewPool.Reusable;
+
 /**
  * Utility class to maintain a pool of reusable views.
  * During initialization, views are inflated on the background thread.
@@ -58,14 +58,18 @@
         Preconditions.assertUIThread();
         Handler handler = new Handler();
 
+        // LayoutInflater is not thread save as it maintains a global variable 'mConstructorArgs'.
+        // Create a different copy to use on the background thread.
+        LayoutInflater inflater = mInflater.cloneInContext(mInflater.getContext());
+
         // Inflate views on a non looper thread. This allows us to catch errors like calling
         // "new Handler()" in constructor easily.
         new Thread(() -> {
             for (int i = 0; i < initialSize; i++) {
-                T view = inflateNewView();
+                T view = inflateNewView(inflater);
                 handler.post(() -> addToPool(view));
             }
-        }).start();
+        }, "ViewPool-init").start();
     }
 
     @UiThread
@@ -94,12 +98,12 @@
             mCurrentSize--;
             return (T) mPool[mCurrentSize];
         }
-        return inflateNewView();
+        return inflateNewView(mInflater);
     }
 
     @AnyThread
-    private T inflateNewView() {
-        return (T) mInflater.inflate(mLayoutId, mParent, false);
+    private T inflateNewView(LayoutInflater inflater) {
+        return (T) inflater.inflate(mLayoutId, mParent, false);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index 5c24687..2ad80cf 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -198,7 +200,7 @@
         private float mOffsetX;
 
         public OffsetHandler(Context context) {
-            super(UiThreadHelper.getBackgroundLooper());
+            super(UI_HELPER_EXECUTOR.getLooper());
             mInterpolator = Interpolators.DEACCEL_1_5;
             mWM = WallpaperManager.getInstance(context);
         }
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index f948beb..195a77a 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -32,13 +32,14 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 
 /**
  * Extension of AbstractFloatingView with common methods for sliding in from bottom
  */
 public abstract class AbstractSlideInView extends AbstractFloatingView
-        implements SwipeDetector.Listener {
+        implements SingleAxisSwipeDetector.Listener {
 
     protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
             new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
@@ -57,7 +58,7 @@
     protected static final float TRANSLATION_SHIFT_OPENED = 0f;
 
     protected final Launcher mLauncher;
-    protected final SwipeDetector mSwipeDetector;
+    protected final SingleAxisSwipeDetector mSwipeDetector;
     protected final ObjectAnimator mOpenCloseAnimator;
 
     protected View mContent;
@@ -73,7 +74,8 @@
         mLauncher = Launcher.getLauncher(context);
 
         mScrollInterpolator = Interpolators.SCROLL_CUBIC;
-        mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
+        mSwipeDetector = new SingleAxisSwipeDetector(context, this,
+                SingleAxisSwipeDetector.VERTICAL);
 
         mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
@@ -97,7 +99,7 @@
         }
 
         int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
-                SwipeDetector.DIRECTION_NEGATIVE : 0;
+                SingleAxisSwipeDetector.DIRECTION_NEGATIVE : 0;
         mSwipeDetector.setDetectableScrollConditions(
                 directionsToDetectScroll, false);
         mSwipeDetector.onTouchEvent(ev);
@@ -122,7 +124,7 @@
         return mIsOpen && mOpenCloseAnimator.isRunning();
     }
 
-    /* SwipeDetector.Listener */
+    /* SingleAxisSwipeDetector.Listener */
 
     @Override
     public void onDragStart(boolean start) { }
@@ -136,32 +138,32 @@
     }
 
     @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
+    public void onDragEnd(float velocity) {
+        if ((mSwipeDetector.isFling(velocity) && velocity > 0) || mTranslationShift > 0.5f) {
             mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
-            mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
+            mOpenCloseAnimator.setDuration(BaseSwipeDetector.calculateDuration(
                     velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
             close(true);
         } else {
             mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
                     TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
             mOpenCloseAnimator.setDuration(
-                    SwipeDetector.calculateDuration(velocity, mTranslationShift))
+                    BaseSwipeDetector.calculateDuration(velocity, mTranslationShift))
                     .setInterpolator(Interpolators.DEACCEL);
             mOpenCloseAnimator.start();
         }
     }
 
     protected void handleClose(boolean animate, long defaultDuration) {
-        if (mIsOpen && !animate) {
+        if (!mIsOpen) {
+            return;
+        }
+        if (!animate) {
             mOpenCloseAnimator.cancel();
             setTranslationShift(TRANSLATION_SHIFT_CLOSED);
             onCloseComplete();
             return;
         }
-        if (!mIsOpen) {
-            return;
-        }
         mOpenCloseAnimator.setValues(
                 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 15f2724..a4f6a8e 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -20,11 +20,16 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
-import static com.android.launcher3.Utilities.shouldDisableGestures;
+import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.annotation.TargetApi;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -40,8 +45,11 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -101,6 +109,14 @@
 
     protected final T mActivity;
     private final MultiValueAlpha mMultiValueAlpha;
+    private final WallpaperManager mWallpaperManager;
+    private final BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onWallpaperChanged();
+        }
+    };
+    private final String[] mWallpapersWithoutSysuiScrims;
 
     // All the touch controllers for the view
     protected TouchController[] mControllers;
@@ -111,10 +127,15 @@
 
     private TouchCompleteListener mTouchCompleteListener;
 
+    protected boolean mAllowSysuiScrims = true;
+
     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
         super(context, attrs);
         mActivity = (T) ActivityContext.lookupContext(context);
         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
+        mWallpaperManager = context.getSystemService(WallpaperManager.class);
+        mWallpapersWithoutSysuiScrims = getResources().getStringArray(
+                R.array.live_wallpapers_remove_sysui_scrims);
     }
 
     /**
@@ -152,8 +173,6 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
-        if (shouldDisableGestures(ev)) return null;
-
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
             return topView;
@@ -173,10 +192,8 @@
             // Only look for controllers if we are not dispatching from gesture area and proxy is
             // not active
             mActiveController = findControllerToHandleTouch(ev);
-
-            if (mActiveController != null) return true;
         }
-        return false;
+        return mActiveController != null;
     }
 
     @Override
@@ -223,17 +240,13 @@
             // This can happen if something goes wrong during a state change/transition.
             AbstractFloatingView floatingView = (AbstractFloatingView) child;
             if (floatingView.isOpen()) {
-                postDelayed(() -> floatingView.close(false), SINGLE_FRAME_MS);
+                postDelayed(() -> floatingView.close(false), getSingleFrameMs(getContext()));
             }
         }
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                    "onTouchEvent " + ev);
-        }
         int action = ev.getAction();
         if (action == ACTION_UP || action == ACTION_CANCEL) {
             if (mTouchCompleteListener != null) {
@@ -243,10 +256,6 @@
         }
 
         if (mActiveController != null) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_DRAG_TAG,
-                        "onTouchEvent 1");
-            }
             return mActiveController.onControllerTouchEvent(ev);
         } else {
             // In case no child view handled the touch event, we may not get onIntercept anymore
@@ -256,9 +265,6 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "BaseDragLayer.dispatchTouchEvent " + ev);
-        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 float x = ev.getX();
@@ -277,6 +283,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;
@@ -475,7 +485,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);
@@ -525,4 +535,47 @@
         }
         return super.dispatchApplyWindowInsets(insets);
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mActivity.registerReceiver(mWallpaperChangeReceiver,
+                new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+        onWallpaperChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mActivity.unregisterReceiver(mWallpaperChangeReceiver);
+    }
+
+    private void onWallpaperChanged() {
+        WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo();
+        boolean oldAllowSysuiScrims = mAllowSysuiScrims;
+        mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo);
+        if (mAllowSysuiScrims != oldAllowSysuiScrims) {
+            // Reapply insets so scrim can be removed or re-added if necessary.
+            setInsets(mInsets);
+        }
+    }
+
+    /**
+     * Determines whether we can scrim the status bar and nav bar for the given wallpaper by
+     * checking against a list of live wallpapers that we don't show the scrims on.
+     */
+    private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
+        if (newWallpaperInfo == null) {
+            // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+            return true;
+        }
+        ComponentName newWallpaper = newWallpaperInfo.getComponent();
+        for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
+            if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
+                // New wallpaper is blacklisted from showing a scrim.
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index ab4b576..49d94f0 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -41,7 +42,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.CancellationSignal;
-import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -50,11 +50,17 @@
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -66,13 +72,6 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
@@ -129,6 +128,7 @@
 
     private final Launcher mLauncher;
     private final int mBlurSizeOutline;
+    private final boolean mIsRtl;
 
     private boolean mIsVerticalBarLayout = false;
     private boolean mIsAdaptiveIcon = false;
@@ -174,6 +174,7 @@
         mLauncher = Launcher.getLauncher(context);
         mBlurSizeOutline = getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
+        mIsRtl = Utilities.isRtl(getResources());
         mListenerView = new ListenerView(context, attrs);
 
         mFgSpringX = new SpringAnimation(this, mFgTransXProperty)
@@ -213,7 +214,10 @@
         setAlpha(alpha);
 
         LayoutParams lp = (LayoutParams) getLayoutParams();
-        float dX = rect.left - lp.leftMargin;
+        float dX = mIsRtl
+                ? rect.left
+                - (mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width)
+                : rect.left - lp.getMarginStart();
         float dY = rect.top - lp.topMargin;
         setTranslationX(dX);
         setTranslationY(dY);
@@ -323,14 +327,18 @@
         mPositionOut.set(position);
         lp.ignoreInsets = true;
         // Position the floating view exactly on top of the original
-        lp.leftMargin = Math.round(position.left);
         lp.topMargin = Math.round(position.top);
-
+        if (mIsRtl) {
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - position.right));
+        } else {
+            lp.setMarginStart(Math.round(position.left));
+        }
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
-        layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
-                + lp.height);
-
+        int left = mIsRtl
+                ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                : lp.leftMargin;
+        layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
     }
 
     /**
@@ -514,8 +522,11 @@
             } else {
                 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
             }
-            layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
-                    + lp.height);
+
+            int left = mIsRtl
+                    ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                    : lp.leftMargin;
+            layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
 
             float scale = Math.max((float) lp.height / originalHeight,
                     (float) lp.width / originalWidth);
@@ -549,13 +560,8 @@
      * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a
      * callback to set the icon once the icon result is loaded.
      */
-    private void checkIconResult(View originalView, boolean isOpening) {
+    private void checkIconResult(View originalView) {
         CancellationSignal cancellationSignal = new CancellationSignal();
-        if (!isOpening) {
-            // Hide immediately since the floating view starts at a different location.
-            originalView.setVisibility(INVISIBLE);
-            cancellationSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE));
-        }
 
         if (mIconLoadResult == null) {
             Log.w(TAG, "No icon load result found in checkIconResult");
@@ -566,9 +572,7 @@
             if (mIconLoadResult.isIconLoaded) {
                 setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
                         mIconLoadResult.iconOffset);
-                if (isOpening) {
-                    originalView.setVisibility(INVISIBLE);
-                }
+                hideOriginalView(originalView);
             } else {
                 mIconLoadResult.onIconLoaded = () -> {
                     if (cancellationSignal.isCanceled()) {
@@ -577,16 +581,23 @@
 
                     setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge,
                             mIconLoadResult.iconOffset);
-
-                    // Delay swapping views until the icon is loaded to prevent a flash.
                     setVisibility(VISIBLE);
-                    originalView.setVisibility(INVISIBLE);
+                    hideOriginalView(originalView);
                 };
                 mLoadIconSignal = cancellationSignal;
             }
         }
     }
 
+    private void hideOriginalView(View originalView) {
+        if (originalView instanceof IconLabelDotView) {
+            ((IconLabelDotView) originalView).setIconVisible(false);
+            ((IconLabelDotView) originalView).setForceHideDot(true);
+        } else {
+            originalView.setVisibility(INVISIBLE);
+        }
+    }
+
     private void setBackgroundDrawableBounds(float scale) {
         sTmpRect.set(mFinalDrawableBounds);
         Utilities.scaleRectAboutCenter(sTmpRect, scale);
@@ -656,8 +667,10 @@
         canvas.restoreToCount(count);
     }
 
-    public void onListenerViewClosed() {
-        // Fast finish here.
+    public void fastFinish() {
+        if (mLoadIconSignal != null) {
+            mLoadIconSignal.cancel();
+        }
         if (mEndRunnable != null) {
             mEndRunnable.run();
             mEndRunnable = null;
@@ -673,6 +686,10 @@
         if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
             setVisibility(View.VISIBLE);
         }
+        if (!mIsOpening) {
+            // When closing an app, we want the item on the workspace to be invisible immediately
+            hideOriginalView(mOriginalIcon);
+        }
     }
 
     @Override
@@ -704,8 +721,8 @@
      */
     @UiThread
     public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) {
-        IconLoadResult result = new IconLoadResult();
-        new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> {
+        IconLoadResult result = new IconLoadResult(info);
+        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
             RectF position = new RectF();
             getLocationBoundsForView(l, v, isOpening, position);
             getIconResult(l, v, info, position, result);
@@ -733,10 +750,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;
 
@@ -748,23 +768,23 @@
         // 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);
         dragLayer.addView(view.mListenerView);
-        view.mListenerView.setListener(view::onListenerViewClosed);
+        view.mListenerView.setListener(view::fastFinish);
 
         view.mEndRunnable = () -> {
             view.mEndRunnable = null;
 
             if (hideOriginal) {
                 if (isOpening) {
-                    originalView.setVisibility(VISIBLE);
+                    if (originalView instanceof BubbleTextView) {
+                        ((BubbleTextView) originalView).setIconVisible(true);
+                        ((BubbleTextView) originalView).setForceHideDot(false);
+                    } else {
+                        originalView.setVisibility(VISIBLE);
+                    }
                     view.finish(dragLayer);
                 } else {
                     view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
@@ -774,6 +794,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);
+        }
+
         return view;
     }
 
@@ -792,38 +820,34 @@
             }
         });
 
-        if (mBadge != null && !(mOriginalIcon instanceof FolderIcon)) {
+        if (mBadge != null) {
             ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255);
             badgeFade.addUpdateListener(valueAnimator -> invalidate());
             fade.play(badgeFade);
         }
 
-        if (originalView instanceof BubbleTextView) {
-            BubbleTextView btv = (BubbleTextView) originalView;
-            btv.forceHideDot(true);
+        if (originalView instanceof IconLabelDotView) {
+            IconLabelDotView view = (IconLabelDotView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    btv.forceHideDot(false);
+                    view.setIconVisible(true);
+                    view.setForceHideDot(false);
                 }
             });
         }
 
-        if (originalView instanceof FolderIcon) {
-            FolderIcon folderIcon = (FolderIcon) originalView;
-            folderIcon.setBackgroundVisible(false);
-            folderIcon.getFolderName().setTextVisibility(false);
-            fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
+        if (originalView instanceof BubbleTextView) {
+            BubbleTextView btv = (BubbleTextView) originalView;
             fade.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationEnd(Animator animation) {
-                    folderIcon.setBackgroundVisible(true);
-                    if (folderIcon.hasDot()) {
-                        folderIcon.animateDotScale(0, 1f);
-                    }
+                public void onAnimationStart(Animator animation) {
+                    btv.setIconVisible(true);
+                    btv.setForceHideDot(true);
                 }
             });
-        } else {
+            fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255));
+        } else if (!(originalView instanceof FolderIcon)) {
             fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
         }
 
@@ -878,10 +902,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/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java
new file mode 100644
index 0000000..057caaf
--- /dev/null
+++ b/src/com/android/launcher3/views/IconLabelDotView.java
@@ -0,0 +1,24 @@
+/*
+ * 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.views;
+
+/**
+ * A view that has an icon, label, and notification dot.
+ */
+public interface IconLabelDotView {
+    void setIconVisible(boolean visible);
+    void setForceHideDot(boolean hide);
+}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 63f7427..465df44 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -18,10 +18,8 @@
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
 
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.text.TextUtils;
@@ -33,6 +31,9 @@
 import android.view.View.OnLongClickListener;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -46,7 +47,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.VisibleForTesting;
 
 /**
  * Popup shown on long pressing an empty space in launcher
@@ -169,16 +169,17 @@
     }
 
     public static boolean onWidgetsClicked(View view) {
-        return openWidgets(Launcher.getLauncher(view.getContext()));
+        return openWidgets(Launcher.getLauncher(view.getContext())) != null;
     }
 
-    public static boolean openWidgets(Launcher launcher) {
+    /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */
+    @Nullable
+    public static WidgetsFullSheet openWidgets(Launcher launcher) {
         if (launcher.getPackageManager().isSafeMode()) {
             Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-            return false;
+            return null;
         } else {
-            WidgetsFullSheet.show(launcher, true /* animated */);
-            return true;
+            return WidgetsFullSheet.show(launcher, true /* animated */);
         }
     }
 
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index c360117..9f59d78 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -18,14 +18,14 @@
 import static android.content.Context.ACCESSIBILITY_SERVICE;
 import static android.view.MotionEvent.ACTION_DOWN;
 
+import static androidx.core.graphics.ColorUtils.compositeColors;
+
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
-import static androidx.core.graphics.ColorUtils.compositeColors;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.Keyframe;
@@ -47,6 +47,13 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
@@ -62,15 +69,10 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.WidgetsFullSheet;
 
 import java.util.List;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
 
 /**
  * Simple scrim which draws a flat color
@@ -286,6 +288,7 @@
             anim.addUpdateListener((v) -> invalidate(invalidateRegion));
             getOverlay().add(drawable);
             anim.start();
+            return true;
         }
         return value;
     }
@@ -325,7 +328,7 @@
 
         if (enabled) {
             stateManager.addStateListener(this);
-            handleStateChangedComplete(mLauncher.getStateManager().getState());
+            handleStateChangedComplete(stateManager.getState());
         } else {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
         }
@@ -437,7 +440,24 @@
             } else if (action == WALLPAPERS) {
                 return OptionsPopupView.startWallpaperPicker(ScrimView.this);
             } else if (action == WIDGETS) {
-                return OptionsPopupView.onWidgetsClicked(ScrimView.this);
+                int originalImportanceForAccessibility = getImportantForAccessibility();
+                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+                WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
+                if (widgetsFullSheet == null) {
+                    setImportantForAccessibility(originalImportanceForAccessibility);
+                    return false;
+                }
+                widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View view) {}
+
+                    @Override
+                    public void onViewDetachedFromWindow(View view) {
+                        setImportantForAccessibility(originalImportanceForAccessibility);
+                        widgetsFullSheet.removeOnAttachStateChangeListener(this);
+                    }
+                });
+                return true;
             } else if (action == SETTINGS) {
                 return OptionsPopupView.startSettings(ScrimView.this);
             }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index dce839f..f3fd7ca 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
 
@@ -96,7 +97,7 @@
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
         if (Utilities.ATLEAST_OREO) {
-            setExecutor(Utilities.THREAD_POOL_EXECUTOR);
+            setExecutor(Executors.THREAD_POOL_EXECUTOR);
         }
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
@@ -332,12 +333,7 @@
         if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) {
             mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance;
             if (mAutoAdvanceRunnable == null) {
-                mAutoAdvanceRunnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        runAutoAdvance();
-                    }
-                };
+                mAutoAdvanceRunnable = this::runAutoAdvance;
             }
 
             handler.removeCallbacks(mAutoAdvanceRunnable);
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
new file mode 100644
index 0000000..f20c83d
--- /dev/null
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -0,0 +1,178 @@
+/*
+ * 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.widget.custom;
+
+import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.systemui.plugins.CustomWidgetPlugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * CustomWidgetManager handles custom widgets implemented as a plugin.
+ */
+public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
+
+    public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
+            new MainThreadInitializedObject<>(CustomWidgetManager::new);
+
+    /**
+     * 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 SparseArray<WeakReference<Context>> mContexts;
+    private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
+    private final SparseArray<ComponentName> mWidgetsIdMap;
+    private Consumer<PackageUserKey> mWidgetRefreshCallback;
+
+    private CustomWidgetManager(Context context) {
+        mPlugins = new SparseArray<>();
+        mContexts = new SparseArray<>();
+        mCustomWidgets = new ArrayList<>();
+        mWidgetsIdMap = new SparseArray<>();
+        PluginManagerWrapper.INSTANCE.get(context)
+                .addPluginListener(this, CustomWidgetPlugin.class, true);
+    }
+
+    @Override
+    public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
+        mPlugins.put(mAutoProviderId, plugin);
+        mContexts.put(mAutoProviderId, new WeakReference<>(context));
+        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(mAutoProviderId, plugin, parcel, context);
+        parcel.recycle();
+        mCustomWidgets.add(info);
+        mWidgetsIdMap.put(mAutoProviderId, info.provider);
+        mWidgetRefreshCallback.accept(null);
+        mAutoProviderId++;
+    }
+
+    @Override
+    public void onPluginDisconnected(CustomWidgetPlugin plugin) {
+        int providerId = findProviderId(plugin);
+        if (providerId == -1) return;
+        mPlugins.remove(providerId);
+        mContexts.remove(providerId);
+        mCustomWidgets.remove(getWidgetProvider(providerId));
+        mWidgetsIdMap.remove(providerId);
+    }
+
+    /**
+     * Inject a callback function to refresh the widgets.
+     */
+    public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) {
+        mWidgetRefreshCallback = cb;
+    }
+
+    /**
+     * Callback method to inform a plugin it's corresponding widget has been created.
+     */
+    public void onViewCreated(LauncherAppWidgetHostView view) {
+        CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
+        CustomWidgetPlugin plugin = mPlugins.get(info.providerId);
+        WeakReference<Context> context = mContexts.get(info.providerId);
+        if (plugin == null) return;
+        plugin.onViewCreated(context == null ? null : context.get(), view);
+    }
+
+    /**
+     * Returns the list of custom widgets.
+     */
+    @NonNull
+    public List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+        return mCustomWidgets;
+    }
+
+    /**
+     * Returns the widget id for a specific provider.
+     */
+    public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) {
+        int index = mWidgetsIdMap.indexOfValue(provider);
+        if (index >= 0) {
+            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index);
+        } else {
+            return AppWidgetManager.INVALID_APPWIDGET_ID;
+        }
+    }
+
+    /**
+     * Returns the widget provider in respect to given widget id.
+     */
+    @Nullable
+    public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) {
+        ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
+        for (LauncherAppWidgetProviderInfo info : mCustomWidgets) {
+            if (info.provider.equals(cn)) return info;
+        }
+        return null;
+    }
+
+    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(context);
+        info.resizeMode = plugin.getResizeMode(context);
+
+        info.spanX = plugin.getSpanX(context);
+        info.spanY = plugin.getSpanY(context);
+        info.minSpanX = plugin.getMinSpanX(context);
+        info.minSpanY = plugin.getMinSpanY(context);
+        return info;
+    }
+
+    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/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
deleted file mode 100644
index 00720c4..0000000
--- a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
+++ /dev/null
@@ -1,142 +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.widget.custom;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Parcel;
-import android.os.Process;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
-
-/**
- * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml
- */
-public class CustomWidgetParser {
-
-    private static List<LauncherAppWidgetProviderInfo> sCustomWidgets;
-    private static SparseArray<ComponentName> sWidgetsIdMap;
-
-    public static List<LauncherAppWidgetProviderInfo> getCustomWidgets(Context context) {
-        if (sCustomWidgets == null) {
-            // Synchronization not needed as it it safe to load multiple times
-            parseCustomWidgets(context);
-        }
-
-        return sCustomWidgets;
-    }
-
-    public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) {
-        if (sWidgetsIdMap == null) {
-            parseCustomWidgets(context);
-        }
-        int index = sWidgetsIdMap.indexOfValue(provider);
-        if (index >= 0) {
-            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index);
-        } else {
-            return AppWidgetManager.INVALID_APPWIDGET_ID;
-        }
-    }
-
-    public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) {
-        if (sWidgetsIdMap == null || sCustomWidgets == null) {
-            parseCustomWidgets(context);
-        }
-        ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
-        for (LauncherAppWidgetProviderInfo info : sCustomWidgets) {
-            if (info.provider.equals(cn)) {
-                return info;
-            }
-        }
-        return null;
-    }
-
-    private static void parseCustomWidgets(Context context) {
-        ArrayList<LauncherAppWidgetProviderInfo> widgets = new ArrayList<>();
-        SparseArray<ComponentName> idMap = new SparseArray<>();
-
-        List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
-                .getInstalledProvidersForProfile(Process.myUserHandle());
-        if (providers.isEmpty()) {
-            sCustomWidgets = widgets;
-            sWidgetsIdMap = idMap;
-            return;
-        }
-
-        Parcel parcel = Parcel.obtain();
-        providers.get(0).writeToParcel(parcel, 0);
-
-        try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) {
-            final int depth = parser.getDepth();
-            int type;
-
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-                if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) {
-                    TypedArray a = context.obtainStyledAttributes(
-                            Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo);
-
-                    parcel.setDataPosition(0);
-                    CustomAppWidgetProviderInfo info = newInfo(a, parcel, context);
-                    widgets.add(info);
-                    a.recycle();
-
-                    idMap.put(info.providerId, info.provider);
-                }
-            }
-        } catch (IOException | XmlPullParserException e) {
-            throw new RuntimeException(e);
-        }
-        parcel.recycle();
-        sCustomWidgets = widgets;
-        sWidgetsIdMap = idMap;
-    }
-
-    private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) {
-        int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0);
-        CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId);
-        info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
-
-        info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label);
-        info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0);
-        info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0);
-        info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0);
-        info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0);
-
-        info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1);
-        info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1);
-        info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1);
-        info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1);
-        return info;
-    }
-}
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
new file mode 100644
index 0000000..47aa94b
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * 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.appwidget.AppWidgetHostView;
+import android.content.Context;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a custom widget.
+ */
+@ProvidesInterface(action = CustomWidgetPlugin.ACTION, version = CustomWidgetPlugin.VERSION)
+public interface CustomWidgetPlugin extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PLUGIN_CUSTOM_WIDGET";
+    int VERSION = 1;
+
+    /**
+     * The label to display to the user in the AppWidget picker.
+     */
+    String getLabel(Context context);
+
+    /**
+     * 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.
+     */
+    int getSpanX(Context context);
+
+    /**
+     * The default height of the widget when added to a host, in dp. The widget will get
+     * at least this height, and will often be given more, depending on the host.
+     */
+    int getSpanY(Context context);
+
+    /**
+     * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+     * is greater than minWidth or if horizontal resizing isn't enabled.
+     */
+    int getMinSpanX(Context context);
+
+    /**
+     * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+     * is greater than minHeight or if vertical resizing isn't enabled.
+     */
+    int getMinSpanY(Context context);
+
+    /**
+     * The rules by which a widget can be resized.
+     */
+    int getResizeMode(Context context);
+
+    /**
+     * Notify the plugin that container of the widget has been rendered, where the custom widget
+     * can be attached to.
+     */
+    void onViewCreated(Context context, AppWidgetHostView parent);
+}
diff --git a/src_plugins/com/android/systemui/plugins/HotseatPlugin.java b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java
new file mode 100644
index 0000000..1264e0d
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java
@@ -0,0 +1,20 @@
+package com.android.systemui.plugins;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a sub-view in the Hotseat.
+ */
+@ProvidesInterface(action = HotseatPlugin.ACTION, version = HotseatPlugin.VERSION)
+public interface HotseatPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_HOTSEAT";
+    int VERSION = 1;
+
+    /**
+     * Creates a plugin view which will be added to the Hotseat.
+     */
+    View createView(ViewGroup parent);
+}
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_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index 1710aef..789bfd8 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -16,9 +16,8 @@
 
 package com.android.launcher3.model;
 
-import com.android.launcher3.AllAppsList;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
index 6b6f70d..57f4164 100644
--- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
+++ b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java
@@ -27,9 +27,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.WorkspaceItemInfo;
+import androidx.annotation.Nullable;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -45,42 +43,27 @@
             | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
 
     private static DeepShortcutManager sInstance;
-    private static final Object sInstanceLock = new Object();
 
     public static DeepShortcutManager getInstance(Context context) {
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                sInstance = new DeepShortcutManager(context.getApplicationContext());
-            }
-            return sInstance;
+        if (sInstance == null) {
+            sInstance = new DeepShortcutManager(context.getApplicationContext());
         }
+        return sInstance;
     }
 
     private final LauncherApps mLauncherApps;
-    private boolean mWasLastCallSuccess;
 
     private DeepShortcutManager(Context context) {
         mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
     }
 
-    public static boolean supportsShortcuts(ItemInfo info) {
-        boolean isItemPromise = info instanceof WorkspaceItemInfo
-                && ((WorkspaceItemInfo) info).hasPromiseIconUi();
-        return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                && !info.isDisabled() && !isItemPromise;
-    }
-
-    public boolean wasLastCallSuccess() {
-        return mWasLastCallSuccess;
-    }
-
     /**
      * Queries for the shortcuts with the package name and provided ids.
      *
      * This method is intended to get the full details for shortcuts when they are added or updated,
      * because we only get "key" fields in onShortcutsChanged().
      */
-    public List<ShortcutInfo> queryForFullDetails(String packageName,
+    public QueryResult queryForFullDetails(String packageName,
             List<String> shortcutIds, UserHandle user) {
         return query(FLAG_GET_ALL, packageName, null, shortcutIds, user);
     }
@@ -89,8 +72,9 @@
      * Gets all the manifest and dynamic shortcuts associated with the given package and user,
      * to be displayed in the shortcuts container on long press.
      */
-    public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity,
+    public QueryResult queryForShortcutsContainer(@Nullable ComponentName activity,
             UserHandle user) {
+        if (activity == null) return QueryResult.FAILURE;
         return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC,
                 activity.getPackageName(), activity, null, user);
     }
@@ -107,10 +91,8 @@
         pinnedIds.remove(id);
         try {
             mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-            mWasLastCallSuccess = true;
         } catch (SecurityException|IllegalStateException e) {
             Log.w(TAG, "Failed to unpin shortcut", e);
-            mWasLastCallSuccess = false;
         }
     }
 
@@ -126,10 +108,8 @@
         pinnedIds.add(id);
         try {
             mLauncherApps.pinShortcuts(packageName, pinnedIds, user);
-            mWasLastCallSuccess = true;
         } catch (SecurityException|IllegalStateException e) {
             Log.w(TAG, "Failed to pin shortcut", e);
-            mWasLastCallSuccess = false;
         }
     }
 
@@ -138,23 +118,18 @@
         try {
             mLauncherApps.startShortcut(packageName, id, sourceBounds,
                     startActivityOptions, user);
-            mWasLastCallSuccess = true;
         } catch (SecurityException|IllegalStateException e) {
             Log.e(TAG, "Failed to start shortcut", e);
-            mWasLastCallSuccess = false;
         }
     }
 
     public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) {
         try {
-            Drawable icon = mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
-            mWasLastCallSuccess = true;
-            return icon;
+            return mLauncherApps.getShortcutIconDrawable(shortcutInfo, density);
         } catch (SecurityException|IllegalStateException e) {
             Log.e(TAG, "Failed to get shortcut icon", e);
-            mWasLastCallSuccess = false;
+            return null;
         }
-        return null;
     }
 
     /**
@@ -162,20 +137,20 @@
      *
      * If packageName is null, returns all pinned shortcuts regardless of package.
      */
-    public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) {
+    public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) {
         return queryForPinnedShortcuts(packageName, null, user);
     }
 
-    public List<ShortcutInfo> queryForPinnedShortcuts(String packageName,
-            List<String> shortcutIds, UserHandle user) {
+    public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds,
+            UserHandle user) {
         return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user);
     }
 
-    public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) {
+    public QueryResult queryForAllShortcuts(UserHandle user) {
         return query(FLAG_GET_ALL, null, null, null, user);
     }
 
-    private List<String> extractIds(List<ShortcutInfo> shortcuts) {
+    private static List<String> extractIds(List<ShortcutInfo> shortcuts) {
         List<String> shortcutIds = new ArrayList<>(shortcuts.size());
         for (ShortcutInfo shortcut : shortcuts) {
             shortcutIds.add(shortcut.getId());
@@ -189,8 +164,8 @@
      *
      * TODO: Use the cache to optimize this so we don't make an RPC every time.
      */
-    private List<ShortcutInfo> query(int flags, String packageName,
-            ComponentName activity, List<String> shortcutIds, UserHandle user) {
+    private QueryResult query(int flags, String packageName, ComponentName activity,
+            List<String> shortcutIds, UserHandle user) {
         ShortcutQuery q = new ShortcutQuery();
         q.setQueryFlags(flags);
         if (packageName != null) {
@@ -198,18 +173,12 @@
             q.setActivity(activity);
             q.setShortcutIds(shortcutIds);
         }
-        List<ShortcutInfo> shortcutInfos = null;
         try {
-            shortcutInfos = mLauncherApps.getShortcuts(q, user);
-            mWasLastCallSuccess = true;
+            return new QueryResult(mLauncherApps.getShortcuts(q, user));
         } catch (SecurityException|IllegalStateException e) {
             Log.e(TAG, "Failed to query for shortcuts", e);
-            mWasLastCallSuccess = false;
+            return QueryResult.FAILURE;
         }
-        if (shortcutInfos == null) {
-            return Collections.EMPTY_LIST;
-        }
-        return shortcutInfos;
     }
 
     public boolean hasHostPermission() {
@@ -220,4 +189,25 @@
         }
         return false;
     }
+
+    public static class QueryResult extends ArrayList<ShortcutInfo> {
+
+        static QueryResult FAILURE = new QueryResult();
+
+        private final boolean mWasSuccess;
+
+        QueryResult(List<ShortcutInfo> result) {
+            super(result == null ? Collections.emptyList() : result);
+            mWasSuccess = true;
+        }
+
+        QueryResult() {
+            mWasSuccess = false;
+        }
+
+
+        public boolean wasSuccess() {
+            return mWasSuccess;
+        }
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index e9dc800..23f21a1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -10,7 +10,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationComponents;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
-import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -21,7 +21,7 @@
     private MotionEvent mTouchDownEvent;
 
     public AllAppsSwipeController(Launcher l) {
-        super(l, SwipeDetector.VERTICAL);
+        super(l, SingleAxisSwipeDetector.VERTICAL);
     }
 
     @Override
@@ -57,7 +57,7 @@
     }
 
     @Override
-    protected int getLogContainerTypeForNormalState() {
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
         return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ?
                 ContainerType.HOTSEAT : ContainerType.WORKSPACE;
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
new file mode 100644
index 0000000..60f12d8
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java
@@ -0,0 +1,35 @@
+/*
+ * 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.content.Context;
+import com.android.launcher3.config.BaseFlags.BaseTogglableFlag;
+
+public class TogglableFlag extends BaseTogglableFlag {
+
+    public TogglableFlag(String key, boolean defaultValue, String description) {
+        super(key, defaultValue, description);
+    }
+
+    @Override
+    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 5cc64dc..f2b5ed2 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -17,9 +17,11 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Activity;
+import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 
@@ -27,6 +29,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.TouchController;
 
@@ -95,4 +98,9 @@
 
     public static void clearSwipeSharedState(boolean finishAnimation) {}
 
+    public static Person[] getPersons(ShortcutInfo si) {
+        return Utilities.EMPTY_PERSON_ARRAY;
+    }
+
+    public static void closeSystemWindows() {}
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 0c41241..02ead4e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,7 +27,7 @@
 
 ifneq (,$(wildcard frameworks/base))
 else
-    LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI
+    LOCAL_STATIC_JAVA_LIBRARIES += SystemUISharedLib
 
     LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
         ../src/com/android/launcher3/ResourceUtils.java \
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 61c7306..ffa90b9 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -20,6 +20,9 @@
 
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
 
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
@@ -59,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"
diff --git a/tests/OWNERS b/tests/OWNERS
index 046d871..02e8ebc 100644
--- a/tests/OWNERS
+++ b/tests/OWNERS
@@ -1 +1,4 @@
 vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
new file mode 100644
index 0000000..efbd9c9
--- /dev/null
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.UUID;
+
+
+/**
+ * Test to verify promise icon flow.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class PromiseIconUiTest extends AbstractLauncherUiTest {
+
+    private int mSessionId = -1;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+        waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+        mSessionId = -1;
+    }
+
+    @After
+    public void tearDown() {
+        if (mSessionId > -1) {
+            mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+        }
+    }
+
+    /**
+     * Create a session and return the id.
+     */
+    private int createSession(String label, Bitmap icon) throws Throwable {
+        SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+        params.setAppPackageName("test.promise.app");
+        params.setAppLabel(label);
+        params.setAppIcon(icon);
+        params.setInstallReason(PackageManager.INSTALL_REASON_USER);
+        return mTargetContext.getPackageManager().getPackageInstaller().createSession(params);
+    }
+
+    @Test
+    public void testPromiseIcon_addedFromEligibleSession() throws Throwable {
+        final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
+        final Workspace.ItemOperator findPromiseApp = (info, view) ->
+                info != null && TextUtils.equals(info.title, appLabel);
+
+        // Create and add test session
+        mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+
+        // Verify promise icon is added
+        waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+                launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
+
+        // Remove session
+        mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+        mSessionId = -1;
+
+        // Verify promise icon is removed
+        waitForLauncherCondition("Test Promise App not removed from workspace", launcher ->
+                launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
+    }
+
+    @Test
+    public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
+        final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
+        final Workspace.ItemOperator findPromiseApp = (info, view) ->
+                info != null && TextUtils.equals(info.title, appLabel);
+
+        // Create and add test session without icon or label
+        mSessionId = createSession(null, null);
+
+        // Sleep for duration of animation if a view was to be added + some buffer time.
+        Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500);
+
+        // Verify promise icon is not added
+        waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+                launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 7d60ad6..64df8e0 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -15,9 +15,9 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.Utilities;
 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;
@@ -115,7 +115,7 @@
         WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
                 new Intent().setComponent(cn), false /* allowMissingTarget */, true);
         assertNotNull(info);
-        assertTrue(Utilities.isLauncherAppTarget(info.intent));
+        assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
     }
 
     @Test
@@ -127,7 +127,7 @@
         WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo(
                 new Intent().setComponent(cn), true  /* allowMissingTarget */, true);
         assertNotNull(info);
-        assertTrue(Utilities.isLauncherAppTarget(info.intent));
+        assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent));
     }
 
     @Test
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/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index fa23b8d..4246096 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -32,13 +32,14 @@
 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.FileOutputStream;
 import java.io.IOException;
 
-import androidx.test.InstrumentationRegistry;
-
 /**
  * Content provider to receive commands from tests
  */
@@ -47,6 +48,7 @@
     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";
 
     @Override
     public boolean onCreate() {
@@ -99,6 +101,13 @@
                         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;
+            }
         }
         return super.call(method, arg, extras);
     }
@@ -122,7 +131,8 @@
             // Create an empty file so that we can pass its descriptor
             try {
                 file.createNewFile();
-            } catch (IOException e) { }
+            } catch (IOException e) {
+            }
         }
 
         return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
similarity index 72%
rename from tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
rename to tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index e042357..5174e4d 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -15,6 +15,12 @@
  */
 package com.android.launcher3.touch;
 
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
+import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyObject;
@@ -25,6 +31,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,25 +43,21 @@
 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 {
+public class SingleAxisSwipeDetectorTest {
 
-    private static final String TAG = SwipeDetectorTest.class.getSimpleName();
+    private static final String TAG = SingleAxisSwipeDetectorTest.class.getSimpleName();
     public static void L(String s, Object... parts) {
         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
     }
 
     private TouchEventGenerator mGenerator;
-    private SwipeDetector mDetector;
+    private SingleAxisSwipeDetector mDetector;
     private int mTouchSlop;
 
     @Mock
-    private SwipeDetector.Listener mMockListener;
+    private SingleAxisSwipeDetector.Listener mMockListener;
 
     @Mock
     private ViewConfiguration mMockConfig;
@@ -65,8 +71,8 @@
         doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
                 .getScaledMaximumFlingVelocity();
 
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false);
         mTouchSlop = orgConfig.getScaledTouchSlop();
         doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
 
@@ -75,8 +81,8 @@
 
     @Test
     public void testDragStart_verticalPositive() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 - mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
@@ -85,8 +91,8 @@
 
     @Test
     public void testDragStart_verticalNegative() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL, false);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+        mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
@@ -103,8 +109,8 @@
 
     @Test
     public void testDragStart_horizontalPositive() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+        mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
 
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -114,8 +120,8 @@
 
     @Test
     public void testDragStart_horizontalNegative() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, false);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+        mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
 
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -125,8 +131,8 @@
 
     @Test
     public void testDragStart_horizontalRtlPositive() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_POSITIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+        mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
 
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 - mTouchSlop, 100);
@@ -136,8 +142,8 @@
 
     @Test
     public void testDragStart_horizontalRtlNegative() {
-        mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL, true);
-        mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_NEGATIVE, false);
+        mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+        mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
 
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
@@ -160,6 +166,6 @@
         mGenerator.move(0, 100, 100 + mTouchSlop * 2);
         mGenerator.lift(0);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragEnd(anyFloat(), anyBoolean());
+        verify(mMockListener).onDragEnd(anyFloat());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4a0ca5c..9a894b1 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,16 +15,16 @@
  */
 package com.android.launcher3.ui;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
+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;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import static java.lang.System.exit;
 
-import android.app.Instrumentation;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -39,9 +39,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
-import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.Launcher;
@@ -49,17 +47,21 @@
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.ResourceUtils;
+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.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;
@@ -86,14 +88,12 @@
     public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
-    public static final long SHORT_UI_TIMEOUT = 300;
-    public static final long DEFAULT_UI_TIMEOUT = 10000;
+    public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866
     private static final String TAG = "AbstractLauncherUiTest";
 
-    protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+    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;
 
@@ -103,7 +103,17 @@
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
-        if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
+        if (TestHelpers.isInLauncherProcess()) {
+            Utilities.enableRunningInTestHarnessForTests();
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
+            mLauncher.setOnSettledStateAction(
+                    containerType -> executeOnLauncher(
+                            launcher ->
+                                    checkLauncherIntegrity(launcher, containerType)));
+        }
+        mLauncher.enableDebugTracing();
     }
 
     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@@ -116,6 +126,23 @@
     public ShellCommandRule mDisableHeadsUpNotification =
             ShellCommandRule.disableHeadsUpNotification();
 
+    protected void clearPackageData(String pkg) throws IOException, InterruptedException {
+        final CountDownLatch count = new CountDownLatch(2);
+        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                count.countDown();
+            }
+        };
+        mTargetContext.registerReceiver(broadcastReceiver,
+                PackageManagerHelper.getPackageFilter(pkg,
+                        Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED));
+
+        mDevice.executeShellCommand("pm clear " + pkg);
+        assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
+        mTargetContext.unregisterReceiver(broadcastReceiver);
+    }
+
     // Annotation for tests that need to be run in portrait and landscape modes.
     @Retention(RetentionPolicy.RUNTIME)
     @Target(ElementType.METHOD)
@@ -130,7 +157,8 @@
 
     @Rule
     public TestRule mOrderSensitiveRules = RuleChain.
-            outerRule(mActivityMonitor).
+            outerRule(new TestStabilityRule()).
+            around(mActivityMonitor).
             around(getRulesInsideActivityMonitor());
 
     public UiDevice getDevice() {
@@ -144,8 +172,6 @@
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
-        // Unlock the phone
-        mDevice.executeShellCommand("input keyevent 82");
     }
 
     @After
@@ -162,46 +188,14 @@
         }
     }
 
-    protected void lockRotation(boolean naturalOrientation) throws RemoteException {
-        if (naturalOrientation) {
-            mDevice.setOrientationNatural();
-        } else {
-            mDevice.setOrientationRight();
-        }
-    }
-
-    protected void clearLauncherData() throws IOException {
+    protected void clearLauncherData() throws IOException, InterruptedException {
         if (TestHelpers.isInLauncherProcess()) {
             LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
                     LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
             resetLoaderState();
         } else {
-            mDevice.executeShellCommand("pm clear " + mDevice.getLauncherPackageName());
-        }
-    }
-
-    /**
-     * Scrolls the {@param container} until it finds an object matching {@param condition}.
-     *
-     * @return the matching object.
-     */
-    protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) {
-        final int margin = ResourceUtils.getNavbarSize(
-                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
-        container.setGestureMargins(0, 0, 0, margin);
-
-        int i = 0;
-        for (; ; ) {
-            // findObject can only execute after spring settles.
-            mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT);
-            UiObject2 widget = container.findObject(condition);
-            if (widget != null && widget.getVisibleBounds().intersects(
-                    0, 0, mDevice.getDisplayWidth(),
-                    mDevice.getDisplayHeight() - margin)) {
-                return widget;
-            }
-            if (++i > 40) fail("Too many attempts");
-            container.scroll(Direction.DOWN, 1f);
+            clearPackageData(mDevice.getLauncherPackageName());
+            mLauncher.enableDebugTracing();
         }
     }
 
@@ -275,6 +269,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;
@@ -283,6 +283,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,
@@ -331,30 +345,33 @@
     }
 
     protected 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)), DEFAULT_UI_TIMEOUT));
+        startIntent(
+                getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
+                        packageName),
+                By.pkg(packageName).depth(0),
+                true /* newTask */);
     }
 
     protected void startTestActivity(int activityNumber) {
         final String packageName = getAppPackageName();
-        final Instrumentation instrumentation = getInstrumentation();
-        final Intent intent = instrumentation.getContext().getPackageManager().
+        final Intent intent = getInstrumentation().getContext().getPackageManager().
                 getLaunchIntentForPackage(packageName);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setComponent(new ComponentName(packageName,
                 "com.android.launcher3.tests.Activity" + activityNumber));
-        instrumentation.getTargetContext().startActivity(intent);
-        assertTrue(packageName + " didn't start",
-                mDevice.wait(
-                        Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)),
-                        DEFAULT_UI_TIMEOUT));
+        startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber),
+                false /* newTask */);
+    }
+
+    private void startIntent(Intent intent, BySelector selector, boolean newTask) {
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        if (newTask) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        } else {
+            intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        }
+        getInstrumentation().getTargetContext().startActivity(intent);
+        assertTrue("App didn't start: " + selector,
+                mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
     }
 
     public static String resolveSystemApp(String category) {
@@ -387,4 +404,68 @@
     protected int getAllAppsScroll(Launcher launcher) {
         return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
     }
+
+    private static void checkLauncherIntegrity(
+            Launcher launcher, ContainerType expectedContainerType) {
+        if (launcher != null) {
+            final LauncherStateManager stateManager = launcher.getStateManager();
+            final LauncherState stableState = stateManager.getCurrentStableState();
+
+            assertTrue("Stable state != state: " + stableState.getClass().getSimpleName() + ", "
+                            + stateManager.getState().getClass().getSimpleName(),
+                    stableState == stateManager.getState());
+
+            final boolean isResumed = launcher.hasBeenResumed();
+            assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
+                    isResumed == launcher.isStarted());
+            assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
+                    isResumed == launcher.isUserActive());
+
+            final int ordinal = stableState.ordinal;
+
+            switch (expectedContainerType) {
+                case WORKSPACE:
+                case WIDGETS: {
+                    assertTrue(
+                            "Launcher is not resumed in state: " + expectedContainerType,
+                            isResumed);
+                    assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+                            ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
+                    break;
+                }
+                case ALL_APPS: {
+                    assertTrue(
+                            "Launcher is not resumed in state: " + expectedContainerType,
+                            isResumed);
+                    assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+                            ordinal == TestProtocol.ALL_APPS_STATE_ORDINAL);
+                    break;
+                }
+                case OVERVIEW: {
+                    assertTrue(
+                            "Launcher is not resumed in state: " + expectedContainerType,
+                            isResumed);
+                    assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+                            ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
+                    break;
+                }
+                case BACKGROUND: {
+                    assertTrue("Launcher is resumed in state: " + expectedContainerType,
+                            !isResumed);
+                    assertTrue(TestProtocol.stateOrdinalToString(ordinal),
+                            ordinal == TestProtocol.NORMAL_STATE_ORDINAL);
+                    break;
+                }
+                default:
+                    throw new IllegalArgumentException(
+                            "Illegal container: " + expectedContainerType);
+            }
+        } else {
+            assertTrue(
+                    "Container type is not BACKGROUND or FALLBACK_OVERVIEW: "
+                            + expectedContainerType,
+                    expectedContainerType == ContainerType.BACKGROUND ||
+                            expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 58c74ce..ff21880 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -72,8 +72,7 @@
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
-        waitForModelLoaded();
+        mDevice.pressHome();
 
         mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
     }
@@ -88,8 +87,7 @@
                         info.getComponent().getClassName(), 2, 2));
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
-        waitForModelLoaded();
+        mDevice.pressHome();
 
         // Verify widget present
         assertTrue("Widget is not present",
@@ -105,8 +103,7 @@
                 .build());
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
-        waitForModelLoaded();
+        mDevice.pressHome();
 
         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 0f36292..80bb3ed 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.ui;
 
+import android.util.Log;
 import android.view.Surface;
 
 import com.android.launcher3.tapl.TestHelpers;
@@ -9,6 +10,7 @@
 import org.junit.runners.model.Statement;
 
 class PortraitLandscapeRunner implements TestRule {
+    private static final String TAG = "PortraitLandscapeRunner";
     private AbstractLauncherUiTest mTest;
 
     public PortraitLandscapeRunner(AbstractLauncherUiTest test) {
@@ -36,11 +38,17 @@
 
                     evaluateInPortrait();
                     evaluateInLandscape();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception", e);
+                    throw e;
                 } 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 c3168f8..2cf6c2b 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;
@@ -190,7 +192,7 @@
                 launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
                         isWorkspaceScrollable(launcher)));
         assertNotNull("ensureScrollable didn't add Chrome app",
-                workspace.tryGetWorkspaceAppIcon("Chrome"));
+                workspace.getWorkspaceAppIcon("Chrome"));
 
         // Test flinging workspace.
         workspace.flingBackward();
@@ -206,7 +208,7 @@
         assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
 
         // Test starting a workspace app.
-        final AppIcon app = workspace.tryGetWorkspaceAppIcon("Chrome");
+        final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
         assertNotNull("No Chrome app in workspace", app);
     }
 
@@ -216,7 +218,8 @@
             final AppIcon app = allApps.getAppIcon("TestActivity7");
             assertNotNull("AppIcon.launch returned null", app.launch(getAppPackageName()));
             test.executeOnLauncher(launcher -> assertTrue(
-                    "Launcher activity is the top activity; expecting another activity to be the top "
+                    "Launcher activity is the top activity; expecting another activity to be the "
+                            + "top "
                             + "one",
                     test.isInBackground(launcher)));
         } finally {
@@ -304,11 +307,8 @@
                 switchToAllApps();
         allApps.freeze();
         try {
-            allApps.
-                    getAppIcon(APP_NAME).
-                    dragToWorkspace().
-                    getWorkspaceAppIcon(APP_NAME).
-                    launch(getAppPackageName());
+            allApps.getAppIcon(APP_NAME).dragToWorkspace();
+            mLauncher.getWorkspace().getWorkspaceAppIcon(APP_NAME).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -335,13 +335,8 @@
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
 
-            // 4. Drag the first shortcut to the home screen.
-            // 5. Verify that the shortcut works on home screen
-            //    (the app opens and has the same text as the shortcut).
-            menuItem.
-                    dragToWorkspace().
-                    getWorkspaceAppIcon(shortcutName).
-                    launch(getAppPackageName());
+            menuItem.dragToWorkspace();
+            mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -350,4 +345,10 @@
     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/TestViewHelpers.java b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
index d13d319..d0df664 100644
--- a/tests/src/com/android/launcher3/ui/TestViewHelpers.java
+++ b/tests/src/com/android/launcher3/ui/TestViewHelpers.java
@@ -18,28 +18,15 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 import static androidx.test.InstrumentationRegistry.getTargetContext;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
 import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.Point;
 import android.os.Process;
-import android.os.SystemClock;
 import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
@@ -50,21 +37,6 @@
 public class TestViewHelpers {
     private static final String TAG = "TestViewHelpers";
 
-    private static UiDevice getDevice() {
-        return UiDevice.getInstance(getInstrumentation());
-    }
-
-    public static UiObject2 findViewById(int id) {
-        return getDevice().wait(Until.findObject(getSelectorForId(id)),
-                AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
-    }
-
-    public static BySelector getSelectorForId(int id) {
-        final Context targetContext = getTargetContext();
-        String name = targetContext.getResources().getResourceEntryName(id);
-        return By.res(targetContext.getPackageName(), name);
-    }
-
     /**
      * Finds a widget provider which can fit on the home screen.
      *
@@ -91,92 +63,6 @@
         return info;
     }
 
-    /**
-     * Drags an icon to the center of homescreen.
-     *
-     * @param icon object that is either app icon or shortcut icon
-     */
-    public static void dragToWorkspace(UiObject2 icon, boolean expectedToShowShortcuts) {
-        Point center = icon.getVisibleCenter();
-
-        // Action Down
-        final long downTime = SystemClock.uptimeMillis();
-        sendPointer(downTime, MotionEvent.ACTION_DOWN, center);
-
-        UiObject2 dragLayer = findViewById(R.id.drag_layer);
-
-        if (expectedToShowShortcuts) {
-            // Make sure shortcuts show up, and then move a bit to hide them.
-            assertNotNull(findViewById(R.id.deep_shortcuts_container));
-
-            Point moveLocation = new Point(center);
-            int distanceToMove =
-                    getTargetContext().getResources().getDimensionPixelSize(
-                            R.dimen.deep_shortcuts_start_drag_threshold) + 50;
-            if (moveLocation.y - distanceToMove >= dragLayer.getVisibleBounds().top) {
-                moveLocation.y -= distanceToMove;
-            } else {
-                moveLocation.y += distanceToMove;
-            }
-            movePointer(downTime, center, moveLocation);
-
-            assertNull(findViewById(R.id.deep_shortcuts_container));
-        }
-
-        // Wait until Remove/Delete target is visible
-        assertNotNull(findViewById(R.id.delete_target_text));
-
-        Point moveLocation = dragLayer.getVisibleCenter();
-
-        // Move to center
-        movePointer(downTime, center, moveLocation);
-        sendPointer(downTime, MotionEvent.ACTION_UP, moveLocation);
-
-        // Wait until remove target is gone.
-        getDevice().wait(Until.gone(getSelectorForId(R.id.delete_target_text)),
-                AbstractLauncherUiTest.DEFAULT_UI_TIMEOUT);
-    }
-
-    private static void movePointer(long downTime, Point from, Point to) {
-        while (!from.equals(to)) {
-            try {
-                Thread.sleep(20);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-            from.x = getNextMoveValue(to.x, from.x);
-            from.y = getNextMoveValue(to.y, from.y);
-            sendPointer(downTime, MotionEvent.ACTION_MOVE, from);
-        }
-    }
-
-    private static int getNextMoveValue(int targetValue, int oldValue) {
-        if (targetValue - oldValue > 10) {
-            return oldValue + 10;
-        } else if (targetValue - oldValue < -10) {
-            return oldValue - 10;
-        } else {
-            return targetValue;
-        }
-    }
-
-    public static void sendPointer(long downTime, int action, Point point) {
-        MotionEvent event = MotionEvent.obtain(downTime,
-                SystemClock.uptimeMillis(), action, point.x, point.y, 0);
-        getInstrumentation().sendPointerSync(event);
-        event.recycle();
-    }
-
-    /**
-     * Opens widget tray and returns the recycler view.
-     */
-    public static UiObject2 openWidgetsTray() {
-        final UiDevice device = getDevice();
-        device.pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
-        device.waitForIdle();
-        return findViewById(R.id.widgets_list_view);
-    }
-
     public static View findChildView(ViewGroup parent, Function<View, Boolean> condition) {
         for (int i = 0; i < parent.getChildCount(); ++i) {
             final View child = parent.getChildAt(i);
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index c93c20a..d9edc35 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -53,8 +53,8 @@
 
     @Test
     public void workTabExists() {
-        mActivityMonitor.startLauncher();
-
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
 
         /*
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 5eb5f19..e1b3ede 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -27,23 +27,20 @@
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.testcomponent.WidgetConfigActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.Condition;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,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;
@@ -71,58 +69,35 @@
     }
 
     @Test
-    // Convert test to TAPL b/131116002
+    @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
-    // Convert test to TAPL b/131116002
+    @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();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
-        // Open widget tray and wait for load complete.
-        final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
-        Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+        final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
 
         // Drag widget to homescreen
         WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
-        UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
-                .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager()))));
-        TestViewHelpers.dragToWorkspace(widget, false);
+        widgets.
+                getWidget(mWidgetInfo.getLabel(mTargetContext.getPackageManager())).
+                dragToWorkspace();
         // 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 0061568..f9d1d93 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -15,26 +15,21 @@
  */
 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;
 import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiObject2;
-import android.view.View;
 
-import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
-import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ShellCommandRule;
-import com.android.launcher3.widget.WidgetCell;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,45 +41,50 @@
 @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();
-    }
-
-    // Convert to TAPL b/131116002
-    private void performTest() throws Throwable {
+    @PortraitLandscape
+    public void testDragIcon() throws Throwable {
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
         final LauncherAppWidgetProviderInfo widgetInfo =
                 TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
 
-        // Open widget tray and wait for load complete.
-        final UiObject2 widgetContainer = TestViewHelpers.openWidgetsTray();
-        Wait.atMost(null, Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT);
+        mLauncher.
+                getWorkspace().
+                openAllWidgets().
+                getWidget(widgetInfo.getLabel(mTargetContext.getPackageManager())).
+                dragToWorkspace();
 
-        // Drag widget to homescreen
-        UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class)
-                .hasDescendant(By.text(widgetInfo.getLabel(mTargetContext.getPackageManager()))));
-        TestViewHelpers.dragToWorkspace(widget, false);
-
-        assertTrue(mActivityMonitor.itemExists(new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View view) {
-                return info instanceof LauncherAppWidgetInfo &&
+        assertTrue(mActivityMonitor.itemExists(
+                (info, view) -> info instanceof LauncherAppWidgetInfo &&
                         ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
-                                widgetInfo.provider.getClassName());
-            }
-        }).call());
+                                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());
+    }
+
+    /**
+     * 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() throws Throwable {
+        clearHomescreen();
+        mDevice.pressHome();
+        mLauncher.getWorkspace().openAllWidgets()
+                .getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
+                .dragToWorkspace();
+        mLauncher.getWorkspace().getWorkspaceAppIcon("Shortcut")
+                .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 d36126b..f42bf1f 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.WidgetHostViewLoader;
@@ -54,7 +55,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Tests for bind widget flow.
@@ -267,8 +270,7 @@
         resetLoaderState();
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
-        waitForModelLoaded();
+        mDevice.pressHome();
     }
 
     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
@@ -326,9 +328,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).updateAndGetActiveSessionCache()
+                    .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 6122dae..07129dd 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -128,10 +128,8 @@
         if (!Utilities.ATLEAST_OREO) {
             return;
         }
-        lockRotation(true);
-
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
         // Open Pin item activity
         BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
index b564a1a..d85dd3a 100644
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ b/tests/src/com/android/launcher3/util/Condition.java
@@ -1,8 +1,8 @@
 package com.android.launcher3.util;
 
-import androidx.test.uiautomator.UiObject2;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
-import com.android.launcher3.MainThreadExecutor;
+import androidx.test.uiautomator.UiObject2;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -16,7 +16,7 @@
      * Converts the condition to be run on UI thread.
      */
     static Condition runOnUiThread(final Condition condition) {
-        final MainThreadExecutor executor = new MainThreadExecutor();
+        final LooperExecutor executor = MAIN_EXECUTOR;
         return () -> {
             final AtomicBoolean value = new AtomicBoolean(false);
             final Throwable[] exceptions = new Throwable[1];
diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
index 0235f95..8f89173 100644
--- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
+++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX;
 
@@ -23,7 +24,6 @@
 import static org.junit.Assert.fail;
 
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -72,9 +72,7 @@
     private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler();
 
     private static Handler createEventResumeHandler() {
-        final HandlerThread thread = new HandlerThread("RaceConditionEventResumer");
-        thread.start();
-        return new Handler(thread.getLooper());
+        return new Handler(createAndStartNewLooper("RaceConditionEventResumer"));
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 593cce8..899686b 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.util;
 
 import android.os.SystemClock;
+import android.util.Log;
 
 import org.junit.Assert;
 
@@ -16,7 +17,9 @@
     }
 
     public static void atMost(String message, Condition condition, long timeout, long sleepMillis) {
-        long endTime = SystemClock.uptimeMillis() + timeout;
+        final long startTime = SystemClock.uptimeMillis();
+        long endTime = startTime + timeout;
+        Log.d("Wait", "atMost: " + startTime + " - " + endTime);
         while (SystemClock.uptimeMillis() < endTime) {
             try {
                 if (condition.isTrue()) {
@@ -36,6 +39,7 @@
         } catch (Throwable t) {
             throw new RuntimeException(t);
         }
+        Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
         Assert.fail(message);
     }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index eef2f24..cdda0f0 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -15,17 +15,16 @@
 
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
-    private static int sScreenshotCount = 0;
     final private UiDevice mDevice;
 
     public FailureWatcher(UiDevice device) {
         mDevice = device;
     }
 
-    private void dumpViewHierarchy() {
+    private static void dumpViewHierarchy(UiDevice device) {
         final ByteArrayOutputStream stream = new ByteArrayOutputStream();
         try {
-            mDevice.dumpWindowHierarchy(stream);
+            device.dumpWindowHierarchy(stream);
             stream.flush();
             stream.close();
             for (String line : stream.toString().split("\\r?\\n")) {
@@ -38,22 +37,27 @@
 
     @Override
     protected void failed(Throwable e, Description description) {
-        if (mDevice == null) return;
+        onError(mDevice, description, e);
+    }
+
+    public static void onError(UiDevice device, Description description, Throwable e) {
+        if (device == null) return;
         final String pathname = getInstrumentation().getTargetContext().
-                getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png";
+                getFilesDir().getPath() + "/TestScreenshot-" + description.getMethodName()
+                + ".png";
         Log.e(TAG, "Failed test " + description.getMethodName() +
                 ", screenshot will be saved to " + pathname +
                 ", track trace is below, UI object dump is further below:\n" +
                 Log.getStackTraceString(e));
-        dumpViewHierarchy();
+        dumpViewHierarchy(device);
 
         try {
-            final String dumpsysResult = mDevice.executeShellCommand(
+            final String dumpsysResult = device.executeShellCommand(
                     "dumpsys activity service TouchInteractionService");
             Log.d(TAG, "TouchInteractionService: " + dumpsysResult);
         } catch (IOException ex) {
         }
 
-        mDevice.takeScreenshot(new File(pathname));
+        device.takeScreenshot(new File(pathname));
     }
 }
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 2aba7a5..62fe26d 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -15,15 +15,11 @@
  */
 package com.android.launcher3.util.rule;
 
-import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
 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;
@@ -52,26 +48,15 @@
     }
 
     public Callable<Boolean> itemExists(final ItemOperator op) {
-        return new Callable<Boolean>() {
-
-            @Override
-            public Boolean call() throws Exception {
-                Launcher launcher = getActivity();
-                if (launcher == null) {
-                    return false;
-                }
-                return launcher.getWorkspace().getFirstMatch(op) != null;
+        return () -> {
+            Launcher launcher = getActivity();
+            if (launcher == null) {
+                return false;
             }
+            return launcher.getWorkspace().getFirstMatch(op) != null;
         };
     }
 
-    /**
-     * Starts the launcher activity in the target package.
-     */
-    public void startLauncher() {
-        getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext()));
-    }
-
     private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
 
         private final Statement mBase;
@@ -100,19 +85,27 @@
         }
 
         @Override
-        public void onActivityStarted(Activity activity) { }
+        public void onActivityStarted(Activity activity) {
+            if (activity instanceof Launcher) {
+                mActivity.getRotationHelper().forceAllowRotationForTesting(true);
+            }
+        }
 
         @Override
-        public void onActivityResumed(Activity activity) { }
+        public void onActivityResumed(Activity activity) {
+        }
 
         @Override
-        public void onActivityPaused(Activity activity) { }
+        public void onActivityPaused(Activity activity) {
+        }
 
         @Override
-        public void onActivityStopped(Activity activity) { }
+        public void onActivityStopped(Activity activity) {
+        }
 
         @Override
-        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
+        }
 
         @Override
         public void onActivityDestroyed(Activity activity) {
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/AndroidManifest.xml b/tests/tapl/AndroidManifest.xml
index 0207e2b..1065446 100644
--- a/tests/tapl/AndroidManifest.xml
+++ b/tests/tapl/AndroidManifest.xml
@@ -23,4 +23,6 @@
 >
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 </manifest>
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index 7f561a2..03d1600 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -16,10 +16,16 @@
 
 package com.android.launcher3.tapl;
 
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import java.util.regex.Pattern;
+
 public class AddToHomeScreenPrompt {
+    private static final Pattern ADD_AUTOMATICALLY =
+            Pattern.compile("^Add automatically$", CASE_INSENSITIVE);
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mWidgetCell;
 
@@ -33,9 +39,6 @@
     public void addAutomatically() {
         mLauncher.waitForObjectInContainer(
                 mWidgetCell.getParent().getParent().getParent().getParent(),
-                By.text(LauncherInstrumentation.isAvd()
-                        ? "ADD AUTOMATICALLY"
-                        : "Add automatically")).
-                click();
+                By.text(ADD_AUTOMATICALLY)).click();
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 9ff354a..96e4b8c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -36,6 +36,7 @@
     private static final int MAX_SCROLL_ATTEMPTS = 40;
 
     private final int mHeight;
+    private final int mIconHeight;
 
     AllApps(LauncherInstrumentation launcher) {
         super(launcher);
@@ -46,6 +47,10 @@
         // Wait for the recycler to populate.
         mLauncher.waitForObjectInContainer(appListRecycler, By.clazz(TextView.class));
         verifyNotFrozen("All apps freeze flags upon opening all apps");
+        mIconHeight = mLauncher.getTestInfo(
+                TestProtocol.REQUEST_ICON_HEIGHT)
+                .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+
     }
 
     @Override
@@ -62,6 +67,10 @@
         }
         final Rect iconBounds = icon.getVisibleBounds();
         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
+        if (iconBounds.height() < mIconHeight / 2) {
+            LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
+            return false;
+        }
         if (iconCenterInSearchBox(allAppsContainer, icon)) {
             LauncherInstrumentation.log("hasClickableIcon: icon center is under search box");
             return false;
@@ -90,6 +99,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                     "apps_list_view");
+            final UiObject2 searchBox = getSearchBox(allAppsContainer);
             allAppsContainer.setGestureMargins(
                     0,
                     getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
@@ -103,7 +113,11 @@
                 int scroll = getScroll(allAppsContainer);
                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
                     while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
-                        mLauncher.scroll(allAppsContainer, Direction.DOWN, 0.8f, null, 50);
+                        mLauncher.scrollToLastVisibleRow(
+                                allAppsContainer,
+                                mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
+                                searchBox.getVisibleBounds().bottom -
+                                        allAppsContainer.getVisibleBounds().top);
                         final int newScroll = getScroll(allAppsContainer);
                         if (newScroll == scroll) break;
 
@@ -120,7 +134,7 @@
             mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName,
                     hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector));
 
-            final UiObject2 appIcon = mLauncher.getObjectInContainer(appListRecycler,
+            final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler,
                     appIconSelector);
             return new AppIcon(mLauncher, appIcon);
         }
@@ -145,7 +159,7 @@
                         "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")) {
@@ -172,7 +186,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 +200,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 c9eaf27..db3d846 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -23,6 +23,7 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
 
@@ -54,12 +55,12 @@
                 "want to switch from background to overview")) {
             verifyActiveContainer();
             goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL);
-            return new BaseOverview(mLauncher);
+            return mLauncher.isFallbackOverview() ?
+                    new BaseOverview(mLauncher) : new Overview(mLauncher);
         }
     }
 
     protected void goToOverviewUnchecked(int expectedState) {
-        mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
                 final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
@@ -112,7 +113,60 @@
                 mLauncher.waitForSystemUiObject("recent_apps").click();
                 break;
         }
-        mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+    }
+
+    /**
+     * Swipes right or double presses the square button to switch to the previous app.
+     */
+    public Background quickSwitchToPreviousApp() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to quick switch to the previous app")) {
+            verifyActiveContainer();
+            quickSwitchToPreviousApp(getExpectedStateForQuickSwitch());
+            return new Background(mLauncher);
+        }
+    }
+
+    protected int getExpectedStateForQuickSwitch() {
+        return BACKGROUND_APP_STATE_ORDINAL;
+    }
+
+    protected void quickSwitchToPreviousApp(int expectedState) {
+        boolean transposeInLandscape = false;
+        switch (mLauncher.getNavigationModel()) {
+            case TWO_BUTTON:
+                transposeInLandscape = true;
+                // Fall through, zero button and two button modes behave the same.
+            case ZERO_BUTTON: {
+                final int startX;
+                final int startY;
+                final int endX;
+                final int endY;
+                if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+                    // Swipe from the bottom left to the bottom right of the screen.
+                    startX = 0;
+                    startY = getSwipeStartY();
+                    endX = mLauncher.getDevice().getDisplayWidth();
+                    endY = startY;
+                } else {
+                    // Swipe from the bottom right to the top right of the screen.
+                    startX = getSwipeStartX();
+                    startY = mLauncher.getRealDisplaySize().y - 1;
+                    endX = startX;
+                    endY = 0;
+                }
+                mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState);
+                break;
+            }
+
+            case THREE_BUTTON:
+                // Double press the recents button.
+                UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+                recentsButton.click();
+                mLauncher.getOverview();
+                recentsButton.click();
+                break;
+        }
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ace49e9..49c0c89 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,11 +16,15 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Rect;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.Collections;
 import java.util.List;
 
@@ -28,7 +32,6 @@
  * Common overview pane for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
-    private static final int FLING_SPEED = LauncherInstrumentation.isAvd() ? 500 : 1500;
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
 
     BaseOverview(LauncherInstrumentation launcher) {
@@ -38,7 +41,7 @@
 
     @Override
     protected LauncherInstrumentation.ContainerType getContainerType() {
-        return LauncherInstrumentation.ContainerType.BASE_OVERVIEW;
+        return LauncherInstrumentation.ContainerType.FALLBACK_OVERVIEW;
     }
 
     /**
@@ -49,9 +52,10 @@
                      mLauncher.addContextLayer("want to fling forward in overview")) {
             LauncherInstrumentation.log("Overview.flingForward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            overview.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
-            overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-            mLauncher.waitForIdle();
+            final int leftMargin = mLauncher.getTestInfo(
+                    TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
+                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin, 0, 0, 0), 20);
             verifyActiveContainer();
         }
     }
@@ -70,7 +74,7 @@
                 flingForward();
             }
 
-            mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
+            mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector).click();
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "dismissed all tasks")) {
                 return new Workspace(mLauncher);
@@ -86,9 +90,10 @@
                      mLauncher.addContextLayer("want to fling backward in overview")) {
             LauncherInstrumentation.log("Overview.flingBackward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            overview.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
-            overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-            mLauncher.waitForIdle();
+            final int rightMargin = mLauncher.getTestInfo(
+                    TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
+                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin, 0), 20);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index cfc4374..e0fe933 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 
 import androidx.annotation.NonNull;
 
@@ -58,4 +59,9 @@
             }
         }
     }
+
+    @Override
+    protected int getExpectedStateForQuickSwitch() {
+        return QUICK_SWITCH_STATE_ORDINAL;
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index d4bdafa..df80a51 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import android.graphics.Point;
 
 import androidx.test.uiautomator.By;
@@ -23,13 +25,10 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.TestProtocol;
-
 /**
  * Ancestor for AppIcon and AppMenuItem.
  */
 abstract class Launchable {
-    private static final int WAIT_TIME_MS = 60000;
     protected final LauncherInstrumentation mLauncher;
 
     protected final UiObject2 mObject;
@@ -53,11 +52,12 @@
     private Background launch(BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
                 mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
-        mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
-        mLauncher.assertTrue(
-                "Launching an app didn't open a new window: " + mObject.getText(),
-                mObject.clickAndWait(Until.newWindow(), WAIT_TIME_MS));
-        mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+
+        mLauncher.executeAndWaitForEvent(
+                () -> mObject.click(),
+                event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                "Launching an app didn't open a new window: " + mObject.getText());
+
         mLauncher.assertTrue(
                 "App didn't start: " + selector,
                 mLauncher.getDevice().wait(Until.hasObject(selector),
@@ -68,7 +68,7 @@
     /**
      * Drags an object to the center of homescreen.
      */
-    public Workspace dragToWorkspace() {
+    public void dragToWorkspace() {
         final Point launchableCenter = getObject().getVisibleCenter();
         final Point displaySize = mLauncher.getRealDisplaySize();
         final int width = displaySize.x / 2;
@@ -80,10 +80,6 @@
                                 launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
                         displaySize.y / 2),
                 getLongPressIndicator());
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "dragged launchable to workspace")) {
-            return new Workspace(mLauncher);
-        }
     }
 
     protected abstract String getLongPressIndicator();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a7e6336..c52650d 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,6 +60,7 @@
 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;
 
@@ -68,10 +69,14 @@
 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.Function;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -82,11 +87,12 @@
     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.
-    enum ContainerType {
-        WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
+    public enum ContainerType {
+        WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, FALLBACK_OVERVIEW
     }
 
     public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
@@ -122,7 +128,7 @@
     private static final String APPS_RES_ID = "apps_view";
     private static final String OVERVIEW_RES_ID = "overview_panel";
     private static final String WIDGETS_RES_ID = "widgets_list_view";
-    public static final int WAIT_TIME_MS = 60000;
+    public static final int WAIT_TIME_MS = 10000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
@@ -132,10 +138,22 @@
     private int mExpectedRotation = Surface.ROTATION_0;
     private final Uri mTestProviderUri;
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
+    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);
@@ -173,6 +191,7 @@
         PackageManager pm = getContext().getPackageManager();
         ProviderInfo pi = pm.resolveContentProvider(
                 testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS);
+        assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
         ComponentName cn = new ComponentName(pi.packageName, pi.name);
 
         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
@@ -206,7 +225,7 @@
         try {
             // Workaround, use constructed context because both the instrumentation context and the
             // app context are not constructed with resources that take overlays into account
-            final Context ctx = baseContext.createPackageContext("android", 0);
+            final Context ctx = baseContext.createPackageContext(getLauncherPackageName(), 0);
             for (int i = 0; i < 100; ++i) {
                 final int currentInteractionMode = getCurrentInteractionMode(ctx);
                 final NavigationModel model = getNavigationModel(currentInteractionMode);
@@ -232,10 +251,6 @@
         return null;
     }
 
-    public static boolean isAvd() {
-        return Build.MODEL.contains("Cuttlefish");
-    }
-
     static void log(String message) {
         Log.d(TAG, message);
     }
@@ -263,10 +278,79 @@
         }
     }
 
+    private String getAnomalyMessage() {
+        UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
+        if (object != null) {
+            return "System alert popup is visible: " + object.getText();
+        }
+
+        object = mDevice.findObject(By.res("android", "message"));
+        if (object != null) {
+            return "Message popup by " + object.getApplicationPackage() + " is visible: "
+                    + object.getText();
+        }
+
+        if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
+
+        if (!mDevice.hasObject(By.textStartsWith(""))) return "Screen is empty";
+
+        return null;
+    }
+
+    private String getVisibleStateMessage() {
+        if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
+        if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
+        if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
+        if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
+        return "Background";
+    }
+
+    public void setSystemHealthSupplier(Function<Long, String> supplier) {
+        this.mSystemHealthSupplier = supplier;
+    }
+
+    public void setOnSettledStateAction(Consumer<ContainerType> onSettledStateAction) {
+        mOnSettledStateAction = onSettledStateAction;
+    }
+
+    private String getSystemHealthMessage() {
+        final String testPackage = getContext().getPackageName();
+        try {
+            mDevice.executeShellCommand("pm grant " + testPackage +
+                    " android.permission.READ_LOGS");
+            mDevice.executeShellCommand("pm grant " + testPackage +
+                    " android.permission.PACKAGE_USAGE_STATS");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return mSystemHealthSupplier != null
+                ? mSystemHealthSupplier.apply(START_TIME)
+                : TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
+    }
+
     private void fail(String message) {
-        log("Hierarchy dump for: " + getContextDescription() + message);
+        message = "http://go/tapl : " + getContextDescription() + message;
+
+        final String anomaly = getAnomalyMessage();
+        if (anomaly != null) {
+            message = anomaly + ", which causes:\n" + message;
+        } else {
+            message = message + " (visible state: " + getVisibleStateMessage() + ")";
+        }
+
+        final String systemHealth = getSystemHealthMessage();
+        if (systemHealth != null) {
+            message = message
+                    + ", which might be a consequence of system health "
+                    + "problems:\n<<<<<<<<<<<<<<<<<<\n"
+                    + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
+        }
+
+        log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
-        Assert.fail("http://go/tapl : " + getContextDescription() + message);
+
+        Assert.fail(message);
     }
 
     private String getContextDescription() {
@@ -293,7 +377,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 + "'");
         }
@@ -331,12 +415,33 @@
     }
 
     private UiObject2 verifyContainerType(ContainerType containerType) {
+        waitForLauncherInitialized();
+
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
+
+        // b/136278866
+        for (int i = 0; i != 100; ++i) {
+            if (getNavigationModeMismatchError() == null) break;
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
         final String error = getNavigationModeMismatchError();
         assertTrue(error, error == null);
         log("verifyContainerType: " + containerType);
 
+        final UiObject2 container = verifyVisibleObjects(containerType);
+
+        if (mOnSettledStateAction != null) mOnSettledStateAction.accept(containerType);
+
+        return container;
+    }
+
+    private UiObject2 verifyVisibleObjects(ContainerType containerType) {
         try (Closable c = addContextLayer(
                 "but the current state is not " + containerType.name())) {
             switch (containerType) {
@@ -373,7 +478,7 @@
 
                     return waitForLauncherObject(OVERVIEW_RES_ID);
                 }
-                case BASE_OVERVIEW: {
+                case FALLBACK_OVERVIEW: {
                     return waitForFallbackLauncherObject(OVERVIEW_RES_ID);
                 }
                 case BACKGROUND: {
@@ -390,6 +495,18 @@
         }
     }
 
+    private void waitForLauncherInitialized() {
+        for (int i = 0; i < 100; ++i) {
+            if (getTestInfo(
+                    TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
+                    getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) {
+                return;
+            }
+            SystemClock.sleep(100);
+        }
+        fail("Launcher didn't initialize");
+    }
+
     Parcelable executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, String message) {
         try {
@@ -426,6 +543,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")) {
@@ -433,13 +553,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;
 
@@ -571,13 +695,6 @@
     }
 
     @NonNull
-    UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) {
-        final UiObject2 object = container.findObject(selector);
-        assertNotNull("Can't find an object with selector: " + selector, object);
-        return object;
-    }
-
-    @NonNull
     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
         return container.findObjects(getLauncherObjectSelector(resName));
     }
@@ -614,12 +731,12 @@
 
     @NonNull
     UiObject2 waitForLauncherObject(BySelector selector) {
-        return waitForObjectBySelector(selector.pkg(getLauncherPackageName()));
+        return waitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()));
     }
 
     @NonNull
     UiObject2 tryWaitForLauncherObject(BySelector selector, long timeout) {
-        return tryWaitForObjectBySelector(selector.pkg(getLauncherPackageName()), timeout);
+        return tryWaitForObjectBySelector(By.copy(selector).pkg(getLauncherPackageName()), timeout);
     }
 
     @NonNull
@@ -649,6 +766,10 @@
         return mDevice.getLauncherPackageName();
     }
 
+    boolean isFallbackOverview() {
+        return !getOverviewPackageName().equals(getLauncherPackageName());
+    }
+
     @NonNull
     public UiDevice getDevice() {
         return mDevice;
@@ -658,14 +779,42 @@
         final Bundle parcel = (Bundle) executeAndWaitForEvent(
                 () -> linearGesture(startX, startY, endX, endY, steps),
                 event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()),
-                "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY
-                        + ", " + endX + ", " + endY);
+                "Swipe failed to receive an event for the swipe end");
         assertEquals("Swipe switched launcher to a wrong state;",
                 TestProtocol.stateOrdinalToString(expectedState),
                 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;
@@ -683,19 +832,35 @@
             case UP: {
                 startX = endX = rect.centerX();
                 final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
-                startY = (int) (vertCenter - halfGestureHeight);
+                final float halfGestureHeight = rect.height() / 2.0f;
+                startY = (int) (vertCenter - halfGestureHeight) + 1;
                 endY = (int) (vertCenter + halfGestureHeight);
             }
             break;
             case DOWN: {
                 startX = endX = rect.centerX();
                 final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
-                startY = (int) (vertCenter + halfGestureHeight);
+                final float halfGestureHeight = rect.height() / 2.0f;
+                startY = (int) (vertCenter + halfGestureHeight) - 1;
                 endY = (int) (vertCenter - halfGestureHeight);
             }
             break;
+            case LEFT: {
+                startY = endY = rect.centerY();
+                final int horizCenter = rect.centerX();
+                final float halfGestureWidth = rect.width() / 2.0f;
+                startX = (int) (horizCenter - halfGestureWidth) + 1;
+                endX = (int) (horizCenter + halfGestureWidth);
+            }
+            break;
+            case RIGHT: {
+                startY = endY = rect.centerY();
+                final int horizCenter = rect.centerX();
+                final float halfGestureWidth = rect.width() / 2.0f;
+                startX = (int) (horizCenter + halfGestureWidth) - 1;
+                endX = (int) (horizCenter - halfGestureWidth);
+            }
+            break;
             default:
                 fail("Unsupported direction");
                 return;
@@ -711,6 +876,7 @@
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
     void linearGesture(int startX, int startY, int endX, int endY, int steps) {
+        log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
         final long downTime = SystemClock.uptimeMillis();
         final Point start = new Point(startX, startY);
         final Point end = new Point(endX, endY);
@@ -723,10 +889,6 @@
         mDevice.waitForIdle();
     }
 
-    float getDisplayDensity() {
-        return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
-    }
-
     int getTouchSlop() {
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
@@ -760,6 +922,7 @@
     }
 
     long movePointer(long downTime, long startTime, long duration, Point from, Point to) {
+        log("movePointer: " + from + " to " + to);
         final Point point = new Point();
         long steps = duration / GESTURE_STEP_MS;
         long currentTime = startTime;
@@ -812,7 +975,7 @@
     int getEdgeSensitivityWidth() {
         try {
             final Context context = mInstrumentation.getTargetContext().createPackageContext(
-                    "android", 0);
+                    getLauncherPackageName(), 0);
             return context.getResources().getDimensionPixelSize(
                     getSystemDimensionResId(context, "config_backGestureInset")) + 1;
         } catch (PackageManager.NameNotFoundException e) {
@@ -826,4 +989,25 @@
         getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
         return size;
     }
+
+    public void enableDebugTracing() {
+        getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
+    }
+
+    public void disableDebugTracing() {
+        getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
+    }
+
+    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);
+    }
 }
\ 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 058831f..da68da3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Overview.java
+++ b/tests/tapl/com/android/launcher3/tapl/Overview.java
@@ -19,9 +19,9 @@
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 
 import androidx.annotation.NonNull;
-import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Overview pane.
@@ -51,11 +51,15 @@
 
             // Swipe from an app icon to the top.
             LauncherInstrumentation.log("Overview.switchToAllApps before swipe");
-            final UiObject2 allApps = mLauncher.waitForLauncherObject("apps_view");
-            mLauncher.swipeToState(mLauncher.getDevice().getDisplayWidth() / 2,
-                    allApps.getVisibleBounds().top,
+            mLauncher.swipeToState(
                     mLauncher.getDevice().getDisplayWidth() / 2,
-                    0, 50, ALL_APPS_STATE_ORDINAL);
+                    mLauncher.getTestInfo(
+                            TestProtocol.REQUEST_HOTSEAT_TOP).
+                            getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD),
+                    mLauncher.getDevice().getDisplayWidth() / 2,
+                    0,
+                    50,
+                    ALL_APPS_STATE_ORDINAL);
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped all way up from overview")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 641c413..91f0fc4 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,18 +16,16 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import android.graphics.Rect;
 
 import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.launcher3.testing.TestProtocol;
 
 /**
  * A recent task in the overview panel carousel.
  */
 public final class OverviewTask {
-    private static final long WAIT_TIME_MS = 60000;
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
     private final BaseOverview mOverview;
@@ -64,14 +62,14 @@
      */
     public Background open() {
         verifyActiveContainer();
-        mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "clicking an overview task")) {
-            mLauncher.assertTrue("Launching task didn't open a new window: " +
-                            mTask.getParent().getContentDescription(),
-                    mTask.clickAndWait(Until.newWindow(), WAIT_TIME_MS));
+            mLauncher.executeAndWaitForEvent(
+                    () -> mTask.click(),
+                    event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                    "Launching task didn't open a new window: " +
+                            mTask.getParent().getContentDescription());
         }
-        mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
         return new Background(mLauncher);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 93554d2..0c9fda3 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -26,7 +26,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.os.DropBoxManager;
 
+import org.junit.Assert;
+
+import java.util.Date;
 import java.util.List;
 
 public class TestHelpers {
@@ -81,4 +85,69 @@
         }
         return "com.android.systemui";
     }
+
+    private static String truncateCrash(String text, int maxLines) {
+        String[] lines = text.split("\\r?\\n");
+        StringBuilder ret = new StringBuilder();
+        for (int i = 0; i < maxLines && i < lines.length; i++) {
+            ret.append(lines[i]);
+            ret.append('\n');
+        }
+        if (lines.length > maxLines) {
+            ret.append("... ");
+            ret.append(lines.length - maxLines);
+            ret.append(" more lines truncated ...\n");
+        }
+        return ret.toString();
+    }
+
+    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 = startTime;
+        DropBoxManager.Entry entry;
+        StringBuilder errorDetails = new StringBuilder();
+        while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+            errorDetails.append("------------------------------\n");
+            timestamp = entry.getTimeMillis();
+            errorDetails.append(new Date(timestamp));
+            errorDetails.append(": ");
+            errorDetails.append(entry.getTag());
+            errorDetails.append(": ");
+            final String dropboxSnippet = entry.getText(4096);
+            if (dropboxSnippet != null) errorDetails.append(truncateCrash(dropboxSnippet, 40));
+            errorDetails.append("    ...\n");
+            entry.close();
+        }
+        return errorDetails.length() != 0 ? errorDetails.toString() : null;
+    }
+
+    public static String getSystemHealthMessage(Context context, long startTime) {
+        try {
+            StringBuilder errors = new StringBuilder();
+
+            final String[] labels = {
+                    "system_app_anr",
+                    "system_app_crash",
+                    "system_app_native_crash",
+                    "system_server_anr",
+                    "system_server_crash",
+                    "system_server_native_crash",
+                    "system_server_watchdog",
+            };
+
+            for (String label : labels) {
+                final String crash = checkCrash(context, label, startTime);
+                if (crash != null) errors.append(crash);
+            }
+
+            return errors.length() != 0
+                    ? "Current time: " + new Date(System.currentTimeMillis()) + "\n" + errors
+                    : null;
+        } catch (Exception e) {
+            return "Failed to get system health diags, maybe build your test via .bp instead of "
+                    + ".mk? " + android.util.Log.getStackTraceString(e);
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 128789d..1b6d8c4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -18,7 +18,16 @@
 
 import androidx.test.uiautomator.UiObject2;
 
-public class Widget {
-    Widget(LauncherInstrumentation launcher, UiObject2 widget) {
+/**
+ * Widget in workspace or a widget list.
+ */
+public final class Widget extends Launchable {
+    Widget(LauncherInstrumentation launcher, UiObject2 icon) {
+        super(launcher, icon);
+    }
+
+    @Override
+    protected String getLongPressIndicator() {
+        return "drop_target_bar";
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 94003be..51239c9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,16 +16,21 @@
 
 package com.android.launcher3.tapl;
 
+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);
@@ -40,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)),
+                    FLING_STEPS);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
@@ -60,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();
             }
@@ -75,4 +81,35 @@
     protected LauncherInstrumentation.ContainerType getContainerType() {
         return LauncherInstrumentation.ContainerType.WIDGETS;
     }
+
+    public Widget getWidget(String labelText) {
+        final UiObject2 widgetsContainer = verifyActiveContainer();
+        final Point displaySize = mLauncher.getRealDisplaySize();
+        final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+
+        int i = 0;
+        for (; ; ) {
+            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);
+                }
+            }
+
+            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 b01b6f3..0aa36dd 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) {
@@ -67,7 +67,6 @@
                     "switchToAllApps: swipeHeight = " + swipeHeight + ", slop = "
                             + mLauncher.getTouchSlop());
 
-            mLauncher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
             mLauncher.swipeToState(
                     start.x,
                     start.y,
@@ -75,7 +74,6 @@
                     start.y - swipeHeight - mLauncher.getTouchSlop(),
                     60,
                     ALL_APPS_STATE_ORDINAL);
-            mLauncher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "swiped to all apps")) {
@@ -110,10 +108,13 @@
      */
     @NonNull
     public AppIcon getWorkspaceAppIcon(String appName) {
-        return new AppIcon(mLauncher,
-                mLauncher.getObjectInContainer(
-                        verifyActiveContainer(),
-                        AppIcon.getAppIconSelector(appName, mLauncher)));
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get a workspace icon")) {
+            return new AppIcon(mLauncher,
+                    mLauncher.waitForObjectInContainer(
+                            verifyActiveContainer(),
+                            AppIcon.getAppIconSelector(appName, mLauncher)));
+        }
     }
 
     /**
@@ -139,25 +140,24 @@
     }
 
     private boolean isWorkspaceScrollable(UiObject2 workspace) {
-        return workspace.isScrollable();
+        return workspace.getChildCount() > 1;
     }
 
     @NonNull
     public AppIcon getHotseatAppIcon(String appName) {
-        return new AppIcon(mLauncher, mLauncher.getObjectInContainer(
+        return new AppIcon(mLauncher, mLauncher.waitForObjectInContainer(
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
     @NonNull
     public Folder getHotseatFolder(String appName) {
-        return new Folder(mLauncher, mLauncher.getObjectInContainer(
+        return new Folder(mLauncher, mLauncher.waitForObjectInContainer(
                 mHotseat, Folder.getSelector(appName, mLauncher)));
     }
 
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
             String longPressIndicator) {
-        launcher.getTestInfo(TestProtocol.REQUEST_ENABLE_DEBUG_TRACING);
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
@@ -172,7 +172,6 @@
                 downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);
         LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
-        launcher.getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING);
     }
 
     /**
@@ -181,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(), 0),
+                FLING_STEPS);
         verifyActiveContainer();
     }
 
@@ -193,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(), 0, 0, 0),
+                FLING_STEPS);
         verifyActiveContainer();
     }