Merge "Rotate quickswitch task thumbnails" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3a9a2c3..a099ada 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,9 +78,8 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    Launcher3CommonDepsLib \
-    SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, src_shortcuts_overrides) \
@@ -154,9 +153,7 @@
 endif
 LOCAL_MODULE := Launcher3QuickStepLib
 LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    Launcher3CommonDepsLib \
-    SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 555cc73..26698eb 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -184,5 +184,20 @@
             android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
             android:exported="true"
             android:enabled="false" />
+
+        <!--
+        Launcher activity for secondary display
+        -->
+        <activity
+            android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
+            android:theme="@style/AppTheme"
+            android:launchMode="singleTop"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.SECONDARY_HOME" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
index 97eef66..22f1f23 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -30,6 +30,8 @@
 
     private static final String TAG = "GraphicsUtils";
 
+    public static Runnable sOnNewBitmapRunnable = () -> { };
+
     /**
      * Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
      * it bounds the alpha in valid range instead of throwing an exception to allow for safer
@@ -77,5 +79,7 @@
     /**
      * Utility method to track new bitmap creation
      */
-    public static void noteNewBitmapCreated() { }
+    public static void noteNewBitmapCreated() {
+        sOnNewBitmapRunnable.run();
+    }
 }
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index a7cd167..d94c665 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -34,10 +34,10 @@
         android:orientation="vertical">
 
         <TextView
+            style="@style/TextHeadline"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="18dp"
-            android:fontFamily="google-sans"
             android:paddingLeft="@dimen/bottom_sheet_edu_padding"
             android:paddingRight="@dimen/bottom_sheet_edu_padding"
             android:text="@string/hotseat_migrate_title"
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 06b9f1f..632b9b5 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
@@ -317,8 +317,8 @@
                 && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
             return;
         }
-        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            HotseatPredictionController.fillInHybridHotseatRank(itemInfo, target);
+        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+            HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
             return;
         }
 
@@ -327,7 +327,7 @@
         IntStream.range(0, predictedApps.size())
                 .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
                 .findFirst()
-                .ifPresent((rank) -> target.predictedRank = rank);
+                .ifPresent((rank) -> target.predictedRank = 0 - rank);
     }
 
     public static class PredictionState {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index c2b55ab..cc6ec69 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -68,6 +69,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.OptionalInt;
 import java.util.stream.IntStream;
 
 /**
@@ -442,6 +444,20 @@
         mHotseat.invalidate();
     }
 
+    /**
+     * Unpins pinned app when it's converted into a folder
+     */
+    public void folderCreatedFromIcon(ItemInfo info, FolderInfo folderInfo) {
+        AppTarget target = getAppTargetFromItemInfo(info);
+        if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && !isInHotseat(
+                info)) {
+            notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+        } else if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
+                && folderInfo.screenId == Workspace.FIRST_SCREEN_ID && !isInFirstPage(info)) {
+            notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+        }
+    }
+
     @Override
     public void onDragEnd() {
         if (mDragObject == null) {
@@ -548,12 +564,11 @@
      * Fill in predicted_rank field based on app prediction.
      * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
      */
-    public static void fillInHybridHotseatRank(
+    public static void encodeHotseatLayoutIntoPredictionRank(
             @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
         QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
         if (launcher == null || launcher.getHotseatPredictionController() == null
-                || itemInfo.getTargetComponent() == null
-                || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+                || itemInfo.getTargetComponent() == null) {
             return;
         }
         HotseatPredictionController controller = launcher.getHotseatPredictionController();
@@ -561,11 +576,12 @@
         final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
 
         final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
-        IntStream.range(0, predictedApps.size())
+        OptionalInt rank = IntStream.range(0, predictedApps.size())
                 .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
-                .findFirst()
-                .ifPresent((rank) -> target.predictedRank =
-                        Integer.parseInt(controller.mPredictedSpotsCount + "0" + rank));
+                .findFirst();
+
+        target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+                + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
     }
 
     private static boolean isPredictedIcon(View view) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index bd89626..b2e1798 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -47,6 +47,7 @@
 
     private static final float RING_EFFECT_RATIO = 0.11f;
 
+    boolean mIsDrawingDot = false;
     private final DeviceProfile mDeviceProfile;
     private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private boolean mIsPinned = false;
@@ -81,6 +82,16 @@
     }
 
     @Override
+    protected void drawDotIfNecessary(Canvas canvas) {
+        mIsDrawingDot = true;
+        int count = canvas.save();
+        canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+        super.drawDotIfNecessary(canvas);
+        canvas.restoreToCount(count);
+        mIsDrawingDot = false;
+    }
+
+    @Override
     public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
         super.applyFromWorkspaceItem(info);
         int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
@@ -112,7 +123,7 @@
     @Override
     public void getIconBounds(Rect outBounds) {
         super.getIconBounds(outBounds);
-        if (!mIsPinned) {
+        if (!mIsPinned && !mIsDrawingDot) {
             int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
             outBounds.inset(predictionInset, predictionInset);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index c359423..b87fcf2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -26,11 +26,14 @@
 import android.view.Gravity;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.popup.SystemShortcut;
@@ -169,6 +172,16 @@
     }
 
     @Override
+    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
+            int screenId, int cellX, int cellY) {
+        FolderIcon fi =  super.addFolder(layout, info, container, screenId, cellX, cellY);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.folderCreatedFromIcon(info, fi.getFolder().getInfo());
+        }
+        return fi;
+    }
+
+    @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
         if (mHotseatPredictionController != null) {
             return Stream.concat(super.getSupportedShortcuts(),
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 ff1b5f6..b80830a 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
@@ -43,6 +43,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -87,6 +88,10 @@
         if (handlingOverviewAnim()) {
             mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
         }
+
+        if (mAtomicAnim != null) {
+            mAtomicAnim.cancel();
+        }
     }
 
     protected void onMotionPauseChanged(boolean isPaused) {
@@ -193,13 +198,27 @@
 
         Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
                 INDEX_PAUSE_TO_OVERVIEW_ANIM);
-        overviewAnim.addListener(new AnimatorListenerAdapter() {
+        mAtomicAnim = new AnimatorSet();
+        mAtomicAnim.addListener(new AnimationSuccessListener() {
             @Override
-            public void onAnimationEnd(Animator animation) {
+            public void onAnimationSuccess(Animator animator) {
                 onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
             }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                if (mCancelled) {
+                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
+                            mToState, new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
+                            PEEK_OUT_ANIM_DURATION);
+                    mPeekAnim.start();
+                }
+                mAtomicAnim = null;
+            }
         });
-        overviewAnim.start();
+        mAtomicAnim.play(overviewAnim);
+        mAtomicAnim.start();
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 8b5283e..1b60404 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,7 +21,7 @@
 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.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -973,7 +973,7 @@
         }
         mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
 
-        if (UNSTABLE_SPRINGS.get()) {
+        if (QUICKSTEP_SPRINGS.get()) {
             mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
         }
         mLauncherTransitionController.getAnimationPlayer().start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 4a39e73..9e29238 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -224,7 +224,6 @@
             surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
                     cornerRadius / scale);
         }
-        applySurfaceParams(params.syncTransactionApplier, surfaceParams);
         return surfaceParams;
     }
 
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 58e36bc..5954b86 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
@@ -243,6 +243,7 @@
                         getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
                         Gravity.BOTTOM);
                 addView(mActionsView, params);
+                mActionsView.setAlpha(0);
             }
         }
     }
@@ -447,7 +448,8 @@
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
 
-        if (mActionsView != null) {
+
+        if (mActionsView != null && isRunningTask()) {
             mActionsView.setAlpha(scale);
         }
 
@@ -523,9 +525,11 @@
     public void onPageScroll(ScrollState scrollState) {
         float curveInterpolation =
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+        float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
+                curveInterpolation);
 
         mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
-        setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+        setCurveScale(curveScaleForCurveInterpolation);
 
         mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
         for (FooterWrapper footer : mFooters) {
@@ -539,6 +543,28 @@
             mMenuView.setScaleX(getScaleX());
             mMenuView.setScaleY(getScaleY());
         }
+
+        // This is not the proper implementation and will be replaced with a proper layout.
+        if (mActionsView != null) {
+            if (mFocusTransitionProgress == 1f) {
+                mActionsView.setAlpha(1 - curveInterpolation / MAX_PAGE_SCRIM_ALPHA);
+            }
+            maintainActionViewPosition(curveScaleForCurveInterpolation);
+        }
+
+    }
+
+    private void maintainActionViewPosition(float curveScaleForCurveInterpolation) {
+        float inverseCurveScaleFactor = curveScaleForCurveInterpolation == 0 ? 0 :
+                (1f / curveScaleForCurveInterpolation);
+        mActionsView.setScaleX(inverseCurveScaleFactor);
+        mActionsView.setScaleY(inverseCurveScaleFactor);
+        mActionsView.setTranslationX(inverseCurveScaleFactor * (-getX()
+                + getRecentsView().getScrollX() + getRecentsView().scrollOffsetLeft()));
+        mActionsView.setTranslationY(
+                (1f - curveScaleForCurveInterpolation) * (mSnapshotView.getHeight()
+                        + mActionsView.getHeight()) / 2f
+                        + inverseCurveScaleFactor * (-getTranslationY()));
     }
 
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d5ce734..99b2a81 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
@@ -277,7 +277,7 @@
     private void handleFirstSwipeToOverview(final ValueAnimator animator,
             final long expectedDuration, final LauncherState targetState, final float velocity,
             final boolean isFling) {
-        if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+        if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
                 && targetState == OVERVIEW) {
             mFinishFastOnSecondTouch = true;
         } else  if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index bd2c539..a726052 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -147,7 +147,7 @@
     @Test
     @Ignore // b/143488140
     public void goToOverviewFromApp() {
-        startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
         mLauncher.getBackground().switchToOverview();
     }
@@ -182,8 +182,8 @@
     @Test
     @Ignore // b/143488140
     public void testOverview() {
-        startAppFastAndWaitForRecentTask(getAppPackageName());
-        startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+        startAppFast(getAppPackageName());
+        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
         Wait.atMost("Expected three apps in the task list",
                 () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
@@ -236,25 +236,4 @@
     private int getTaskCount(RecentsActivity recents) {
         return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
     }
-
-    /**
-     * Workaround for b/141580748, there was an issue where the recent task is only updated when the
-     * activity starting the task is resumed.  In this case, we should wait until the task is in
-     * the recents task list before continuing.
-     */
-    private void startAppFastAndWaitForRecentTask(String packageName) {
-        startAppFast(packageName);
-        Wait.atMost("Expected app in task list",
-                () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT,
-                mLauncher);
-    }
-
-    private boolean containsRecentTaskWithPackage(String packageName) {
-        for (ComponentName cn : mLauncher.getRecentTasks()) {
-            if (cn.getPackageName().equals(packageName)) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index b786c8b..97424bb 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -73,6 +73,9 @@
 
     private final LauncherInstrumentation mLauncher;
 
+    static final SysUINavigationMode SYS_UI_NAVIGATION_MODE =
+            SysUINavigationMode.INSTANCE.get(getInstrumentation().getTargetContext());
+
     public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
         mLauncher = launcher;
     }
@@ -83,32 +86,13 @@
                 description.getAnnotation(NavigationModeSwitch.class) != null) {
             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
-                private void assertTrue(String message, boolean condition) {
-                    if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) {
-                        // The condition above is "screen is not empty". We are not treating
-                        // "Screen is empty" as an anomaly here. It's an acceptable state when
-                        // Launcher just starts under instrumentation.
-                        mLauncher.checkForAnomaly();
-                    }
-                    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);
-                    final String prevOverlayPkg =
-                            QuickStepContract.isGesturalMode(currentInteractionMode)
-                                    ? NAV_BAR_MODE_GESTURAL_OVERLAY
-                                    : QuickStepContract.isSwipeUpMode(currentInteractionMode)
-                                            ? NAV_BAR_MODE_2BUTTON_OVERLAY
-                                            : NAV_BAR_MODE_3BUTTON_OVERLAY;
+                    final String prevOverlayPkg = getCurrentOverlayPackage(currentInteractionMode);
                     final LauncherInstrumentation.NavigationModel originalMode =
                             mLauncher.getNavigationModel();
                     try {
@@ -125,104 +109,47 @@
                         Log.e(TAG, "Error", e);
                         throw e;
                     } finally {
-                        assertTrue("Couldn't set overlay",
-                                setActiveOverlay(prevOverlayPkg, originalMode));
+                        Log.d(TAG, "In Finally block");
+                        assertTrue(mLauncher, "Couldn't set overlay",
+                                setActiveOverlay(mLauncher, prevOverlayPkg, originalMode,
+                                        description), description);
                     }
                 }
 
                 private void evaluateWithThreeButtons() throws Throwable {
-                    if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+                    if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) {
                         base.evaluate();
                     }
                 }
 
                 private void evaluateWithTwoButtons() throws Throwable {
-                    if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+                    if (setActiveOverlay(mLauncher, NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON, description)) {
                         base.evaluate();
                     }
                 }
 
                 private void evaluateWithZeroButtons() throws Throwable {
-                    if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+                    if (setActiveOverlay(mLauncher, NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON, description)) {
                         base.evaluate();
                     }
                 }
-
-                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,
-                            overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
-                    setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                            overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
-
-                    if (currentSysUiNavigationMode() != expectedMode) {
-                        final CountDownLatch latch = new CountDownLatch(1);
-                        final Context targetContext = getInstrumentation().getTargetContext();
-                        final SysUINavigationMode.NavigationModeChangeListener listener =
-                                newMode -> {
-                                    if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
-                                            == expectedMode) {
-                                        latch.countDown();
-                                    }
-                                };
-                        final SysUINavigationMode sysUINavigationMode =
-                                SysUINavigationMode.INSTANCE.get(targetContext);
-                        targetContext.getMainExecutor().execute(() ->
-                                sysUINavigationMode.addModeChangeListener(listener));
-                        latch.await(60, TimeUnit.SECONDS);
-                        targetContext.getMainExecutor().execute(() ->
-                                sysUINavigationMode.removeModeChangeListener(listener));
-                        assertTrue("Navigation mode didn't change to " + expectedMode,
-                                currentSysUiNavigationMode() == expectedMode);
-                    }
-
-                    Wait.atMost("Couldn't switch to " + overlayPackage,
-                            () -> mLauncher.getNavigationModel() == expectedMode, WAIT_TIME_MS,
-                            mLauncher);
-
-                    Wait.atMost(() -> "Switching nav mode: "
-                                    + mLauncher.getNavigationModeMismatchError(),
-                            () -> mLauncher.getNavigationModeMismatchError() == null, WAIT_TIME_MS,
-                            mLauncher);
-
-                    return true;
-                }
-
-                private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
-                        throws Exception {
-                    Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
-                    final String action = enable ? "enable" : "disable";
-                    UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                            "cmd overlay " + action + " " + overlayPackage);
-                }
             };
         } else {
             return base;
         }
     }
 
+    public static String getCurrentOverlayPackage(int currentInteractionMode) {
+        return QuickStepContract.isGesturalMode(currentInteractionMode)
+                ? NAV_BAR_MODE_GESTURAL_OVERLAY
+                : QuickStepContract.isSwipeUpMode(currentInteractionMode)
+                        ? NAV_BAR_MODE_2BUTTON_OVERLAY
+                        : NAV_BAR_MODE_3BUTTON_OVERLAY;
+    }
+
     private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() {
         return LauncherInstrumentation.getNavigationModel(
                 SysUINavigationMode.getMode(
@@ -230,4 +157,85 @@
                                 getTargetContext()).
                         resValue);
     }
+
+    public static boolean setActiveOverlay(LauncherInstrumentation launcher, String overlayPackage,
+            LauncherInstrumentation.NavigationModel expectedMode, Description description)
+            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,
+                overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+        setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+        if (currentSysUiNavigationMode() != expectedMode) {
+            final CountDownLatch latch = new CountDownLatch(1);
+            final Context targetContext = getInstrumentation().getTargetContext();
+            final SysUINavigationMode.NavigationModeChangeListener listener =
+                    newMode -> {
+                        if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
+                                == expectedMode) {
+                            latch.countDown();
+                        }
+                    };
+            targetContext.getMainExecutor().execute(() ->
+                    SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener));
+            latch.await(60, TimeUnit.SECONDS);
+            targetContext.getMainExecutor().execute(() ->
+                    SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
+            assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
+                    currentSysUiNavigationMode() == expectedMode, description);
+        }
+
+        Wait.atMost("Couldn't switch to " + overlayPackage,
+                () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher);
+
+        Wait.atMost(() -> "Switching nav mode: "
+                        + launcher.getNavigationModeMismatchError(),
+                () -> launcher.getNavigationModeMismatchError() == null, WAIT_TIME_MS, launcher);
+
+        return true;
+    }
+
+    private static void setOverlayPackageEnabled(String overlayPackage, boolean enable)
+            throws Exception {
+        Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+        final String action = enable ? "enable" : "disable";
+        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                "cmd overlay " + action + " " + overlayPackage);
+    }
+
+    private static 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 static void assertTrue(LauncherInstrumentation launcher, String message,
+            boolean condition, Description description) {
+        if (launcher.getDevice().hasObject(By.textStartsWith(""))) {
+            // The condition above is "screen is not empty". We are not treating
+            // "Screen is empty" as an anomaly here. It's an acceptable state when
+            // Launcher just starts under instrumentation.
+            launcher.checkForAnomaly();
+        }
+        if (!condition) {
+            final AssertionError assertionError = new AssertionError(message);
+            if (description != null) {
+                FailureWatcher.onError(launcher.getDevice(), description, assertionError);
+            }
+            throw assertionError;
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index c99df10..d2f5d8f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -17,6 +17,8 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -40,6 +42,7 @@
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.views.RecentsView;
 
@@ -268,7 +271,10 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @Ignore // b/143285809
+    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
+    // b/143285809
+    @Ignore
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
         startTestActivity(3);
diff --git a/res/drawable/bg_all_apps_button.xml b/res/drawable/bg_all_apps_button.xml
new file mode 100644
index 0000000..169a468
--- /dev/null
+++ b/res/drawable/bg_all_apps_button.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2020, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="oval" >
+            <solid android:color="#ffffffff" />
+        </shape>
+    </item>
+
+    <item>
+        <shape android:shape="oval" >
+            <solid android:color="?android:attr/colorAccent" />
+        </shape>
+    </item>
+</ripple>
diff --git a/res/drawable/ic_apps.xml b/res/drawable/ic_apps.xml
new file mode 100644
index 0000000..db779c2
--- /dev/null
+++ b/res/drawable/ic_apps.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<vector android:height="24dp" android:tint="#FFFFFF"
+        android:viewportHeight="24.0" android:viewportWidth="24.0"
+        android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
+</vector>
diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block.xml
new file mode 100644
index 0000000..edeb4c6
--- /dev/null
+++ b/res/drawable/ic_block.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:tint="?android:attr/textColorPrimary">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M4,12c0-4.42,3.58-8,8-8c1.85,0,3.55,0.63,4.9,1.69
+	L5.69,16.9C4.63,15.55,4,13.85,4,12z M12,20c-1.85,0-3.55-0.63-4.9-1.69L18.31,7.1C19.37,8.45,20,10.15,20,12
+	C20,16.42,16.42,20,12,20z" />
+</vector>
diff --git a/res/drawable/ic_corp.xml b/res/drawable/ic_corp.xml
index b59113d..76dccd3 100644
--- a/res/drawable/ic_corp.xml
+++ b/res/drawable/ic_corp.xml
@@ -19,9 +19,5 @@
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
     android:tint="?android:attr/textColorHint" >
-    <path
-        android:pathData="M20 6h-4V4c0-1.11-0.89-2-2-2h-4c-1.11 0-2 0.89-2 2v2H4c-1.11 0-1.99 0.89 -1.99
-2L2 19c0 1.11 0.89 2 2 2h16c1.11 0 2-0.89 2-2V8c0-1.11-0.89-2-2-2zM10
-4h4v2h-4V4zm10 15H4V8h16v11z"
-        android:fillColor="@android:color/white"/>
+    <path android:fillColor="@android:color/white" android:pathData="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-8 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm2-9h-4V4h4v2z"/>
 </vector>
\ No newline at end of file
diff --git a/res/drawable/ic_corp_off.xml b/res/drawable/ic_corp_off.xml
new file mode 100644
index 0000000..62a9787
--- /dev/null
+++ b/res/drawable/ic_corp_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/textColorHint" >
+    <path
+        android:pathData="M22 7.95c.05-1.11-.84-2-1.95-1.95H16V3.95c0-1.11-.84-2-1.95-1.95h-4C8.94 1.95 8 2.84 8 3.95v.32l14 14V7.95zM14 6h-4V4h4v2zm7.54 14.28l-7.56-7.56v.01l-1.7-1.7h.01L7.21 5.95 3.25 1.99 1.99 3.27 4.69 6h-.64c-1.11 0-1.99.86-1.99 1.97l-.01 11.02c0 1.11.89 2.01 2 2.01h15.64l2.05 2.02L23 21.75l-1.46-1.47z"
+        android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
similarity index 100%
rename from quickstep/res/drawable/ic_pin.xml
rename to res/drawable/ic_pin.xml
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 9427ae0..a41fb9a 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -16,7 +16,7 @@
 <!-- The top and bottom paddings are defined in this container, but since we want
      the list view to span the full width (for touch interception purposes), we
      will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.allapps.AllAppsContainerView
+<com.android.launcher3.allapps.LauncherAllAppsContainerView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/apps_view"
     android:layout_width="match_parent"
@@ -57,7 +57,6 @@
                 android:layout_weight="1"
                 android:background="?android:attr/selectableItemBackground"
                 android:text="@string/all_apps_personal_tab"
-                android:textAllCaps="true"
                 android:textColor="@color/all_apps_tab_text"
                 android:textSize="14sp" />
 
@@ -68,7 +67,6 @@
                 android:layout_weight="1"
                 android:background="?android:attr/selectableItemBackground"
                 android:text="@string/all_apps_work_tab"
-                android:textAllCaps="true"
                 android:textColor="@color/all_apps_tab_text"
                 android:textSize="14sp" />
         </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
@@ -79,4 +77,4 @@
         layout="@layout/search_container_all_apps"/>
 
     <include layout="@layout/all_apps_fast_scroller" />
-</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
+</com.android.launcher3.allapps.LauncherAllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
new file mode 100644
index 0000000..98cfc34
--- /dev/null
+++ b/res/layout/secondary_launcher.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<com.android.launcher3.secondarydisplay.SecondaryDragLayer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/drag_layer" >
+
+    <GridView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="100dp"
+        android:theme="@style/HomeScreenElementTheme"
+        android:layout_gravity="center_horizontal|top"
+        android:layout_margin="@dimen/dynamic_grid_edge_margin"
+        android:id="@+id/workspace_grid" />
+
+    <ImageButton
+        android:id="@+id/all_apps_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="40dp"
+        android:padding="16dp"
+        android:src="@drawable/ic_apps"
+        android:background="@drawable/bg_all_apps_button"
+        android:contentDescription="@string/all_apps_button_label"
+        android:onClick="onAppsButtonClicked" />
+
+    <com.android.launcher3.allapps.AllAppsContainerView
+        android:id="@+id/apps_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="true"
+        android:clipToPadding="false"
+        android:focusable="false"
+        android:saveEnabled="false"
+        android:layout_gravity="bottom|end"
+        android:background="@drawable/round_rect_primary"
+        android:elevation="2dp"
+        android:visibility="invisible" >
+
+        <include
+            layout="@layout/all_apps_rv_layout"
+            android:visibility="gone" />
+
+        <com.android.launcher3.allapps.FloatingHeaderView
+            android:id="@+id/all_apps_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/search_container_all_apps"
+            android:clipToPadding="false"
+            android:paddingTop="@dimen/all_apps_header_top_padding"
+            android:orientation="vertical" >
+
+            <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+                android:id="@+id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/all_apps_header_tab_height"
+                android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+                android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+                android:orientation="horizontal"
+                style="@style/TextHeadline">
+
+                <Button
+                    android:id="@+id/tab_personal"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:text="@string/all_apps_personal_tab"
+                    android:textAllCaps="true"
+                    android:textColor="@color/all_apps_tab_text"
+                    android:textSize="14sp" />
+
+                <Button
+                    android:id="@+id/tab_work"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:text="@string/all_apps_work_tab"
+                    android:textAllCaps="true"
+                    android:textColor="@color/all_apps_tab_text"
+                    android:textSize="14sp" />
+            </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+        </com.android.launcher3.allapps.FloatingHeaderView>
+
+        <com.android.launcher3.allapps.search.AppsSearchContainerLayout
+            android:id="@id/search_container_all_apps"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/all_apps_search_bar_field_height"
+            android:layout_centerHorizontal="true"
+            android:layout_gravity="top|center_horizontal"
+            android:background="@drawable/bg_all_apps_searchbox"
+            android:elevation="1dp"
+            android:focusableInTouchMode="true"
+            android:gravity="center"
+            android:hint="@string/all_apps_search_bar_hint"
+            android:imeOptions="actionSearch|flagNoExtractUi"
+            android:inputType="text|textNoSuggestions|textCapWords"
+            android:maxLines="1"
+            android:padding="8dp"
+            android:saveEnabled="false"
+            android:scrollHorizontally="true"
+            android:singleLine="true"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textColorHint="@drawable/all_apps_search_hint"
+            android:textSize="16sp"
+            android:translationY="24dp" />
+
+        <include layout="@layout/all_apps_fast_scroller" />
+    </com.android.launcher3.allapps.AllAppsContainerView>
+</com.android.launcher3.secondarydisplay.SecondaryDragLayer>
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
new file mode 100644
index 0000000..5607e78
--- /dev/null
+++ b/res/layout/work_apps_paused.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="8dp"
+    android:orientation="vertical"
+    android:background="?attr/allAppsScrimColor"
+    android:gravity="center">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:contentDescription="@string/work_apps_paused_title"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:tint="?attr/folderTextColor"
+        android:src="@drawable/ic_corp_off" />
+
+    <TextView
+        style="@style/TextHeadline"
+        android:textColor="?attr/folderTextColor"
+        android:id="@+id/work_apps_paused_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
+        android:text="@string/work_apps_paused_title"
+        android:textAlignment="center"
+        android:textSize="24sp" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="?attr/folderTextColor"
+        android:text="@string/work_apps_paused_body"
+        android:textAlignment="center"
+        android:textSize="16sp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
index a8e3d20..f7a529d 100644
--- a/res/layout/work_profile_edu.xml
+++ b/res/layout/work_profile_edu.xml
@@ -35,12 +35,12 @@
         android:paddingRight="@dimen/bottom_sheet_edu_padding">
 
         <TextView
+            style="@style/TextHeadline"
             android:id="@+id/content_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="48dp"
             android:layout_marginBottom="48dp"
-            android:fontFamily="google-sans"
             android:text="@string/work_profile_edu_personal_apps"
             android:textAlignment="center"
             android:textColor="@android:color/white"
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 379e9d0..db95416 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,63 +17,31 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:id="@+id/work_toggle_container"
     android:focusable="true"
-    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
-    android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
-    android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
-    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_top_padding">
-
-    <ImageView
-        android:id="@+id/work_footer_divider"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:focusable="false"
-        android:importantForAccessibility="no"
-        android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
-        android:paddingTop="@dimen/all_apps_divider_margin_vertical"
-        android:scaleType="fitXY"
-        android:src="@drawable/all_apps_divider"/>
-
-    <com.android.launcher3.allapps.WorkModeSwitch
-        android:id="@+id/work_mode_toggle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentEnd="true"
-        android:layout_below="@id/work_footer_divider"/>
+    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding_vertical"
+    android:orientation="horizontal"
+    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
+    android:background="?attr/allAppsScrimColor"
+    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
+    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding_vertical">
 
     <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
+        android:id="@+id/work_mode_label"
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:drawableStart="@drawable/ic_corp"
+        android:drawablePadding="3dp"
         android:layout_height="wrap_content"
-        android:layout_alignBaseline="@id/work_mode_toggle"
-        android:layout_alignParentStart="true"
-        android:ellipsize="end"
-        android:lines="1"
-        android:text="@string/work_profile_toggle_label"
-        android:textColor="?android:attr/textColorTertiary"
-        android:textSize="16sp"/>
-
-    <ImageView
-        android:id="@android:id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_below="@android:id/title"
-        android:layout_marginTop="8dp"
-        android:src="@drawable/ic_corp"/>
-
-    <TextView
-        android:id="@+id/managed_by_label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@android:id/title"
-        android:layout_marginTop="8dp"
-        android:layout_toEndOf="@android:id/icon"
         android:ellipsize="end"
         android:gravity="center_vertical"
         android:lines="1"
         android:minHeight="24dp"
-        android:paddingStart="12dp"
-        android:textColor="?android:attr/textColorHint"
-        android:textSize="13sp"/>
+        android:paddingEnd="12dp"
+        android:textSize="16sp"/>
+    <com.android.launcher3.allapps.WorkModeSwitch
+        android:id="@+id/work_mode_toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
 
 </com.android.launcher3.views.WorkFooterContainer>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0945642..ef34dcd 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -108,6 +108,7 @@
     <item type="id" name="action_shortcuts_and_notifications"/>
     <item type="id" name="action_dismiss_notification" />
     <item type="id" name="action_remote_action_shortcut" />
+    <item type="id" name="action_dismiss_prediction" />
 
     <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0293573..edae7f4 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,9 @@
     <dimen name="all_apps_tabs_side_padding">12dp</dimen>
     <dimen name="all_apps_divider_height">1dp</dimen>
 
+    <dimen name="all_apps_work_profile_tab_footer_padding_vertical">20dp</dimen>
+    <dimen name="all_apps_work_profile_tab_footer_padding_horizontal">24dp</dimen>
+
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
     <dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3f279f4..bfa92f7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -338,9 +338,12 @@
 
     <!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
     "Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
-    <string name="work_mode_on_label">Managed by your organization</string>
+    <string name="work_mode_on_label">Work apps: On</string>
     <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
-    <string name="work_mode_off_label">Notifications and apps are off</string>
+    <string name="work_mode_off_label">Work apps: Paused</string>
+
+    <string name="work_apps_paused_title">Work apps are paused</string>
+    <string name="work_apps_paused_body">You won\'t get any work notifications, and your IT admin can\'t see your location</string>
 
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 80c791c..35ae49c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -55,6 +55,8 @@
         <item name="android:windowDrawsSystemBarBackgrounds">true</item>
         <item name="android:statusBarColor">#00000000</item>
         <item name="android:navigationBarColor">#00000000</item>
+
+
     </style>
 
     <style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 0283eac..dda38b3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 import android.view.ActionMode;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -36,6 +37,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -262,5 +264,9 @@
         }
     }
 
+    public OnClickListener getItemOnClickListener() {
+        return ItemClickHandler.INSTANCE;
+    }
+
     protected abstract void reapplyUi();
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bc6fa6e..0d71da4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -245,7 +245,7 @@
             allAppsIconTextSizePx = originalProfile.iconTextSizePx;
             allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
             allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
-            allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+            allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
         }
         updateWorkspacePadding();
 
@@ -360,7 +360,7 @@
         allAppsIconTextSizePx = iconTextSizePx;
         allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
         allAppsCellHeightPx = getCellSize().y;
-        allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+        allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
 
         if (isVerticalBarLayout()) {
             // Always hide the Workspace text with vertical bar layout.
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 9d87152..a807e4f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,8 +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.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.annotation.TargetApi;
@@ -41,6 +41,7 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.Xml;
+import android.view.Display;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -48,6 +49,7 @@
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.Info;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Themes;
@@ -172,6 +174,13 @@
     }
 
     /**
+     * This constructor should NOT have any monitors by design.
+     */
+    public InvariantDeviceProfile(Context context, Display display) {
+        initGrid(context, null, new Info(display));
+    }
+
+    /**
      * Retrieve system defined or RRO overriden icon shape.
      */
     private static String getIconShapePath(Context context) {
@@ -183,8 +192,10 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+        return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+    }
 
+    private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
         Point smallestSize = new Point(displayInfo.smallestSize);
         Point largestSize = new Point(displayInfo.largestSize);
 
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3f723d1..c99465c 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -214,4 +214,11 @@
         return id;
     }
 
+    /**
+     * Returns if an Item is a predicted item
+     */
+    public boolean isPredictedItem() {
+        return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
+                || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fe987bc..43540ce 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -32,7 +32,6 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
-import static com.android.launcher3.popup.SystemShortcut.DISMISS_PREDICTION;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
@@ -371,7 +370,7 @@
         mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
 
         setupViews();
-        mPopupDataProvider = new PopupDataProvider(this);
+        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
         mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
         mAppTransitionManager.registerRemoteAnimations();
@@ -1344,7 +1343,7 @@
         }
     };
 
-    public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
+    private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
         mWorkspace.updateNotificationDots(updatedDots);
         mAppsView.getAppsStore().updateNotificationDots(updatedDots);
     }
@@ -1721,8 +1720,11 @@
         }
     }
 
-    FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
-            int cellY) {
+    /**
+     * Creates and adds new folder to CellLayout
+     */
+    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
+            final int screenId, int cellX, int cellY) {
         final FolderInfo folderInfo = new FolderInfo();
         folderInfo.title = "";
 
@@ -1730,7 +1732,8 @@
         getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
 
         // Create the view
-        FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout, folderInfo);
+        FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout,
+                folderInfo);
         mWorkspace.addInScreen(newFolder, folderInfo);
         // Force measure the new folder icon
         CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
@@ -1807,7 +1810,6 @@
 
         // Note: There should be at most one log per method call. This is enforced implicitly
         // by using if-else statements.
-        UserEventDispatcher ued = getUserEventDispatcher();
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
         if (topView != null && topView.onBackPressed()) {
             // Handled by the floating view.
@@ -1875,6 +1877,7 @@
         }
     }
 
+    @Override
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
             @Nullable String sourceContainer) {
         if (!hasBeenResumed()) {
@@ -2691,7 +2694,7 @@
     }
 
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
-        return Stream.of(APP_INFO, WIDGETS, INSTALL, DISMISS_PREDICTION);
+        return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
     public static Launcher getLauncher(Context context) {
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index ce1795a..2b2224a 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,7 +8,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.os.Build;
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 95ee687..5f6ecb5 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapCreationCheck;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,9 @@
         FeatureFlags.initialize(context);
         SessionCommitReceiver.applyDefaultUserPrefs(context);
         IconShape.init(context);
+
+        if (BitmapCreationCheck.ENABLED) {
+            BitmapCreationCheck.startTracking(context);
+        }
     }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a1888bf..ae4eae9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -245,6 +245,13 @@
         forceFinishScroller(true);
     }
 
+    /**
+     * Returns left offset of a page. This is the gap between pages and prevents overlap.
+     */
+    public int scrollOffsetLeft() {
+        return mInsets.left + getPaddingLeft();
+    }
+
     private void abortScrollerAnimation(boolean resetNextPage) {
         mScroller.abortAnimation();
         // We need to clean up the next page here to avoid computeScrollHelper from
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 3c2ed72..1dbe195 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -6,6 +6,7 @@
 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;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
 
@@ -29,9 +30,11 @@
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -43,6 +46,7 @@
  * Drop target which provides a secondary option for an item.
  *    For app targets: shows as uninstall
  *    For configurable widgets: shows as setup
+ *    For predicted app icons: don't suggest app
  */
 public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
 
@@ -81,7 +85,11 @@
             mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
             setDrawable(R.drawable.ic_uninstall_shadow);
             updateText(R.string.uninstall_drop_target_label);
-        } else {
+        } else if (action == DISMISS_PREDICTION) {
+            mHoverColor = Themes.getColorAccent(getContext());
+            setDrawable(R.drawable.ic_block);
+            updateText(R.string.dismiss_prediction_label);
+        } else if (action == RECONFIGURE) {
             mHoverColor = Themes.getColorAccent(getContext());
             setDrawable(R.drawable.ic_setup_shadow);
             updateText(R.string.gadget_setup_text);
@@ -101,8 +109,13 @@
     @Override
     public Target getDropTargetForLogging() {
         Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
-        t.controlType = mCurrentAccessibilityAction == UNINSTALL ? ControlType.UNINSTALL_TARGET
-                : ControlType.SETTINGS_BUTTON;
+        if (mCurrentAccessibilityAction == UNINSTALL) {
+            t.controlType = ControlType.UNINSTALL_TARGET;
+        } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+            t.controlType = ControlType.DISMISS_PREDICTION;
+        } else {
+            t.controlType = ControlType.SETTINGS_BUTTON;
+        }
         return t;
     }
 
@@ -119,6 +132,9 @@
                 return true;
             }
             return false;
+        } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
+            setupUi(DISMISS_PREDICTION);
+            return true;
         }
 
         setupUi(UNINSTALL);
@@ -229,6 +245,11 @@
             }
             return null;
         }
+        if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+            AppLaunchTracker.INSTANCE.get(getContext()).onDismissApp(info.getTargetComponent(),
+                    info.user, AppLaunchTracker.CONTAINER_PREDICTIONS);
+            return null;
+        }
         // else: mCurrentAccessibilityAction == UNINSTALL
 
         ComponentName cn = getUninstallTarget(info);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index beaafda..b53889a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1703,8 +1703,8 @@
             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
             target.removeView(v);
 
-            FolderIcon fi =
-                mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
+            FolderIcon fi = mLauncher.addFolder(target, sourceInfo, container, screenId,
+                    targetCell[0], targetCell[1]);
             destInfo.cellX = -1;
             destInfo.cellY = -1;
             sourceInfo.cellX = -1;
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index a7ef9ef..0b439ec 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -54,6 +54,7 @@
 
     public static final int REMOVE = R.id.action_remove;
     public static final int UNINSTALL = R.id.action_uninstall;
+    public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
     public static final int RECONFIGURE = R.id.action_reconfigure;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
@@ -86,6 +87,8 @@
                 launcher.getText(R.string.remove_drop_target_label)));
         mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
                 launcher.getText(R.string.uninstall_drop_target_label)));
+        mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
+                launcher.getText(R.string.dismiss_prediction_label)));
         mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
                 launcher.getText(R.string.gadget_setup_text)));
         mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
@@ -166,13 +169,22 @@
     }
 
     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();
+        if (action == ACTION_LONG_CLICK) {
+            if (ShortcutUtil.isDeepShortcut(item)) {
+                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+                if (popup.canShow()) {
+                    popup.show();
+                    return true;
+                }
+            } else if (host instanceof BubbleTextView) {
+                // Long press should be consumed for workspace items, and it should invoke the
+                // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
+                // standard long press path does.
+                PopupContainerWithArrow.showForIcon((BubbleTextView) host);
                 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 27668eb..56bd1b6 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -40,6 +43,7 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DragSource;
@@ -47,12 +51,10 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -61,7 +63,7 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.launcher3.views.WorkEduView;
+import com.android.launcher3.views.WorkFooterContainer;
 
 /**
  * The all apps view container.
@@ -74,8 +76,8 @@
     private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
     private static final int ALPHA_CHANNEL_COUNT = 2;
 
-    private final Launcher mLauncher;
-    private final AdapterHolder[] mAH;
+    protected final BaseDraggingActivity mLauncher;
+    protected final AdapterHolder[] mAH;
     private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
     private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
     private final AllAppsStore mAllAppsStore = new AllAppsStore();
@@ -83,18 +85,19 @@
     private final Paint mNavBarScrimPaint;
     private int mNavBarScrimHeight = 0;
 
-    private SearchUiManager mSearchUiManager;
+    protected SearchUiManager mSearchUiManager;
     private View mSearchContainer;
     private AllAppsPagedView mViewPager;
+
     private FloatingHeaderView mHeader;
+    private WorkFooterContainer mWorkFooterContainer;
+
 
     private SpannableStringBuilder mSearchQueryBuilder = null;
 
-    private boolean mUsingTabs;
+    protected boolean mUsingTabs;
     private boolean mSearchModeWhileUsingTabs = false;
 
-    private LauncherStateManager.StateListener mWorkTabListener;
-
     private RecyclerViewFastScroller mTouchHandler;
     private final Point mFastScrollerOffset = new Point();
 
@@ -111,7 +114,7 @@
     public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mLauncher = Launcher.getLauncher(context);
+        mLauncher = BaseDraggingActivity.fromContext(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
 
         mSearchQueryBuilder = new SpannableStringBuilder();
@@ -133,6 +136,15 @@
         mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
     }
 
+    /**
+     * Sets the long click listener for icons
+     */
+    public void setOnIconLongClickListener(OnLongClickListener listener) {
+        for (AdapterHolder holder : mAH) {
+            holder.adapter.setOnIconLongClickListener(listener);
+        }
+    }
+
     public AllAppsStore getAppsStore() {
         return mAllAppsStore;
     }
@@ -169,6 +181,15 @@
             }
         }
         rebindAdapters(hasWorkApps);
+        if (hasWorkApps) {
+            resetWorkProfile();
+        }
+    }
+
+    private void resetWorkProfile() {
+        mWorkFooterContainer.refresh();
+        mAH[AdapterHolder.WORK].setupOverlay();
+        mAH[AdapterHolder.WORK].applyPadding();
     }
 
     /**
@@ -193,11 +214,6 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-
-        // 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 (ev.getAction() == MotionEvent.ACTION_DOWN) {
             AllAppsRecyclerView rv = getActiveRecyclerView();
             if (rv != null &&
@@ -309,10 +325,10 @@
                 + 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();
+            mAH[i].setupOverlay();
         }
 
         ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
@@ -327,8 +343,6 @@
         setLayoutParams(mlp);
 
         InsettableFrameLayout.dispatchInsets(this, insets);
-        mLauncher.getAllAppsController()
-                .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
     }
 
     @Override
@@ -372,13 +386,17 @@
         mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
 
         if (mUsingTabs) {
+            setupWorkToggle();
             mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
             mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
             onTabChanged(mViewPager.getNextPage());
-            mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
         } else {
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
             mAH[AdapterHolder.WORK].recyclerView = null;
+            if (mWorkFooterContainer != null) {
+                ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
+                mWorkFooterContainer = null;
+            }
         }
         setupHeader();
 
@@ -386,6 +404,16 @@
         mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
     }
 
+    private void setupWorkToggle() {
+        mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
+                R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
+        mWorkFooterContainer.setLayoutParams(
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
+        this.addView(mWorkFooterContainer);
+        mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+    }
+
     private void replaceRVContainer(boolean showTabs) {
         for (int i = 0; i < mAH.length; i++) {
             if (mAH[i].recyclerView != null) {
@@ -421,9 +449,9 @@
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
             findViewById(R.id.tab_work)
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
-            if (pos == AdapterHolder.WORK) {
-                WorkEduView.showWorkEduIfNeeded(mLauncher);
-            }
+        }
+        if (mWorkFooterContainer != null) {
+            mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
         }
     }
 
@@ -570,6 +598,7 @@
         public static final int MAIN = 0;
         public static final int WORK = 1;
 
+        private final boolean mIsWork;
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
         final AlphabeticalAppsList appsList;
@@ -577,7 +606,10 @@
         AllAppsRecyclerView recyclerView;
         boolean verticalFadingEdge;
 
+        boolean mWorkDisabled;
+
         AdapterHolder(boolean isWork) {
+            mIsWork = isWork;
             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
             adapter = new AllAppsGridAdapter(mLauncher, appsList);
             appsList.setAdapter(adapter);
@@ -588,7 +620,7 @@
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
-            recyclerView.setApps(appsList, mUsingTabs);
+            recyclerView.setApps(appsList);
             recyclerView.setLayoutManager(layoutManager);
             recyclerView.setAdapter(adapter);
             recyclerView.setHasFixedSize(true);
@@ -599,11 +631,36 @@
             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
             applyPadding();
+            setupOverlay();
+        }
+
+        void setupOverlay() {
+            if (!mIsWork || recyclerView == null) return;
+            boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
+            recyclerView.getOverlay().clear();
+            if (workDisabled) {
+                View pausedOverlay = mLauncher.getLayoutInflater().inflate(
+                        R.layout.work_apps_paused, null);
+                recyclerView.post(() -> {
+                    int width = recyclerView.getWidth();
+                    int height = recyclerView.getHeight();
+                    pausedOverlay.measure(makeMeasureSpec(width, EXACTLY),
+                            makeMeasureSpec(height, EXACTLY));
+                    pausedOverlay.layout(0, 0, width, height);
+                    applyPadding();
+                });
+                recyclerView.getOverlay().add(pausedOverlay);
+            }
+            mWorkDisabled = workDisabled;
         }
 
         void applyPadding() {
             if (recyclerView != null) {
-                recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+                int bottomOffset =
+                        mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
+                                : 0;
+                recyclerView.setPadding(padding.left, padding.top, padding.right,
+                        padding.bottom + bottomOffset);
             }
         }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 80ea1eb..1f861bc 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -15,17 +15,22 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
 import androidx.core.view.accessibility.AccessibilityEventCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
@@ -33,14 +38,11 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
 import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.List;
@@ -64,7 +66,6 @@
 
     // A divider that separates the apps list and the search market button
     public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
-    public static final int VIEW_TYPE_WORK_TAB_FOOTER = 1 << 5;
 
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
@@ -174,23 +175,26 @@
         }
     }
 
-    private final Launcher mLauncher;
+    private final BaseDraggingActivity mLauncher;
     private final LayoutInflater mLayoutInflater;
     private final AlphabeticalAppsList mApps;
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
 
+    private final OnClickListener mOnIconClickListener;
+    private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+
     private int mAppsPerRow;
 
     private BindViewCallback mBindViewCallback;
     private OnFocusChangeListener mIconFocusListener;
 
     // The text to show when there are no search results and no market search handler.
-    private String mEmptySearchMessage;
+    protected String mEmptySearchMessage;
     // The intent to send off to the market app, updated each time the search query changes.
     private Intent mMarketSearchIntent;
 
-    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
+    public AllAppsGridAdapter(BaseDraggingActivity launcher, AlphabeticalAppsList apps) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -200,6 +204,8 @@
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         mLayoutInflater = LayoutInflater.from(launcher);
 
+        mOnIconClickListener = launcher.getItemOnClickListener();
+
         setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
     }
 
@@ -208,6 +214,13 @@
         mGridLayoutMgr.setSpanCount(mAppsPerRow);
     }
 
+    /**
+     * Sets the long click listener for icons
+     */
+    public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
+        mOnIconLongClickListener = listener;
+    }
+
     public static boolean isDividerViewType(int viewType) {
         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
     }
@@ -254,8 +267,8 @@
             case VIEW_TYPE_ICON:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
-                icon.setOnClickListener(ItemClickHandler.INSTANCE);
-                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+                icon.setOnClickListener(mOnIconClickListener);
+                icon.setOnLongClickListener(mOnIconLongClickListener);
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
@@ -274,9 +287,6 @@
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
-            case VIEW_TYPE_WORK_TAB_FOOTER:
-                View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
-                return new ViewHolder(footer);
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -308,15 +318,6 @@
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
                 break;
-            case VIEW_TYPE_WORK_TAB_FOOTER:
-                WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
-                workModeToggle.refresh();
-                TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
-                boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
-                        managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
-                managedByLabel.setText(anyProfileQuietModeEnabled
-                        ? R.string.work_mode_off_label : R.string.work_mode_on_label);
-                break;
         }
         if (mBindViewCallback != null) {
             mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index f82e380..b6744cf 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -28,14 +28,13 @@
 
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.BaseDraggingActivity;
 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.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -84,7 +83,7 @@
     /**
      * Sets the list of apps in this view, used to determine the fastscroll position.
      */
-    public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
+    public void setApps(AlphabeticalAppsList apps) {
         mApps = apps;
         mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
     }
@@ -94,7 +93,7 @@
     }
 
     private void updatePoolSize() {
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
         RecyclerView.RecycledViewPool pool = getRecycledViewPool();
         int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0681919..1bde138 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -46,7 +46,7 @@
  */
 public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
 
-    private static final float SPRING_DAMPING_RATIO = 0.9f;
+    private static final float SPRING_DAMPING_RATIO = 0.75f;
     private static final float SPRING_STIFFNESS = 600f;
 
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
@@ -185,7 +185,7 @@
     }
 
     public Animator createSpringAnimation(float... progressValues) {
-        if (UNSTABLE_SPRINGS.get()) {
+        if (QUICKSTEP_SPRINGS.get()) {
             return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
                     SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
         }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 10e2821..b501c82 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,14 +15,11 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 
 import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
@@ -117,16 +114,9 @@
             item.position = pos;
             return item;
         }
-
-        public static AdapterItem asWorkTabFooter(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_WORK_TAB_FOOTER;
-            item.position = pos;
-            return item;
-        }
     }
 
-    private final Launcher mLauncher;
+    private final BaseDraggingActivity mLauncher;
 
     // The set of apps from the system
     private final List<AppInfo> mApps = new ArrayList<>();
@@ -151,7 +141,7 @@
 
     public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
         mAllAppsStore = appsStore;
-        mLauncher = Launcher.getLauncher(context);
+        mLauncher = BaseDraggingActivity.fromContext(context);
         mAppNameComparator = new AppInfoComparator(context);
         mIsWork = isWork;
         mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
@@ -390,18 +380,6 @@
                     break;
             }
         }
-
-        // Add the work profile footer if required.
-        if (shouldShowWorkFooter()) {
-            mAdapterItems.add(AdapterItem.asWorkTabFooter(position++));
-        }
-    }
-
-    private boolean shouldShowWorkFooter() {
-        return mIsWork && Utilities.ATLEAST_P &&
-                (hasShortcutsPermission(mLauncher)
-                        || mLauncher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
-                        == PackageManager.PERMISSION_GRANTED);
     }
 
     private List<AppInfo> getFiltersAppInfos() {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 42a0eee..cc33af9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -31,9 +31,9 @@
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -393,7 +393,7 @@
 
     @Override
     public void setInsets(Rect insets) {
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
         for (FloatingHeaderRow row : mAllRows) {
             row.setInsets(insets, grid);
         }
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
new file mode 100644
index 0000000..9d0ecd3
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.allapps;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.views.WorkEduView;
+
+/**
+ * AllAppsContainerView with launcher specific callbacks
+ */
+public class LauncherAllAppsContainerView extends AllAppsContainerView {
+
+    private final Launcher mLauncher;
+
+    private LauncherStateManager.StateListener mWorkTabListener;
+
+    public LauncherAllAppsContainerView(Context context) {
+        this(context, null);
+    }
+
+    public LauncherAllAppsContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LauncherAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // 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;
+
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        super.setInsets(insets);
+        mLauncher.getAllAppsController()
+                .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
+    }
+
+    @Override
+    public void onTabChanged(int pos) {
+        super.onTabChanged(pos);
+        if (mUsingTabs) {
+            if (pos == AdapterHolder.WORK) {
+                WorkEduView.showWorkEduIfNeeded(mLauncher);
+            } else {
+                mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index decdcc0..6204f31 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -24,15 +24,14 @@
 import android.widget.Button;
 import android.widget.LinearLayout;
 
-import com.android.launcher3.Launcher;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.util.Themes;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 /**
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
@@ -73,7 +72,7 @@
         mDividerPaint.setStrokeWidth(
                 getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
 
-        mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
+        mSharedPreferences = Utilities.getPrefs(context);
         mIsRtl = Utilities.isRtl(getResources());
     }
 
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4515dde..ed45749 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -25,8 +25,8 @@
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.util.ComponentKey;
@@ -41,7 +41,7 @@
         implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
         OnFocusChangeListener {
 
-    protected Launcher mLauncher;
+    protected BaseDraggingActivity mLauncher;
     protected Callbacks mCb;
     protected ExtendedEditText mInput;
     protected String mQuery;
@@ -56,7 +56,7 @@
      */
     public final void initialize(
             SearchAlgorithm searchAlgorithm, ExtendedEditText input,
-            Launcher launcher, Callbacks cb) {
+            BaseDraggingActivity launcher, Callbacks cb) {
         mCb = cb;
         mLauncher = launcher;
 
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 31fcc8c..d497c3a 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -26,8 +26,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
 import android.util.AttributeSet;
@@ -36,18 +34,16 @@
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
@@ -59,8 +55,7 @@
         implements SearchUiManager, AllAppsSearchBarController.Callbacks,
         AllAppsStore.OnUpdateListener, Insettable {
 
-
-    private final Launcher mLauncher;
+    private final BaseDraggingActivity mLauncher;
     private final AllAppsSearchBarController mSearchBarController;
     private final SpannableStringBuilder mSearchQueryBuilder;
 
@@ -82,7 +77,7 @@
     public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mLauncher = Launcher.getLauncher(context);
+        mLauncher = BaseDraggingActivity.fromContext(context);
         mSearchBarController = new AllAppsSearchBarController();
 
         mSearchQueryBuilder = new SpannableStringBuilder();
@@ -97,13 +92,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
+        mAppsView.getAppsStore().addUpdateListener(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
+        mAppsView.getAppsStore().removeUpdateListener(this);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 91a3106..27b9c18 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -29,12 +29,13 @@
 import android.util.FloatProperty;
 import android.util.Log;
 
-import java.util.ArrayList;
-
 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import java.util.ArrayList;
+
+
 /**
  * This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
  * a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -137,11 +138,13 @@
 
         mProperty.switchToSpring();
 
-        mSpring.setStartVelocity(velocity);
-
         float startValue = end == 0 ? mValues[1] : mValues[0];
         float endValue = end == 0 ? mValues[0] : mValues[1];
-        mSpring.setStartValue(startValue);
+
+        // Ensures that the velocity matches the direction of the values.
+        velocity = Math.signum(endValue - startValue) * Math.abs(velocity);
+        mSpring.setStartVelocity(velocity);
+
         new Handler(Looper.getMainLooper()).postDelayed(() -> {
             mSpring.animateToFinalPosition(endValue);
         }, getStartDelay());
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 54a44ee..1b7b015 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -565,11 +565,13 @@
 
     /**
      * Since accessible drag and drop won't cause the same sequence of touch events, we manually
-     * inject the appropriate state.
+     * inject the appropriate state which would have been otherwise initiated via touch events.
      */
     public void prepareAccessibleDrag(int x, int y) {
         mMotionDownX = x;
         mMotionDownY = y;
+        mLastTouch[0] = x;
+        mLastTouch[1] = y;
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/BitmapCreationCheck.java b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
new file mode 100644
index 0000000..e63e542
--- /dev/null
+++ b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.GraphicsUtils;
+
+/**
+ * Utility class to check bitmap creation during draw pass.
+ */
+public class BitmapCreationCheck {
+
+    private static final String TAG = "BitmapCreationCheck";
+
+    public static final boolean ENABLED = false;
+
+    /**
+     * Starts tracking bitmap creations during {@link View#draw(Canvas)} calls
+     */
+    public static void startTracking(Context context) {
+        MyTracker tracker = new MyTracker();
+        ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(tracker);
+        GraphicsUtils.sOnNewBitmapRunnable = tracker::onBitmapCreated;
+    }
+
+    @TargetApi(VERSION_CODES.Q)
+    private static class MyTracker
+            implements ActivityLifecycleCallbacks, OnAttachStateChangeListener {
+
+        private final ThreadLocal<Boolean> mCurrentThreadDrawing =
+                ThreadLocal.withInitial(() -> false);
+
+        @Override
+        public void onActivityCreated(Activity activity, Bundle bundle) {
+            activity.getWindow().getDecorView().addOnAttachStateChangeListener(this);
+        }
+
+        @Override
+        public void onActivityStarted(Activity activity) { }
+
+        @Override
+        public void onActivityResumed(Activity activity) { }
+
+        @Override
+        public void onActivityPaused(Activity activity) { }
+
+        @Override
+        public void onActivityStopped(Activity activity) { }
+
+        @Override
+        public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+        @Override
+        public void onActivityDestroyed(Activity activity) { }
+
+        @Override
+        public void onViewAttachedToWindow(View view) {
+            view.getViewTreeObserver().addOnDrawListener(new MyViewDrawListener(view.getHandler()));
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View view) { }
+
+        private class MyViewDrawListener implements OnDrawListener, Runnable {
+
+            private final Handler mHandler;
+
+            MyViewDrawListener(Handler handler) {
+                mHandler = handler;
+            }
+
+            @Override
+            public void onDraw() {
+                mCurrentThreadDrawing.set(true);
+                Utilities.postAsyncCallback(mHandler, this);
+            }
+
+            @Override
+            public void run() {
+                mCurrentThreadDrawing.set(false);
+            }
+        }
+
+        private void onBitmapCreated() {
+            if (mCurrentThreadDrawing.get()) {
+                Log.e(TAG, "Bitmap created during draw pass", new Exception());
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index f352b46..b004edf 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,7 +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;
+    private static final int DEFAULT_PREDICTED_RANK = 10000;
 
     public static String getFieldName(int value, Class c) {
         SparseArray<String> cache;
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 98f7fd8..d9bd714 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,8 +40,8 @@
 import android.widget.FrameLayout;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -57,14 +57,16 @@
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
  */
-public abstract class ArrowPopup extends AbstractFloatingView {
+public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
 
     private final Rect mTempRect = new Rect();
 
     protected final LayoutInflater mInflater;
     private final float mOutlineRadius;
-    protected final Launcher mLauncher;
+    protected final T mLauncher;
     protected final boolean mIsRtl;
 
     private final int mArrowOffset;
@@ -83,7 +85,7 @@
         super(context, attrs, defStyleAttr);
         mInflater = LayoutInflater.from(context);
         mOutlineRadius = Themes.getDialogCornerRadius(context);
-        mLauncher = Launcher.getLauncher(context);
+        mLauncher = BaseDraggingActivity.fromContext(context);
         mIsRtl = Utilities.isRtl(getResources());
 
         setClipToOutline(true);
@@ -120,16 +122,22 @@
         }
     }
 
-    public <T extends View> T inflateAndAdd(int resId, ViewGroup container) {
+    /**
+     * Utility method for inflating and adding a view
+     */
+    public <R extends View> R inflateAndAdd(int resId, ViewGroup container) {
         View view = mInflater.inflate(resId, container, false);
         container.addView(view);
-        return (T) view;
+        return (R) view;
     }
 
-    public <T extends View> T inflateAndAdd(int resId, ViewGroup container, int index) {
+    /**
+     * Utility method for inflating and adding a view
+     */
+    public <R extends View> R inflateAndAdd(int resId, ViewGroup container, int index) {
         View view = mInflater.inflate(resId, container, false);
         container.addView(view, index);
-        return (T) view;
+        return (R) view;
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b764a07..05ea694 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -44,6 +44,7 @@
 import android.widget.ImageView;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -65,7 +66,6 @@
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.ShortcutUtil;
@@ -74,22 +74,22 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
  */
-public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
-        DragController.DragListener, View.OnLongClickListener,
-        View.OnTouchListener, PopupDataChangeListener {
+public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends ArrowPopup<T>
+        implements DragSource, DragController.DragListener {
 
     private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
     private final PointF mInterceptTouchDown = new PointF();
-    protected final Point mIconLastTouchPos = new Point();
 
     private final int mStartDragThreshold;
-    private final LauncherAccessibilityDelegate mAccessibilityDelegate;
 
     private BubbleTextView mOriginalIcon;
     private NotificationItemView mNotificationItemView;
@@ -97,11 +97,13 @@
 
     private ViewGroup mSystemShortcutContainer;
 
+    protected PopupItemDragHandler mPopupItemDragHandler;
+    protected LauncherAccessibilityDelegate mAccessibilityDelegate;
+
     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mStartDragThreshold = getResources().getDimensionPixelSize(
                 R.dimen.deep_shortcuts_start_drag_threshold);
-        mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
     }
 
     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -117,18 +119,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mLauncher.getPopupDataProvider().setChangeListener(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mLauncher.getPopupDataProvider().setChangeListener(null);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mInterceptTouchDown.set(ev.getX(), ev.getY());
@@ -168,11 +158,15 @@
 
     public OnClickListener getItemClickListener() {
         return (view) -> {
-            ItemClickHandler.INSTANCE.onClick(view);
+            mLauncher.getItemOnClickListener().onClick(view);
             close(true);
         };
     }
 
+    public PopupItemDragHandler getItemDragHandler() {
+        return mPopupItemDragHandler;
+    }
+
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -201,18 +195,35 @@
             icon.clearFocus();
             return null;
         }
-        ItemInfo itemInfo = (ItemInfo) icon.getTag();
-        if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
+        ItemInfo item = (ItemInfo) icon.getTag();
+        if (!ShortcutUtil.supportsShortcuts(item)) {
             return null;
         }
 
         final PopupContainerWithArrow container =
                 (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                         R.layout.popup_container, launcher.getDragLayer(), false);
-        container.populateAndShow(icon, itemInfo);
+        container.configureForLauncher(launcher);
+
+        PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+        container.populateAndShow(icon,
+                popupDataProvider.getShortcutCountForItem(item),
+                popupDataProvider.getNotificationKeysForItem(item),
+                launcher.getSupportedShortcuts()
+                        .map(s -> s.getShortcut(launcher, item))
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList()));
+        launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
         return container;
     }
 
+    private void configureForLauncher(Launcher launcher) {
+        addOnAttachStateChangeListener(new LiveUpdateHandler(launcher));
+        mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+        mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
+        launcher.getDragController().addDragListener(this);
+    }
+
     @Override
     protected void onInflationComplete(boolean isReversed) {
         if (isReversed && mNotificationItemView != null) {
@@ -234,23 +245,8 @@
         }
     }
 
-    protected void populateAndShow(BubbleTextView icon, ItemInfo item) {
-        PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
-        populateAndShow(icon,
-                popupDataProvider.getShortcutCountForItem(item),
-                popupDataProvider.getNotificationKeysForItem(item),
-                mLauncher.getSupportedShortcuts()
-                        .map(s -> s.getShortcut(mLauncher, item))
-                        .filter(s -> s != null)
-                        .collect(Collectors.toList()));
-    }
-
-    public ViewGroup getSystemShortcutContainerForTesting() {
-        return mSystemShortcutContainer;
-    }
-
     @TargetApi(Build.VERSION_CODES.P)
-    protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
+    public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
             final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
         mNumNotifications = notificationKeys.size();
         mOriginalIcon = originalIcon;
@@ -316,7 +312,6 @@
             setAccessibilityPaneTitle(getTitleForAccessibility());
         }
 
-        mLauncher.getDragController().addDragListener(this);
         mOriginalIcon.setForceHideDot(true);
 
         // All views are added. Animate layout from now on.
@@ -390,44 +385,6 @@
         }
     }
 
-    @Override
-    public void onWidgetsBound() {
-        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
-        SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
-        View widgetsView = null;
-        int count = mSystemShortcutContainer.getChildCount();
-        for (int i = 0; i < count; i++) {
-            View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
-            if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
-                widgetsView = systemShortcutView;
-                break;
-            }
-        }
-
-        if (widgetInfo != null && widgetsView == null) {
-            // We didn't have any widgets cached but now there are some, so enable the shortcut.
-            if (mSystemShortcutContainer != this) {
-                initializeSystemShortcut(
-                        R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo);
-            } else {
-                // If using the expanded system shortcut (as opposed to just the icon), we need to
-                // reopen the container to ensure measurements etc. all work out. While this could
-                // be quite janky, in practice the user would typically see a small flicker as the
-                // animation restarts partway through, and this is a very rare edge case anyway.
-                close(false);
-                PopupContainerWithArrow.showForIcon(mOriginalIcon);
-            }
-        } else if (widgetInfo == null && widgetsView != null) {
-            // No widgets exist, but we previously added the shortcut so remove it.
-            if (mSystemShortcutContainer != this) {
-                mSystemShortcutContainer.removeView(widgetsView);
-            } else {
-                close(false);
-                PopupContainerWithArrow.showForIcon(mOriginalIcon);
-            }
-        }
-    }
-
     private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
         View view = inflateAndAdd(
                 resId, container, getInsertIndexForSystemShortcut(container, info));
@@ -498,18 +455,6 @@
         };
     }
 
-    /**
-     * Updates the notification header if the original icon's dot updated.
-     */
-    @Override
-    public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
-        ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
-        PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
-        if (updatedDots.test(packageUser)) {
-            updateNotificationHeader();
-        }
-    }
-
     private void updateNotificationHeader() {
         ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
         DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
@@ -520,25 +465,6 @@
     }
 
     @Override
-    public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
-        if (mNotificationItemView == null) {
-            return;
-        }
-        ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
-        DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
-        if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
-            // No more notifications, remove the notification views and expand all shortcuts.
-            mNotificationItemView.removeAllViews();
-            mNotificationItemView = null;
-            updateHiddenShortcuts();
-            updateDividers();
-        } else {
-            mNotificationItemView.trimNotifications(
-                    NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
-        }
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d, boolean success) {  }
 
     @Override
@@ -592,47 +518,164 @@
         super.closeComplete();
     }
 
-    @Override
-    public boolean onTouch(View v, MotionEvent ev) {
-        // Touched a shortcut, update where it was touched so we can drag from there on long click.
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
-                break;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
-        // Return early if not the correct view
-        if (!(v.getParent() instanceof DeepShortcutView)) return false;
-
-        // Long clicked on a shortcut.
-        DeepShortcutView sv = (DeepShortcutView) v.getParent();
-        sv.setWillDrawIcon(false);
-
-        // Move the icon to align with the center-top of the touch point
-        Point iconShift = new Point();
-        iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
-        iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
-        DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
-                this, sv.getFinalInfo(),
-                new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
-        dv.animateShift(-iconShift.x, -iconShift.y);
-
-        // TODO: support dragging from within folder without having to close it
-        AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
-        return false;
-    }
-
     /**
      * Returns a PopupContainerWithArrow which is already open or null
      */
-    public static PopupContainerWithArrow getOpen(Launcher launcher) {
+    public static PopupContainerWithArrow getOpen(BaseDraggingActivity launcher) {
         return getOpenView(launcher, TYPE_ACTION_POPUP);
     }
+
+    /**
+     * Utility class to handle updates while the popup is visible (like widgets and
+     * notification changes)
+     */
+    private class LiveUpdateHandler implements
+            PopupDataChangeListener, View.OnAttachStateChangeListener {
+
+        private final Launcher mLauncher;
+
+        LiveUpdateHandler(Launcher launcher) {
+            mLauncher = launcher;
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View view) {
+            mLauncher.getPopupDataProvider().setChangeListener(this);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View view) {
+            mLauncher.getPopupDataProvider().setChangeListener(null);
+        }
+
+        @Override
+        public void onWidgetsBound() {
+            ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+            SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
+            View widgetsView = null;
+            int count = mSystemShortcutContainer.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+                if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+                    widgetsView = systemShortcutView;
+                    break;
+                }
+            }
+
+            if (widgetInfo != null && widgetsView == null) {
+                // We didn't have any widgets cached but now there are some, so enable the shortcut.
+                if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+                    initializeSystemShortcut(R.layout.system_shortcut_icon_only,
+                            mSystemShortcutContainer, widgetInfo);
+                } else {
+                    // If using the expanded system shortcut (as opposed to just the icon), we need
+                    // to reopen the container to ensure measurements etc. all work out. While this
+                    // could be quite janky, in practice the user would typically see a small
+                    // flicker as the animation restarts partway through, and this is a very rare
+                    // edge case anyway.
+                    close(false);
+                    PopupContainerWithArrow.showForIcon(mOriginalIcon);
+                }
+            } else if (widgetInfo == null && widgetsView != null) {
+                // No widgets exist, but we previously added the shortcut so remove it.
+                if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+                    mSystemShortcutContainer.removeView(widgetsView);
+                } else {
+                    close(false);
+                    PopupContainerWithArrow.showForIcon(mOriginalIcon);
+                }
+            }
+        }
+
+        /**
+         * Updates the notification header if the original icon's dot updated.
+         */
+        @Override
+        public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
+            ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+            PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+            if (updatedDots.test(packageUser)) {
+                updateNotificationHeader();
+            }
+        }
+
+
+        @Override
+        public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
+            if (mNotificationItemView == null) {
+                return;
+            }
+            ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+            DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
+            if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
+                // No more notifications, remove the notification views and expand all shortcuts.
+                mNotificationItemView.removeAllViews();
+                mNotificationItemView = null;
+                updateHiddenShortcuts();
+                updateDividers();
+            } else {
+                mNotificationItemView.trimNotifications(
+                        NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
+            }
+        }
+    }
+
+    /**
+     * Handler to control drag-and-drop for popup items
+     */
+    public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
+
+    /**
+     * Drag and drop handler for popup items in Launcher activity
+     */
+    public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
+
+        protected final Point mIconLastTouchPos = new Point();
+        private final Launcher mLauncher;
+        private final PopupContainerWithArrow mContainer;
+
+        LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
+            mLauncher = launcher;
+            mContainer = container;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent ev) {
+            // Touched a shortcut, update where it was touched so we can drag from there on
+            // long click.
+            switch (ev.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                case MotionEvent.ACTION_MOVE:
+                    mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+                    break;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean onLongClick(View v) {
+            if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+            // Return early if not the correct view
+            if (!(v.getParent() instanceof DeepShortcutView)) return false;
+
+            // Long clicked on a shortcut.
+            DeepShortcutView sv = (DeepShortcutView) v.getParent();
+            sv.setWillDrawIcon(false);
+
+            // Move the icon to align with the center-top of the touch point
+            Point iconShift = new Point();
+            iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+            iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+            DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+                    mContainer, sv.getFinalInfo(),
+                    new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
+                    new DragOptions());
+            dv.animateShift(-iconShift.x, -iconShift.y);
+
+            // TODO: support dragging from within folder without having to close it
+            AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+            return false;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index c5aa836..1092c7b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -24,7 +24,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.notification.NotificationKeyData;
@@ -41,6 +40,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -52,7 +52,7 @@
     private static final boolean LOGD = false;
     private static final String TAG = "PopupDataProvider";
 
-    private final Launcher mLauncher;
+    private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
 
     /** Maps launcher activity components to a count of how many shortcuts they have. */
     private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
@@ -63,12 +63,12 @@
 
     private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
 
-    public PopupDataProvider(Launcher launcher) {
-        mLauncher = launcher;
+    public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
+        mNotificationDotsChangeListener = notificationDotsChangeListener;
     }
 
     private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        mLauncher.updateNotificationDots(updatedDots);
+        mNotificationDotsChangeListener.accept(updatedDots);
         mChangeListener.onNotificationDotsUpdated(updatedDots);
     }
 
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 947f49d..9faeb40 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -24,8 +24,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.notification.NotificationInfo;
@@ -33,7 +33,6 @@
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -123,7 +122,11 @@
         return filteredShortcuts;
     }
 
-    public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+    /**
+     * Returns a runnable to update the provided shortcuts and notifications
+     */
+    public static Runnable createUpdateRunnable(final BaseDraggingActivity launcher,
+            final ItemInfo originalInfo,
             final Handler uiHandler, final PopupContainerWithArrow container,
             final List<DeepShortcutView> shortcutViews,
             final List<NotificationKeyData> notificationKeys) {
@@ -162,11 +165,6 @@
                 final DeepShortcutView view = shortcutViews.get(i);
                 uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
             }
-
-            // This ensures that mLauncher.getWidgetsForPackageUser()
-            // doesn't return null (it puts all the widgets in memory).
-            uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
-                    PackageUserKey.fromItemInfo(originalInfo)));
         };
     }
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 48f1c49..21c5ac5 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -16,14 +16,10 @@
 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;
@@ -176,33 +172,6 @@
         }
     }
 
-    public static final Factory<Launcher> DISMISS_PREDICTION = (launcher, itemInfo) -> {
-        if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
-        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION
-                && itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            return null;
-        }
-        return new DismissPrediction(launcher, itemInfo);
-    };
-
-    public static class DismissPrediction extends SystemShortcut<Launcher> {
-        public DismissPrediction(Launcher launcher, ItemInfo itemInfo) {
-            super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label, launcher,
-                    itemInfo);
-        }
-
-        @Override
-        public void onClick(View view) {
-            PopupContainerWithArrow.closeAllOpenViews(mTarget);
-            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
-                    ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
-            AppLaunchTracker.INSTANCE.get(view.getContext()).onDismissApp(
-                    mItemInfo.getTargetComponent(),
-                    mItemInfo.user,
-                    AppLaunchTracker.CONTAINER_PREDICTIONS);
-        }
-    }
-
     public static void dismissTaskMenuView(BaseDraggingActivity activity) {
         AbstractFloatingView.closeOpenViews(activity, true,
             AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
new file mode 100644
index 0000000..54b7fb9
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Process;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AppInfoComparator;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Adapter to manage pinned apps and show then in a grid.
+ */
+public class PinnedAppsAdapter extends BaseAdapter implements OnSharedPreferenceChangeListener {
+
+    private static final String PINNED_APPS_KEY = "pinned_apps";
+
+    private final SecondaryDisplayLauncher mLauncher;
+    private final OnClickListener mOnClickListener;
+    private final OnLongClickListener mOnLongClickListener;
+    private final SharedPreferences mPrefs;
+    private final AllAppsStore mAllAppsList;
+    private final AppInfoComparator mAppNameComparator;
+
+    private final Set<ComponentKey> mPinnedApps = new HashSet<>();
+    private final ArrayList<AppInfo> mItems = new ArrayList<>();
+
+    public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+            OnLongClickListener onLongClickListener) {
+        mLauncher = launcher;
+        mOnClickListener = launcher.getItemOnClickListener();
+        mOnLongClickListener = onLongClickListener;
+        mAllAppsList = allAppsStore;
+        mPrefs = launcher.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE);
+        mAppNameComparator = new AppInfoComparator(launcher);
+
+        mAllAppsList.addUpdateListener(this::createFilteredAppsList);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (PINNED_APPS_KEY.equals(key)) {
+            Executors.MODEL_EXECUTOR.submit(() -> {
+                Set<ComponentKey> apps = prefs.getStringSet(key, Collections.emptySet())
+                        .stream()
+                        .map(this::parseComponentKey)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toSet());
+                Executors.MAIN_EXECUTOR.submit(() -> {
+                    mPinnedApps.clear();
+                    mPinnedApps.addAll(apps);
+                    createFilteredAppsList();
+                });
+            });
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getCount() {
+        return mItems.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public AppInfo getItem(int position) {
+        return mItems.get(position);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View getView(int position, View view, ViewGroup parent) {
+        BubbleTextView icon;
+        if (view instanceof BubbleTextView) {
+            icon = (BubbleTextView) view;
+        } else {
+            icon = (BubbleTextView) LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.app_icon, parent, false);
+            icon.setOnClickListener(mOnClickListener);
+            icon.setOnLongClickListener(mOnLongClickListener);
+            icon.setLongPressTimeoutFactor(1f);
+            int padding = mLauncher.getDeviceProfile().edgeMarginPx;
+            icon.setPadding(padding, padding, padding, padding);
+        }
+
+        icon.applyFromApplicationInfo(mItems.get(position));
+        return icon;
+    }
+
+    private void createFilteredAppsList() {
+        mItems.clear();
+        mPinnedApps.stream().map(mAllAppsList::getApp)
+                .filter(Objects::nonNull).forEach(mItems::add);
+        mItems.sort(mAppNameComparator);
+        notifyDataSetChanged();
+    }
+
+    /**
+     * Initialized the pinned apps list and starts listening for changes
+     */
+    public void init() {
+        mPrefs.registerOnSharedPreferenceChangeListener(this);
+        onSharedPreferenceChanged(mPrefs, PINNED_APPS_KEY);
+    }
+
+    /**
+     * Stops listening for any pinned apps changes
+     */
+    public void destroy() {
+        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+    }
+
+    private void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
+        ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+        if (op.apply(key)) {
+            createFilteredAppsList();
+            Set<ComponentKey> copy = new HashSet<>(mPinnedApps);
+            Executors.MODEL_EXECUTOR.submit(() ->
+                    mPrefs.edit().putStringSet(PINNED_APPS_KEY,
+                        copy.stream().map(this::encode).collect(Collectors.toSet()))
+                        .apply());
+        }
+    }
+
+    private ComponentKey parseComponentKey(String string) {
+        try {
+            String[] parts = string.split("#");
+            UserHandle user;
+            if (parts.length > 2) {
+                user = UserCache.INSTANCE.get(mLauncher)
+                        .getUserForSerialNumber(Long.parseLong(parts[2]));
+            } else {
+                user = Process.myUserHandle();
+            }
+            ComponentName cn = ComponentName.unflattenFromString(parts[0]);
+            return new ComponentKey(cn, user);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private String encode(ComponentKey key) {
+        return key.componentName.flattenToShortString() + "#"
+                + UserCache.INSTANCE.get(mLauncher).getSerialNumberForUser(key.user);
+    }
+
+    /**
+     * Returns a system shortcut to pin/unpin a shortcut
+     */
+    public SystemShortcut getSystemShortcut(ItemInfo info) {
+        return new PinUnPinShortcut(mLauncher, info,
+                mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
+    }
+
+    private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> {
+
+        private final boolean mIsPinned;
+
+        PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+            super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
+                    isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
+                    target, info);
+            mIsPinned = isPinned;
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (mIsPinned) {
+                update(mItemInfo, mPinnedApps::remove);
+            } else {
+                update(mItemInfo, mPinnedApps::add);
+            }
+            AbstractFloatingView.closeAllOpenViews(mLauncher);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
new file mode 100644
index 0000000..1cc01f4
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
+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;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher activity for secondary displays
+ */
+public class SecondaryDisplayLauncher extends BaseDraggingActivity
+        implements BgDataModel.Callbacks {
+
+    private LauncherModel mModel;
+
+    private BaseDragLayer mDragLayer;
+    private AllAppsContainerView mAppsView;
+    private View mAppsButton;
+
+    private PopupDataProvider mPopupDataProvider;
+
+    private boolean mAppDrawerShown = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mModel = LauncherAppState.getInstance(this).getModel();
+        if (getWindow().getDecorView().isAttachedToWindow()) {
+            initUi();
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        initUi();
+    }
+
+    private void initUi() {
+        if (mDragLayer != null) {
+            return;
+        }
+        InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
+        InvariantDeviceProfile currentDisplayIdp =
+                new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
+
+        // Pick the device profile with the smaller icon size so that the cached icons are
+        // shown properly
+        if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
+            mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
+        } else {
+            mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
+        }
+
+        setContentView(R.layout.secondary_launcher);
+        mDragLayer = findViewById(R.id.drag_layer);
+        mAppsView = findViewById(R.id.apps_view);
+        mAppsButton = findViewById(R.id.all_apps_button);
+
+        mPopupDataProvider = new PopupDataProvider(
+                mAppsView.getAppsStore()::updateNotificationDots);
+
+        mModel.addCallbacksAndLoad(this);
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+            // Hide keyboard.
+            final View v = getWindow().peekDecorView();
+            if (v != null && v.getWindowToken() != null) {
+                getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+                        v.getWindowToken(), 0);
+            }
+        }
+
+        // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
+        showAppDrawer(false);
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (finishAutoCancelActionMode()) {
+            return;
+        }
+
+        // Note: There should be at most one log per method call. This is enforced implicitly
+        // by using if-else statements.
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+        if (topView != null && topView.onBackPressed()) {
+            // Handled by the floating view.
+        } else {
+            showAppDrawer(false);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mModel.removeCallbacks(this);
+    }
+
+    public boolean isAppDrawerShown() {
+        return mAppDrawerShown;
+    }
+
+    public AllAppsContainerView getAppsView() {
+        return mAppsView;
+    }
+
+    @Override
+    public <T extends View> T getOverviewPanel() {
+        return null;
+    }
+
+    @Override
+    public View getRootView() {
+        return mDragLayer;
+    }
+
+    @Override
+    public ActivityOptions getActivityLaunchOptions(View v) {
+        return null;
+    }
+
+    @Override
+    protected void reapplyUi() { }
+
+    @Override
+    public BaseDragLayer getDragLayer() {
+        return mDragLayer;
+    }
+
+    @Override
+    public int getPageToBindSynchronously() {
+        return 0;
+    }
+
+    @Override
+    public void clearPendingBinds() { }
+
+    @Override
+    public void startBinding() { }
+
+    @Override
+    public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+    @Override
+    public void bindScreens(IntArray orderedScreenIds) { }
+
+    @Override
+    public void finishFirstPageBind(ViewOnDrawExecutor executor) {
+        if (executor != null) {
+            executor.onLoadAnimationCompleted();
+        }
+    }
+
+    @Override
+    public void finishBindingItems(int pageBoundFirst) { }
+
+    @Override
+    public void preAddApps() { }
+
+    @Override
+    public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+            ArrayList<ItemInfo> addAnimated) { }
+
+    @Override
+    public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
+        mAppsView.getAppsStore().updatePromiseAppProgress(app);
+    }
+
+    @Override
+    public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+
+    @Override
+    public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+    @Override
+    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+    @Override
+    public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+    @Override
+    public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+
+    @Override
+    public void onPageBoundSynchronously(int page) { }
+
+    @Override
+    public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+        executor.attachTo(getDragLayer(), false, null);
+    }
+
+    /**
+     * Called when apps-button is clicked
+     */
+    public void onAppsButtonClicked(View v) {
+        showAppDrawer(true);
+    }
+
+    /**
+     * Show/hide app drawer card with animation.
+     */
+    public void showAppDrawer(boolean show) {
+        if (show == mAppDrawerShown) {
+            return;
+        }
+
+        float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
+        float closeR = Themes.getDialogCornerRadius(this);
+        float startR = mAppsButton.getWidth() / 2f;
+
+        float[] buttonPos = new float[] { startR, startR};
+        mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
+        mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
+        final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
+                (int) buttonPos[0], (int) buttonPos[1],
+                show ? closeR : openR, show ? openR : closeR);
+
+        if (show) {
+            mAppDrawerShown = true;
+            mAppsView.setVisibility(View.VISIBLE);
+            mAppsButton.setVisibility(View.INVISIBLE);
+        } else {
+            mAppDrawerShown = false;
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mAppsView.setVisibility(View.INVISIBLE);
+                    mAppsButton.setVisibility(View.VISIBLE);
+                    mAppsView.getSearchUiManager().resetSearch();
+                }
+            });
+        }
+        animator.start();
+    }
+
+    @Override
+    public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
+        mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
+    }
+
+    @Override
+    public void bindAllApplications(AppInfo[] apps) {
+        mAppsView.getAppsStore().setApps(apps);
+    }
+
+    public PopupDataProvider getPopupDataProvider() {
+        return mPopupDataProvider;
+    }
+
+    @Override
+    public OnClickListener getItemOnClickListener() {
+        return this::onIconClicked;
+    }
+
+    private void onIconClicked(View v) {
+        // 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) return;
+
+        Object tag = v.getTag();
+        if (tag instanceof ItemInfo) {
+            ItemInfo item = (ItemInfo) tag;
+            Intent intent;
+            if (item instanceof PromiseAppInfo) {
+                PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+                intent = promiseAppInfo.getMarketIntent(this);
+            } else {
+                intent = item.getIntent();
+            }
+            if (intent == null) {
+                throw new IllegalArgumentException("Input must have a valid intent");
+            }
+            startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
new file mode 100644
index 0000000..8fffee8
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.GridView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * DragLayer for Secondary launcher
+ */
+public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher> {
+
+    private View mAllAppsButton;
+    private AllAppsContainerView mAppsView;
+
+    private GridView mWorkspace;
+    private PinnedAppsAdapter mPinnedAppsAdapter;
+
+    public SecondaryDragLayer(Context context, AttributeSet attrs) {
+        super(context, attrs, 1 /* alphaChannelCount */);
+        mControllers = new TouchController[] {new CloseAllAppsTouchController()};
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mAllAppsButton = findViewById(R.id.all_apps_button);
+
+        mAppsView = findViewById(R.id.apps_view);
+        mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
+
+        // Setup workspace
+        mWorkspace = findViewById(R.id.workspace_grid);
+        mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
+                this::onIconLongClicked);
+        mWorkspace.setAdapter(mPinnedAppsAdapter);
+        mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mPinnedAppsAdapter.init();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mPinnedAppsAdapter.destroy();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+        setMeasuredDimension(width, height);
+
+        DeviceProfile grid = mActivity.getDeviceProfile();
+        InvariantDeviceProfile idp = grid.inv;
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child == mAppsView) {
+                int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+                        + grid.cellLayoutPaddingLeftRightPx);
+                int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+
+                int appsWidth = Math.min(width, maxWidth);
+                int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+                mAppsView.measure(
+                        makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
+
+            } else if (child == mAllAppsButton) {
+                int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY);
+                mAllAppsButton.measure(appsButtonSpec, appsButtonSpec);
+
+            } else if (child == mWorkspace) {
+                measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec,
+                        grid.iconSizePx + grid.edgeMarginPx);
+
+            } else {
+                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            }
+        }
+    }
+
+    private class CloseAllAppsTouchController implements TouchController {
+
+        @Override
+        public boolean onControllerTouchEvent(MotionEvent ev) {
+            return false;
+        }
+
+        @Override
+        public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+            if (!mActivity.isAppDrawerShown()) {
+                return false;
+            }
+
+            if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+                return false;
+            }
+
+            if (ev.getAction() == MotionEvent.ACTION_DOWN
+                    && !isEventOverView(mActivity.getAppsView(), ev)) {
+                mActivity.showAppDrawer(false);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private boolean onIconLongClicked(View v) {
+        if (!(v instanceof BubbleTextView)) {
+            return false;
+        }
+        if (PopupContainerWithArrow.getOpen(mActivity) != null) {
+            // There is already an items container open, so don't open this one.
+            v.clearFocus();
+            return false;
+        }
+        ItemInfo item = (ItemInfo) v.getTag();
+        if (!ShortcutUtil.supportsShortcuts(item)) {
+            return false;
+        }
+        final PopupContainerWithArrow container =
+                (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
+                        R.layout.popup_container, mActivity.getDragLayer(), false);
+
+        container.populateAndShow((BubbleTextView) v,
+                mActivity.getPopupDataProvider().getShortcutCountForItem(item),
+                Collections.emptyList(),
+                Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
+                        APP_INFO.getShortcut(mActivity, item)));
+        v.getParent().requestDisallowInterceptTouchEvent(true);
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9274d44..9cc7d8f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -27,8 +27,8 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 
 /**
@@ -111,8 +111,8 @@
 
         // TODO: Add the click handler to this view directly and not the child view.
         mBubbleText.setOnClickListener(container.getItemClickListener());
-        mBubbleText.setOnLongClickListener(container);
-        mBubbleText.setOnTouchListener(container);
+        mBubbleText.setOnLongClickListener(container.getItemDragHandler());
+        mBubbleText.setOnTouchListener(container.getItemDragHandler());
     }
 
     /**
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3ec480d..7ae0526 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -23,7 +23,7 @@
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
@@ -380,6 +380,7 @@
 
         final LauncherState targetState;
         final float progress = mCurrentAnimation.getProgressFraction();
+        final float progressVelocity = velocity * mProgressMultiplier;
         final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
             targetState =
@@ -406,7 +407,7 @@
                 startProgress = 1;
             } else {
                 startProgress = Utilities.boundToRange(progress
-                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+                        + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
                 duration = BaseSwipeDetector.calculateDuration(velocity,
                         endProgress - Math.max(progress, 0)) * durationMultiplier;
             }
@@ -421,7 +422,7 @@
                 startProgress = 0;
             } else {
                 startProgress = Utilities.boundToRange(progress
-                        + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+                        + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
                 duration = BaseSwipeDetector.calculateDuration(velocity,
                         Math.min(progress, 1) - endProgress) * durationMultiplier;
             }
@@ -433,8 +434,8 @@
         maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
         updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
                 targetState, velocity, fling);
-        mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
-        if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
+        mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity);
+        if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 8529d50..d3dac04 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -128,8 +128,10 @@
         public final DisplayMetrics metrics;
 
         private Info(Context context) {
-            Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+            this(context.getSystemService(WindowManager.class).getDefaultDisplay());
+        }
 
+        public Info(Display display) {
             id = display.getDisplayId();
             rotation = display.getRotation();
 
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 451ae28..82e24c2 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -29,6 +29,7 @@
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * An executor which runs all the tasks after the first onDraw is called on the target view.
@@ -38,7 +39,7 @@
 
     private final ArrayList<Runnable> mTasks = new ArrayList<>();
 
-    private Launcher mLauncher;
+    private Consumer<ViewOnDrawExecutor> mOnClearCallback;
     private View mAttachedView;
     private boolean mCompleted;
 
@@ -46,11 +47,16 @@
     private boolean mFirstDrawCompleted;
 
     public void attachTo(Launcher launcher) {
-        attachTo(launcher, launcher.getWorkspace(), true /* waitForLoadAnimation */);
+        attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
+                launcher::clearPendingExecutor);
     }
 
-    public void attachTo(Launcher launcher, View attachedView, boolean waitForLoadAnimation) {
-        mLauncher = launcher;
+    /**
+     * Attached the executor to the existence of the view
+     */
+    public void attachTo(View attachedView, boolean waitForLoadAnimation,
+            Consumer<ViewOnDrawExecutor> onClearCallback) {
+        mOnClearCallback = onClearCallback;
         mAttachedView = attachedView;
         mAttachedView.addOnAttachStateChangeListener(this);
         if (!waitForLoadAnimation) {
@@ -110,8 +116,8 @@
             mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
             mAttachedView.removeOnAttachStateChangeListener(this);
         }
-        if (mLauncher != null) {
-            mLauncher.clearPendingExecutor(this);
+        if (mOnClearCallback != null) {
+            mOnClearCallback.accept(this);
         }
         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
     }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 0c9a28b..fa625ed 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -399,7 +399,8 @@
         Drawable drawable = null;
         Drawable badge = null;
         boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                && !info.isDisabled(); // Use original icon for disabled icons.
         Drawable btvIcon = originalView instanceof BubbleTextView
                 ? ((BubbleTextView) originalView).getIcon() : null;
         if (info instanceof SystemShortcut) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5ba931d..880f123 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -126,7 +126,8 @@
         popup.mTargetRect = targetRect;
 
         for (OptionItem item : items) {
-            DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+            DeepShortcutView view =
+                    (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
             view.getIconView().setBackgroundResource(item.mIconRes);
             view.getBubbleText().setText(item.mLabelRes);
             view.setDividerVisibility(View.INVISIBLE);
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index c3186f6..b6c81ae 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -52,8 +52,6 @@
     private static final int WORK_EDU_PERSONAL_APPS = 1;
     private static final int WORK_EDU_WORK_APPS = 2;
 
-    private static LauncherStateManager.StateListener sStateListener;
-
     private Rect mInsets = new Rect();
     private View mViewWrapper;
     private Button mProceedButton;
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index fb17b4f..f8add9a 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -15,32 +15,61 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.WorkModeSwitch;
+import com.android.launcher3.pm.UserCache;
 
 /**
  * Container to show work footer in all-apps.
  */
-public class WorkFooterContainer extends RelativeLayout {
+public class WorkFooterContainer extends LinearLayout implements Insettable {
+    private Rect mInsets = new Rect();
+
+    private WorkModeSwitch mWorkModeSwitch;
+    private TextView mWorkModeLabel;
+
+    protected final ObjectAnimator mOpenCloseAnimator;
 
     public WorkFooterContainer(Context context) {
-        super(context);
+        this(context, null, 0);
     }
 
     public WorkFooterContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         updateTranslation();
+        this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
+        mWorkModeLabel = findViewById(R.id.work_mode_label);
     }
 
     @Override
@@ -56,4 +85,44 @@
             setTranslationY(Math.max(0, availableBot - getBottom()));
         }
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+                getPaddingBottom() + bottomInset);
+    }
+
+    /**
+     * Animates in/out work profile toggle panel based on the tab user is on
+     */
+    public void setWorkTabVisible(boolean workTabVisible) {
+        if (!shouldShowWorkFooter()) return;
+
+        mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
+        mOpenCloseAnimator.start();
+    }
+
+    /**
+     * Refreshes views based on current work profile enabled status
+     */
+    public void refresh() {
+        if (!shouldShowWorkFooter()) return;
+        boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
+                getContext()).isAnyProfileQuietModeEnabled();
+
+        mWorkModeLabel.setText(anyProfileQuietModeEnabled
+                ? R.string.work_mode_off_label : R.string.work_mode_on_label);
+        mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
+                anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
+        mWorkModeSwitch.refresh();
+    }
+
+    private boolean shouldShowWorkFooter() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
+                || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+                == PackageManager.PERMISSION_GRANTED);
+    }
 }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f713b33..f055adf 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -104,7 +104,7 @@
     }
 
     private void setContainerWidth() {
-        mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
+        mCellSize = (int) (mDeviceProfile.cellWidthPx * WIDTH_SCALE);
         mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
     }
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index be6f865..d6dfdd9 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -18,9 +18,13 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import android.os.SystemClock;
+
 import androidx.test.uiautomator.UiDevice;
 
 import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.regex.Pattern;
 
 class FailureInvestigator {
@@ -28,49 +32,83 @@
         return Pattern.compile(regex).matcher(string).find();
     }
 
-    static int getBugForFailure(CharSequence exception, String testsStartTime) {
+    static class LogcatMatch {
+        String logcatPattern;
+        int bug;
+
+        LogcatMatch(String logcatPattern, int bug) {
+            this.logcatPattern = logcatPattern;
+            this.bug = bug;
+        }
+    }
+
+    static class ExceptionMatch {
+        String exceptionPattern;
+        LogcatMatch[] logcatMatches;
+
+        ExceptionMatch(String exceptionPattern, LogcatMatch[] logcatMatches) {
+            this.exceptionPattern = exceptionPattern;
+            this.logcatMatches = logcatMatches;
+        }
+    }
+
+    private static final ExceptionMatch[] EXCEPTION_MATCHES = {
+            new ExceptionMatch(
+                    "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
+                            + "non-Launcher system error: Phone is locked",
+                    new LogcatMatch[]{
+                            new LogcatMatch(
+                                    "BroadcastQueue: Can't deliver broadcast to com.android"
+                                            + ".systemui.*Crashing it",
+                                    147845913),
+                            new LogcatMatch(
+                                    "Attempt to invoke virtual method 'boolean android\\"
+                                            + ".graphics\\.Bitmap\\.isRecycled\\(\\)' on a null "
+                                            + "object reference",
+                                    148424291),
+                            new LogcatMatch(
+                                    "java\\.lang\\.IllegalArgumentException\\: Ranking map "
+                                            + "doesn't contain key",
+                                    148570537),
+                    }),
+            new ExceptionMatch("Launcher didn't initialize",
+                    new LogcatMatch[]{
+                            new LogcatMatch(
+                                    "ActivityManager: Reason: executing service com.google"
+                                            + ".android.apps.nexuslauncher/com.android.launcher3"
+                                            + ".notification.NotificationListener",
+                                    148238677),
+                    }),
+    };
+
+    static int getBugForFailure(CharSequence exception) {
         if ("com.google.android.setupwizard".equals(
                 UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
             return 145935261;
         }
 
-        final String logSinceTestsStart;
+
+        final String logSinceBoot;
         try {
-            logSinceTestsStart =
+            final String systemBootTime =
+                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
+                            new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()));
+
+            logSinceBoot =
                     UiDevice.getInstance(getInstrumentation())
-                            .executeShellCommand("logcat -d -t " + testsStartTime.replace(" ", ""));
+                            .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
         } catch (IOException e) {
             return 0;
         }
 
-        if (matches(
-                "java.lang.AssertionError: http://go/tapl : Tests are broken by a non-Launcher "
-                        + "system error: Phone is locked",
-                exception)) {
-            if (matches(
-                    "BroadcastQueue: Can't deliver broadcast to com.android.systemui.*Crashing it",
-                    logSinceTestsStart)) {
-                return 147845913;
-            }
-            if (matches(
-                    "Attempt to invoke virtual method 'boolean android\\.graphics\\.Bitmap\\"
-                            + ".isRecycled\\(\\)' on a null object reference",
-                    logSinceTestsStart)) {
-                return 148424291;
-            }
-        } else if (matches("java.lang.AssertionError: Launcher build match not found", exception)) {
-            if (matches(
-                    "TestStabilityRule: Launcher package: com.google.android.setupwizard",
-                    logSinceTestsStart)) {
-                return 145935261;
-            }
-        } else if (matches("Launcher didn't initialize", exception)) {
-            if (matches(
-                    "ActivityManager: Reason: executing service com.google.android.apps"
-                            + ".nexuslauncher/com.android.launcher3.notification"
-                            + ".NotificationListener",
-                    logSinceTestsStart)) {
-                return 148238677;
+        for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
+            if (matches(exceptionMatch.exceptionPattern, exception)) {
+                for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
+                    if (matches(logcatMatch.logcatPattern, logSinceBoot)) {
+                        return logcatMatch.bug;
+                    }
+                }
+                break;
             }
         }
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
index 4cb309a..99ddee4 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
@@ -22,15 +22,9 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
 public class FailureRewriterRule implements TestRule {
     private static final String TAG = "FailureRewriter";
 
-    private static final String testsStartTime =
-            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
-
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
@@ -39,8 +33,7 @@
                 try {
                     base.evaluate();
                 } catch (Throwable e) {
-                    final int bug =
-                            FailureInvestigator.getBugForFailure(e.toString(), testsStartTime);
+                    final int bug = FailureInvestigator.getBugForFailure(e.toString());
                     if (bug == 0) throw e;
 
                     Log.e(TAG, "Known bug found for the original failure "
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 59d295f..9f29a1a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -25,6 +25,7 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
 import com.android.launcher3.testing.TestProtocol;
@@ -179,9 +180,12 @@
                     endX = startX;
                     endY = 0;
                 }
+                final boolean launcherIsVisible =
+                        mLauncher.hasLauncherObject(By.textStartsWith(""));
+                final boolean isZeroButton = mLauncher.getNavigationModel()
+                        == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
                 mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
-                        mLauncher.getNavigationModel()
-                                == LauncherInstrumentation.NavigationModel.ZERO_BUTTON
+                        launcherIsVisible && isZeroButton
                                 ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
                                 : LauncherInstrumentation.GestureScope.OUTSIDE
                 );
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 5555eab..cf5b24e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -797,7 +797,7 @@
         return mDevice.hasObject(getLauncherObjectSelector(resId));
     }
 
-    private boolean hasLauncherObject(BySelector selector) {
+    boolean hasLauncherObject(BySelector selector) {
         return mDevice.hasObject(makeLauncherSelector(selector));
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index ede5bd9..8659aa7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -24,6 +24,8 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+
 import java.util.Collection;
 
 /**
@@ -93,7 +95,7 @@
         int i = 0;
         for (; ; ) {
             final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
-                    widgetsContainer, "widgets_cell_list_container");
+                    widgetsContainer, "widgets_scroll_container");
             mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
             for (UiObject2 cell : cells) {
                 final UiObject2 label = cell.findObject(labelSelector);
@@ -105,6 +107,19 @@
                         "com.android.launcher3.widget.WidgetCell",
                         widget.getClassName());
 
+                int maxWidth = 0;
+                for (UiObject2 sibling : widget.getParent().getChildren()) {
+                    maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                }
+
+                int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+                if (visibleDelta > 0) {
+                    Rect parentBounds = cell.getVisibleBounds();
+                    mLauncher.linearGesture(parentBounds.centerX() + visibleDelta,
+                            parentBounds.centerY(), parentBounds.centerX(),
+                            parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+                }
+
                 if (widget.getVisibleBounds().bottom
                         <= displaySize.y - mLauncher.getBottomGestureSize()) {
                     return new Widget(mLauncher, widget);