Merge "Fill in LauncherTarget fields with values extracted from LauncherLogProto.Target." into ub-launcher3-qt-future-dev
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index 3aa783a..e40a9c2 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,7 +17,6 @@
 
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageInfo;
 import android.os.LocaleList;
 import android.os.UserHandle;
 
@@ -44,13 +43,6 @@
     }
 
     /**
-     * Returns the timestamp the entry was last updated in cache.
-     */
-    default long getLastUpdatedTime(T object, PackageInfo info) {
-        return info.lastUpdateTime;
-    }
-
-    /**
      * Returns true the object should be added to mem cache; otherwise returns false.
      */
     default boolean addToMemCache() {
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index bcdbce5..8224966 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,8 +171,7 @@
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
                 T app = componentMap.remove(component);
-                if (version == info.versionCode
-                        && updateTime == cachingLogic.getLastUpdatedTime(app, info)
+                if (version == info.versionCode && updateTime == info.lastUpdateTime
                         && TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.getIconSystemState(info.packageName))) {
 
@@ -232,6 +231,7 @@
         }
     }
 
+
     /**
      * A runnable that updates invalid icons and adds missing icons in the DB for the provided
      * LauncherActivityInfo list. Items are updated/added one at a time, so that the
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 4a3ad1d..e215cfe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -32,8 +32,8 @@
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -132,16 +132,33 @@
             // Fade in prediction icons quickly, then rest of all apps after reaching overview.
             float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
                     - OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(ACCEL,
-                    0, ALL_APPS_CONTENT_FADE_THRESHOLD));
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(LINEAR,
-                    progressToReachOverview, 1));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    0,
+                    ALL_APPS_CONTENT_FADE_THRESHOLD));
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    progressToReachOverview,
+                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
 
             // Get workspace out of the way quickly, to prepare for potential pause.
             builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
             builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
             builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
             return builder;
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            // Keep all apps/predictions opaque until the very end of the transition.
+            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    progressToReachOverview));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    1));
+            return builder;
         }
         return super.getAnimatorSetBuilderForStates(fromState, toState);
     }
@@ -164,6 +181,7 @@
 
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
             builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
             if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
                 builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
                 builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index c17b2fe..57e6d96 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -273,17 +273,7 @@
                     // from the side. Calculate the start translation based on current scale/scroll.
                     float currScale = recentsView.getScaleX();
                     float scrollOffsetX = recentsView.getScrollOffset();
-
-                    float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
-                    // The first task is hidden, so offset by its width.
-                    int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
-                    offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
-                    // Offset since scale pushes tasks outwards.
-                    offscreenX += firstTaskWidth * (currScale - 1) / 2;
-                    offscreenX = Math.max(0, offscreenX);
-                    if (recentsView.isRtl()) {
-                        offscreenX = -offscreenX;
-                    }
+                    float offscreenX = recentsView.getOffscreenTranslationX(currScale);
 
                     float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
                     float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
@@ -380,6 +370,10 @@
             TaskView runningTaskView = recentsView.getRunningTaskView();
             if (runningTaskView == null) {
                 runningTaskView = recentsView.getCurrentPageTaskView();
+                if (runningTaskView == null) {
+                    // There are no task views in LockTask mode when Overview is enabled.
+                    return;
+                }
             }
             TimeInterpolator oldInterpolator = translateY.getInterpolator();
             Rect fallbackInsets = launcher.getDeviceProfile().getInsets();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 79273ea..a8d402e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -161,9 +161,6 @@
 
         @Override
         public void run() {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "RecentsActivityCommand.run");
-            }
             long elapsedTime = mCreateTime - mLastToggleTime;
             mLastToggleTime = mCreateTime;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index e51ba63..c4d3fa0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -19,6 +19,8 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
+import android.os.SystemClock;
+import android.util.Log;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -38,6 +40,8 @@
  */
 public class RecentsAnimationWrapper {
 
+    private static final String TAG = "RecentsAnimationWrapper";
+
     // A list of callbacks to run when we receive the recents animation target. There are different
     // than the state callbacks as these run on the current worker thread.
     private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
@@ -125,6 +129,7 @@
             boolean sendUserLeaveHint) {
         SwipeAnimationTargetSet controller = targetSet;
         targetSet = null;
+        disableInputProxy();
         if (controller != null) {
             controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
         }
@@ -153,6 +158,16 @@
         mInputConsumerController.setInputListener(this::onInputConsumerEvent);
     }
 
+    private void disableInputProxy() {
+        if (mInputConsumer != null && mTouchInProgress) {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
+            mInputConsumer.onMotionEvent(dummyCancel);
+            dummyCancel.recycle();
+        }
+        mInputConsumerController.setInputListener(null);
+    }
+
     private boolean onInputConsumerEvent(InputEvent ev) {
         if (ev instanceof MotionEvent) {
             onInputConsumerMotionEvent((MotionEvent) ev);
@@ -168,6 +183,18 @@
 
     private boolean onInputConsumerMotionEvent(MotionEvent ev) {
         int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
             if (mInputConsumer == null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 03441c8..b601834 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -57,6 +57,8 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements StateListener {
 
+    private static final Rect sTempRect = new Rect();
+
     private final TransformParams mTransformParams = new TransformParams();
 
     public LauncherRecentsView(Context context) {
@@ -144,6 +146,25 @@
         LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
     }
 
+    /**
+     * @return The translationX to apply to this view so that the first task is just offscreen.
+     */
+    public float getOffscreenTranslationX(float recentsScale) {
+        float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX;
+        // Offset since scale pushes tasks outwards.
+        getTaskSize(sTempRect);
+        int taskWidth = sTempRect.width();
+        offscreenX += taskWidth * (recentsScale - 1) / 2;
+        if (mRunningTaskTileHidden) {
+            // The first task is hidden, so offset by its width.
+            offscreenX -= (taskWidth + getPageSpacing()) * recentsScale;
+        }
+        if (isRtl()) {
+            offscreenX = -offscreenX;
+        }
+        return offscreenX;
+    }
+
     @Override
     protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 12b37cb..554f437 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -272,7 +272,7 @@
 
     // Only valid until the launcher state changes to NORMAL
     protected int mRunningTaskId = -1;
-    private boolean mRunningTaskTileHidden;
+    protected boolean mRunningTaskTileHidden;
     private Task mTmpRunningTask;
 
     private boolean mRunningTaskIconScaledDown = false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
index 742d6a2..07d0796 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
@@ -161,9 +161,6 @@
     }
 
     public static TaskMenuView showForTask(TaskView taskView) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WELLBEING_NO_TASK_MENU, "showForTask");
-        }
         BaseDraggingActivity activity = BaseDraggingActivity.fromContext(taskView.getContext());
         final TaskMenuView taskMenuView = (TaskMenuView) activity.getLayoutInflater().inflate(
                         R.layout.task_menu, activity.getDragLayer(), false);
@@ -171,15 +168,9 @@
     }
 
     private boolean populateAndShowForTask(TaskView taskView) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WELLBEING_NO_TASK_MENU, "populateAndShowForTask1");
-        }
         if (isAttachedToWindow()) {
             return false;
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WELLBEING_NO_TASK_MENU, "populateAndShowForTask2");
-        }
         mActivity.getDragLayer().addView(this);
         mTaskView = taskView;
         addMenuOptions(mTaskView);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 7f1e898..044292a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -39,24 +39,28 @@
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.util.TaskCornerRadius;
+import com.android.systemui.plugins.OverviewScreenshotActions;
+import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * A task in the Recents view.
  */
-public class TaskThumbnailView extends View {
+public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
 
     private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
     private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
@@ -100,6 +104,7 @@
 
     private boolean mOverlayEnabled;
     private boolean mRotated;
+    private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -147,6 +152,11 @@
             mPaint.setShader(null);
             mOverlay.reset();
         }
+
+        if (mOverviewScreenshotActionsPlugin != null) {
+            mOverviewScreenshotActionsPlugin
+                .setupActions((ViewGroup) getTaskView(), getThumbnail(), mActivity);
+        }
         updateThumbnailPaintFilter();
     }
 
@@ -211,6 +221,33 @@
         canvas.restore();
     }
 
+    @Override
+    public void onPluginConnected(OverviewScreenshotActions overviewScreenshotActions,
+            Context context) {
+        mOverviewScreenshotActionsPlugin = overviewScreenshotActions;
+        mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
+    }
+
+    @Override
+    public void onPluginDisconnected(OverviewScreenshotActions plugin) {
+        if (mOverviewScreenshotActionsPlugin != null) {
+            mOverviewScreenshotActionsPlugin = null;
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext())
+            .addPluginListener(this, OverviewScreenshotActions.class);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
+    }
+
     public RectF getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
         // Don't show insets in multi window mode.
         return isMultiWindowMode ? EMPTY_RECT_F : mClippedInsets;
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 3eed281..51802df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -391,9 +391,6 @@
             mIconView.setDrawable(icon);
             mIconView.setOnClickListener(v -> showTaskMenu(Touch.TAP));
             mIconView.setOnLongClickListener(v -> {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.WELLBEING_NO_TASK_MENU, "setOnLongClickListener");
-                }
                 requestDisallowInterceptTouchEvent(true);
                 return showTaskMenu(Touch.LONGPRESS);
             });
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b81edfa..9813295 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -177,6 +177,20 @@
         return builder;
     }
 
+    private AnimatorSetBuilder getNormalToAllAppsAnimation() {
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
+                0, ALL_APPS_CONTENT_FADE_THRESHOLD));
+        return builder;
+    }
+
+    private AnimatorSetBuilder getAllAppsToNormalAnimation() {
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
+                1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
+        return builder;
+    }
+
     @Override
     protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
             LauncherState toState) {
@@ -187,6 +201,10 @@
             builder = getOverviewToAllAppsAnimation();
         } else if (fromState == ALL_APPS && toState == OVERVIEW) {
             builder = getAllAppsToOverviewAnimation();
+        } else if (fromState == NORMAL && toState == ALL_APPS) {
+            builder = getNormalToAllAppsAnimation();
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            builder = getAllAppsToNormalAnimation();
         }
         return builder;
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index dc6b56e..26e9eaf 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -161,7 +161,7 @@
                 mMidProgress =  OVERVIEW.getVerticalProgress(mLauncher);
                 Rect hotseatPadding = dp.getHotseatLayoutPadding();
                 int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
-                        - hotseatPadding.bottom - hotseatPadding.top;
+                        + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
                         Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
                 mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index e5f949b..8c11c1c 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -42,6 +42,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.rule.FailureWatcher;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -78,7 +79,7 @@
         Context context = instrumentation.getContext();
         mDevice = UiDevice.getInstance(instrumentation);
         mDevice.setOrientationNatural();
-        mLauncher = new LauncherInstrumentation(instrumentation);
+        mLauncher = new LauncherInstrumentation();
 
         mOrderSensitiveRules = RuleChain.
                 outerRule(new NavigationModeSwitchRule(mLauncher)).
@@ -103,6 +104,11 @@
                 }
             }
         };
+        if (TestHelpers.isInLauncherProcess()) {
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
+        }
     }
 
     @NavigationModeSwitch(mode = THREE_BUTTON)
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index c5b560c..2111e2c 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,7 +25,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -80,8 +79,6 @@
     @Test
     @NavigationModeSwitch
     public void testStressPressHome() {
-        if (LauncherInstrumentation.isAvd()) return; // b/136278866
-
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
@@ -94,8 +91,6 @@
     @Test
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
-        if (LauncherInstrumentation.isAvd()) return; // b/136278866
-
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e76b898..7be584e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -159,21 +159,6 @@
         <attr name="canThumbDetach" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="CustomAppWidgetProviderInfo">
-        <attr name="providerId" format="integer" />
-
-        <attr name="android:label" />
-        <attr name="android:initialLayout" />
-        <attr name="android:icon" />
-        <attr name="android:previewImage" />
-        <attr name="android:resizeMode" />
-
-        <attr name="numRows" />
-        <attr name="numColumns" />
-        <attr name="numMinRows" format="integer" />
-        <attr name="numMinColumns" format="integer" />
-    </declare-styleable>
-
     <declare-styleable name="PreviewFragment">
         <attr name="android:name" />
         <attr name="android:id" />
diff --git a/res/xml/custom_widgets.xml b/res/xml/custom_widgets.xml
deleted file mode 100644
index 4b54386..0000000
--- a/res/xml/custom_widgets.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<widgets>
-    <!-- Sample widget definition
-        <widget
-            android:label="My custom widget"
-            android:initialLayout="@layout/sample_widget_layout"
-            android:icon="@drawable/ic_launcher_home"
-            android:resizeMode="horizontal|vertical"
-            launcher:numRows="2"
-            launcher:numColumns="3"
-            launcher:numMinRows="1"
-            launcher:numMinColumns="2"
-            launcher:providerId="1" />
-    -->
-</widgets>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index b113249..7adb6a4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -104,6 +104,8 @@
     private Drawable mIcon;
     private final boolean mCenterVertically;
 
+    private final int mDisplay;
+
     private final CheckLongPressHelper mLongPressHelper;
     private final StylusEventHelper mStylusEventHelper;
     private final float mSlop;
@@ -133,6 +135,9 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDisableRelayout = false;
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final boolean mIgnorePaddingTouch;
+
     private IconLoadRequest mIconLoadRequest;
 
     public BubbleTextView(Context context) {
@@ -152,26 +157,32 @@
                 R.styleable.BubbleTextView, defStyle, 0);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
 
-        int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
+        mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
         final int defaultIconSize;
-        if (display == DISPLAY_WORKSPACE) {
+        if (mDisplay == DISPLAY_WORKSPACE) {
             DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
-        } else if (display == DISPLAY_ALL_APPS) {
+            mIgnorePaddingTouch = true;
+        } else if (mDisplay == DISPLAY_ALL_APPS) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
-        } else if (display == DISPLAY_FOLDER) {
+            mIgnorePaddingTouch = true;
+        } else if (mDisplay == DISPLAY_FOLDER) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+            mIgnorePaddingTouch = true;
         } else {
+            // widget_selection or shortcut_popup
             defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
+            mIgnorePaddingTouch = false;
         }
+
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
 
         mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride,
@@ -319,6 +330,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        // ignore events if they happen in padding area
+        if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
+                && (event.getY() < getPaddingTop()
+                || event.getX() < getPaddingLeft()
+                || event.getY() > getHeight() - getPaddingBottom()
+                || event.getX() > getWidth() - getPaddingRight())) {
+            return false;
+        }
+
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
         boolean result = super.onTouchEvent(event);
@@ -564,7 +584,11 @@
             mDotInfo = mActivity.getDotInfoForItem(itemInfo);
             boolean isDotted = mDotInfo != null;
             float newDotScale = isDotted ? 1f : 0;
-            mDotRenderer = mActivity.getDeviceProfile().mDotRenderer;
+            if (mDisplay == DISPLAY_ALL_APPS) {
+                mDotRenderer = mActivity.getDeviceProfile().mDotRendererAllApps;
+            } else {
+                mDotRenderer = mActivity.getDeviceProfile().mDotRendererWorkSpace;
+            }
             if (wasDotted || isDotted) {
                 // Animate when a dot is first added or when it is removed.
                 if (animate && (wasDotted ^ isDotted) && isShown()) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 44c3070..c034d2d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -129,7 +129,8 @@
     private boolean mIsSeascape;
 
     // Notification dots
-    public DotRenderer mDotRenderer;
+    public DotRenderer mDotRendererWorkSpace;
+    public DotRenderer mDotRendererAllApps;
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
             Point minSize, Point maxSize,
@@ -230,8 +231,11 @@
         updateWorkspacePadding();
 
         // This is done last, after iconSizePx is calculated above.
-        mDotRenderer = new DotRenderer(iconSizePx, IconShape.getShapePath(),
+        mDotRendererWorkSpace = new DotRenderer(iconSizePx, IconShape.getShapePath(),
                 IconShape.DEFAULT_PATH_SIZE);
+        mDotRendererAllApps = iconSizePx == allAppsIconSizePx ? mDotRendererWorkSpace :
+                new DotRenderer(allAppsIconSizePx, IconShape.getShapePath(),
+                        IconShape.DEFAULT_PATH_SIZE);
     }
 
     public DeviceProfile copy(Context context) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index fb80537..17fbb79 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -619,6 +619,11 @@
     }
 
     private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) {
+        if (data == null) {
+            Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
+            return null;
+        }
+
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d667e8c..7bb618d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -79,6 +79,7 @@
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -147,7 +148,7 @@
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetListRowEntry;
 import com.android.launcher3.widget.WidgetsFullSheet;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -158,9 +159,6 @@
 import java.util.List;
 import java.util.function.Predicate;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
 /**
  * Default launcher application.
  */
@@ -1666,10 +1664,9 @@
         } else {
             // In this case, we either need to start an activity to get permission to bind
             // the widget, or we need to start an activity to configure the widget, or both.
-            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS &&
-                    info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
-                appWidgetId = CustomWidgetParser.getWidgetIdForCustomProvider(
-                        this, info.componentName);
+            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
+                appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider(
+                        info.componentName);
             } else {
                 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
             }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index b4a2216..d70abc2 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 public class LauncherAppState {
 
@@ -149,6 +150,8 @@
     LauncherModel setLauncher(Launcher launcher) {
         getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
         mModel.initialize(launcher);
+        CustomWidgetManager.INSTANCE.get(launcher)
+                .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
         return mModel;
     }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java
index 64ec730..1215d43 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHost.java
@@ -27,12 +27,12 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.util.SparseArray;
-import android.view.LayoutInflater;
 import android.widget.Toast;
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.widget.DeferredAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.function.IntConsumer;
@@ -193,10 +193,8 @@
             LauncherAppWidgetProviderInfo appWidget) {
         if (appWidget.isCustomWidget()) {
             LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
-            LayoutInflater inflater = (LayoutInflater)
-                    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            inflater.inflate(appWidget.initialLayout, lahv);
             lahv.setAppWidget(0, appWidget);
+            CustomWidgetManager.INSTANCE.get(context).onViewCreated(lahv);
             return lahv;
         } else if ((mFlags & FLAG_LISTENING) == 0) {
             DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 14f6598..c0cf135 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
@@ -54,8 +53,6 @@
 import com.android.launcher3.model.ShortcutsChangedTask;
 import com.android.launcher3.model.UserLockStateChangedTask;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.PackageUserKey;
@@ -553,5 +550,4 @@
     public Callbacks getCallback() {
         return mCallbacks != null ? mCallbacks.get() : null;
     }
-
 }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 242e099..c509680 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -59,6 +59,10 @@
         public static final String ITEM_TYPE = "itemType";
 
         /**
+         * The gesture is a package
+         */
+        public static final int ITEM_TYPE_NON_ACTIONABLE = -1;
+        /**
          * The gesture is an application
          */
         public static final int ITEM_TYPE_APPLICATION = 0;
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 6bfae13..848e19f 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -227,11 +227,6 @@
 
     private void goToState(LauncherState state, boolean animated, long delay,
             final Runnable onCompleteRunnable) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " +
-                    state.getClass().getSimpleName() +
-                    " @ " + Log.getStackTraceString(new Throwable()));
-        }
         animated &= Utilities.areAnimationsEnabled(mLauncher);
         if (mLauncher.isInState(state)) {
             if (mConfig.mCurrentAnimation == null) {
@@ -412,11 +407,6 @@
             mState.onStateDisabled(mLauncher);
         }
         mState = state;
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " +
-                    state.getClass().getSimpleName() +
-                    " @ " + Log.getStackTraceString(new Throwable()));
-        }
         mState.onStateEnabled(mLauncher);
         mLauncher.onStateSetStart(mState);
 
@@ -436,11 +426,6 @@
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
             mCurrentStableState = state;
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " +
-                        state.getClass().getSimpleName() +
-                        " @ " + Log.getStackTraceString(new Throwable()));
-            }
         }
 
         state.onStateTransitionEnd(mLauncher);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a99c7c2..5c790f3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1548,7 +1548,7 @@
             snapToPage(getNextPage() - 1);
             return true;
         }
-        return false;
+        return onOverscroll(-getMeasuredWidth());
     }
 
     public boolean scrollRight() {
@@ -1556,7 +1556,15 @@
             snapToPage(getNextPage() + 1);
             return true;
         }
-        return false;
+        return onOverscroll(getMeasuredWidth());
+    }
+
+    protected boolean onOverscroll(int amount) {
+        if (!mAllowOverScroll) return false;
+        onScrollInteractionBegin();
+        overScroll(amount);
+        onScrollInteractionEnd();
+        return true;
     }
 
     @Override
@@ -1576,8 +1584,9 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         final boolean pagesFlipped = isPageOrderFlipped();
-        info.setScrollable(getPageCount() > 1);
-        if (getCurrentPage() < getPageCount() - 1) {
+        int offset = (mAllowOverScroll ? 0 : 1);
+        info.setScrollable(getPageCount() > offset);
+        if (getCurrentPage() < getPageCount() - offset) {
             info.addAction(pagesFlipped ?
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
@@ -1585,7 +1594,7 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT);
         }
-        if (getCurrentPage() > 0) {
+        if (getCurrentPage() >= offset) {
             info.addAction(pagesFlipped ?
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
@@ -1593,7 +1602,6 @@
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT
                 : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT);
         }
-
         // Accessibility-wise, PagedView doesn't support long click, so disabling it.
         // Besides disabling the accessibility long-click, this also prevents this view from getting
         // accessibility focus.
@@ -1612,7 +1620,7 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        event.setScrollable(getPageCount() > 1);
+        event.setScrollable(mAllowOverScroll || getPageCount() > 1);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f9201d0..474c59d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1035,6 +1035,13 @@
     }
 
     @Override
+    protected boolean onOverscroll(int amount) {
+        // Enforce overscroll on -1 direction
+        if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false;
+        return super.onOverscroll(amount);
+    }
+
+    @Override
     protected boolean shouldFlingForVelocity(int velocityX) {
         // When the overlay is moving, the fling or settle transition is controlled by the overlay.
         return Float.compare(Math.abs(mOverlayTranslation), 0) == 0 &&
@@ -1461,6 +1468,9 @@
 
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "beginDragShared");
+        }
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1486,7 +1496,7 @@
         Rect dragRect = null;
         if (child instanceof BubbleTextView) {
             dragRect = new Rect();
-            ((BubbleTextView) child).getIconBounds(dragRect);
+            BubbleTextView.getIconBounds(child, dragRect, grid.iconSizePx);
             dragLayerY += dragRect.top;
             // Note: The dragRect is used to calculate drag layer offsets, but the
             // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java
index 1323588..23795c5 100644
--- a/src/com/android/launcher3/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/WorkspaceItemInfo.java
@@ -212,7 +212,7 @@
     public ComponentName getTargetComponent() {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
-                || hasStatusFlag(FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON))) {
+                || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) {
             // Legacy shortcuts and promise icons with web UI may not have a componentName but just
             // a packageName. In that case create a dummy componentName instead of adding additional
             // check everywhere.
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
index 3243256..fc5d11c 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompat.java
@@ -23,19 +23,18 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.HashMap;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 public abstract class AppWidgetManagerCompat {
 
     private static final Object sInstanceLock = new Object();
@@ -63,11 +62,9 @@
     }
 
     public LauncherAppWidgetProviderInfo getLauncherAppWidgetInfo(int appWidgetId) {
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
-                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
-            return CustomWidgetParser.getWidgetProvider(mContext, appWidgetId);
+        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+            return CustomWidgetManager.INSTANCE.get(mContext).getWidgetProvider(appWidgetId);
         }
-
         AppWidgetProviderInfo info = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
         return info == null ? null : LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
     }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
index 1065748..c8b1f67 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVL.java
@@ -24,12 +24,15 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.custom.CustomWidgetParser;
+import com.android.launcher3.widget.custom.CustomAppWidgetProviderInfo;
+import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -37,8 +40,6 @@
 import java.util.Iterator;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 class AppWidgetManagerCompatVL extends AppWidgetManagerCompat {
 
     private final UserManager mUserManager;
@@ -54,14 +55,11 @@
             return Collections.emptyList();
         }
         if (packageUser == null) {
-            ArrayList<AppWidgetProviderInfo> providers = new ArrayList<AppWidgetProviderInfo>();
+            ArrayList<AppWidgetProviderInfo> providers = new ArrayList<>();
             for (UserHandle user : mUserManager.getUserProfiles()) {
                 providers.addAll(mAppWidgetManager.getInstalledProvidersForProfile(user));
             }
-
-            if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
-                providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
-            }
+            providers.addAll(getCustomWidgets());
             return providers;
         }
         // Only get providers for the given package/user.
@@ -74,9 +72,9 @@
             }
         }
 
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(packageUser.mUser)
+        if (Process.myUserHandle().equals(packageUser.mUser)
                 && mContext.getPackageName().equals(packageUser.mPackageName)) {
-            providers.addAll(CustomWidgetParser.getCustomWidgets(mContext));
+            providers.addAll(getCustomWidgets());
         }
         return providers;
     }
@@ -87,9 +85,7 @@
         if (FeatureFlags.GO_DISABLE_WIDGETS) {
             return false;
         }
-
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS
-                && appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
+        if (appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
             return true;
         }
         return mAppWidgetManager.bindAppWidgetIdIfAllowed(
@@ -108,9 +104,8 @@
             }
         }
 
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS && Process.myUserHandle().equals(user)) {
-            for (LauncherAppWidgetProviderInfo info :
-                    CustomWidgetParser.getCustomWidgets(mContext)) {
+        if (Process.myUserHandle().equals(user)) {
+            for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
                 if (info.provider.equals(provider)) {
                     return info;
                 }
@@ -131,13 +126,13 @@
                 result.put(new ComponentKey(info.provider, user), info);
             }
         }
-
-        if (FeatureFlags.ENABLE_CUSTOM_WIDGETS) {
-            for (LauncherAppWidgetProviderInfo info :
-                    CustomWidgetParser.getCustomWidgets(mContext)) {
-                result.put(new ComponentKey(info.provider, info.getProfile()), info);
-            }
+        for (LauncherAppWidgetProviderInfo info : getCustomWidgets()) {
+            result.put(new ComponentKey(info.provider, info.getProfile()), info);
         }
         return result;
     }
+
+    List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+        return CustomWidgetManager.INSTANCE.get(mContext).getCustomWidgets();
+    }
 }
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
index b7b0563..11ec333 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatVO.java
@@ -19,14 +19,14 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.Collections;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 class AppWidgetManagerCompatVO extends AppWidgetManagerCompatVL {
 
     AppWidgetManagerCompatVO(Context context) {
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 9455889..7c8fe16 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -23,11 +23,11 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.Keep;
-
 import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.Utilities;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.TogglableFlag;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.SortedMap;
@@ -74,9 +74,6 @@
     //Feature flag to enable pulling down navigation shade from workspace.
     public static final boolean PULL_DOWN_STATUS_BAR = true;
 
-    // When true, custom widgets are loaded using CustomWidgetParser.
-    public static final boolean ENABLE_CUSTOM_WIDGETS = false;
-
     // Features to control Launcher3Go behavior
     public static final boolean GO_DISABLE_WIDGETS = false;
 
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index b59164a..cdc7061 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -284,7 +284,8 @@
             // The child may be scaled (always about the center of the view) so to account for it,
             // we have to offset the position by the scaled size.  Once we do that, we can center
             // the drag view about the scaled child view.
-            toY += Math.round(toScale * tv.getPaddingTop());
+            // padding will remain constant (does not scale with size)
+            toY += tv.getPaddingTop();
             toY -= dragView.getMeasuredHeight() * (1 - toScale) / 2;
             if (dragView.getDragVisualizeOffset() != null) {
                 toY -=  Math.round(toScale * dragView.getDragVisualizeOffset().y);
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 589ad25..06b5c40 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -106,6 +106,7 @@
      * @return the vector at which the item was flung, or null if no fling was detected.
      */
     private PointF isFlingingToDelete() {
+        if (mVelocityTracker == null) return null;
         if (mDropTarget == null) {
             mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text);
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index a463c7a..3840639 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -176,7 +176,7 @@
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.mDotRenderer = grid.mDotRenderer;
+        icon.mDotRenderer = grid.mDotRendererWorkSpace;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 747efe3..f579451 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -195,15 +195,22 @@
 
         private final Bitmap mPreviewSnapshot;
         private final Context mContext;
+        private final boolean mIsIcon;
 
         OutlineGeneratorCallback(Bitmap preview) {
             mPreviewSnapshot = preview;
             mContext = mView.getContext();
+            mIsIcon = mView instanceof BubbleTextView;
         }
 
         @Override
         public void run() {
             Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
+            if (mIsIcon) {
+                int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx;
+                preview = Bitmap.createScaledBitmap(preview, size, size, false);
+            }
+            //else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
 
             // We start by removing most of the alpha channel so as to ignore shadows, and
             // other types of partial transparency when defining the shape of the object
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 11c7f20..9c46260 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -26,7 +26,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Process;
@@ -50,7 +49,6 @@
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.Preconditions;
 
@@ -65,7 +63,6 @@
 
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
-    private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
 
     private final LauncherAppsCompat mLauncherApps;
     private final UserManagerCompat mUserManager;
@@ -79,7 +76,6 @@
                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
-        mShortcutCachingLogic = new ShortcutCachingLogic();
         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mUserManager = UserManagerCompat.getInstance(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -178,14 +174,6 @@
     }
 
     /**
-     * Fill in info with the icon and label for deep shortcut.
-     */
-    public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
-        return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
-                () -> info, mShortcutCachingLogic, false, false);
-    }
-
-    /**
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index c6949af..adc92c4 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.Process;
 
 import androidx.annotation.Nullable;
@@ -32,8 +33,8 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.icons.cache.BaseIconCache;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.util.Themes;
 
 import java.util.function.Supplier;
@@ -125,12 +126,13 @@
 
     public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
             boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
+                .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-        BaseIconCache.CacheEntry entry = cache.getDeepShortcutTitleAndIcon(shortcutInfo);
 
         final Bitmap unbadgedBitmap;
-        if (entry.icon != null) {
-            unbadgedBitmap = entry.icon;
+        if (unbadgedDrawable != null) {
+            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
         } else {
             if (fallbackIconProvider != null) {
                 // Fallback icons are already badged and with appropriate shadow
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
deleted file mode 100644
index 5d696fd..0000000
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.ShortcutKey;
-
-/**
- * Caching logic for shortcuts.
- */
-public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
-
-    @Override
-    public ComponentName getComponent(ShortcutInfo info) {
-        return ShortcutKey.fromInfo(info).componentName;
-    }
-
-    @Override
-    public UserHandle getUser(ShortcutInfo info) {
-        return info.getUserHandle();
-    }
-
-    @Override
-    public CharSequence getLabel(ShortcutInfo info) {
-        return info.getShortLabel();
-    }
-
-    @Override
-    public void loadIcon(Context context, ShortcutInfo info, BitmapInfo target) {
-        LauncherIcons li = LauncherIcons.obtain(context);
-        Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
-                .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
-        if (unbadgedDrawable != null) {
-            target.icon = li.createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
-        }
-        li.recycle();
-    }
-
-    @Override
-    public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
-        if (shortcutInfo == null) return info.lastUpdateTime;
-        return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
-    }
-
-    @Override
-    public boolean addToMemCache() {
-        return false;
-    }
-}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 583ebee..50e1d56 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -63,7 +63,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherActivityCachingLogic;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.provider.ImportDataTask;
@@ -172,8 +171,7 @@
         TraceHelper.beginSection(TAG);
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
-            List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts);
+            loadWorkspace();
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
@@ -192,23 +190,18 @@
             TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
             List<LauncherActivityInfo> allActivityList = loadAllApps();
 
-            TraceHelper.partitionSection(TAG, "step 2.2: binding all apps");
+            TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
             verifyNotStopped();
             mResults.bindAllApps();
 
             verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 2.3: save app icons in icon cache");
+            TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
             updateHandler.updateIcons(allActivityList,
                     LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                     mApp.getModel()::onPackageIconsUpdated);
 
-            verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 2.4: save shortcuts in icon cache");
-            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
-                    mApp.getModel()::onPackageIconsUpdated);
-
             // Take a break
             TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
             waitForIdle();
@@ -216,17 +209,12 @@
 
             // third step
             TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
-            List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
+            loadDeepShortcuts();
 
             verifyNotStopped();
             TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
             mResults.bindDeepShortcuts();
 
-            verifyNotStopped();
-            TraceHelper.partitionSection(TAG, "step 3.3: save deep shortcuts in icon cache");
-            updateHandler.updateIcons(allDeepShortcuts,
-                    new ShortcutCachingLogic(), (pkgs, user) -> { });
-
             // Take a break
             TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
             waitForIdle();
@@ -241,6 +229,7 @@
             mResults.bindWidgets();
 
             verifyNotStopped();
+
             TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache");
             updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
                     mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
@@ -262,7 +251,7 @@
         this.notify();
     }
 
-    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+    private void loadWorkspace() {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -515,7 +504,6 @@
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                                     }
                                     intent = info.intent;
-                                    allDeepShortcuts.add(pinnedShortcut);
                                 } else {
                                     // Create a shortcut info in disabled mode for now.
                                     info = c.loadSimpleWorkspaceItem();
@@ -864,8 +852,7 @@
         return allActivityList;
     }
 
-    private List<ShortcutInfo> loadDeepShortcuts() {
-        List<ShortcutInfo> allShortcuts = new ArrayList<>();
+    private void loadDeepShortcuts() {
         mBgDataModel.deepShortcutMap.clear();
         mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
         if (mBgDataModel.hasShortcutHostPermission) {
@@ -873,12 +860,10 @@
                 if (mUserManager.isUserUnlocked(user)) {
                     List<ShortcutInfo> shortcuts =
                             mShortcutManager.queryForAllShortcuts(user);
-                    allShortcuts.addAll(shortcuts);
                     mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
                 }
             }
         }
-        return allShortcuts;
     }
 
     public static boolean isValidProvider(AppWidgetProviderInfo provider) {
diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java
index 741be66..3ef48cd 100644
--- a/src/com/android/launcher3/model/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/PackageItemInfo.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.model;
 
 import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.LauncherSettings;
 
 /**
  * Represents a {@link Package} in the widget tray section.
@@ -30,10 +31,12 @@
 
     public PackageItemInfo(String packageName) {
         this.packageName = packageName;
+        this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
     }
 
     public PackageItemInfo(PackageItemInfo copy) {
         this.packageName = copy.packageName;
+        this.itemType = LauncherSettings.Favorites.ITEM_TYPE_NON_ACTIONABLE;
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 5e6c7b8..d6ebaaf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -55,6 +55,9 @@
 import java.util.HashSet;
 import java.util.List;
 
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON;
+
 /**
  * Handles updates due to changes in package manager (app installed/updated/removed)
  * or when a user availability changes.
@@ -221,7 +224,7 @@
                                     isTargetValid = LauncherAppsCompat.getInstance(context)
                                             .isActivityEnabledForProfile(cn, mUser);
                                 }
-                                if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) {
+                                if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                     if (updateWorkspaceItemIntent(context, si, packageName)) {
                                         infoUpdated = true;
                                     } else if (si.hasPromiseIconUi()) {
diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java
index 32410a6..717a7e9 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
+
 import android.app.Notification;
 import android.content.Context;
 import android.graphics.Color;
@@ -33,8 +35,6 @@
 
 import java.util.List;
 
-import static com.android.launcher3.touch.SwipeDetector.HORIZONTAL;
-
 /**
  * Utility class to manage notification UI
  */
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index c2aabca..4833c26 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -65,6 +66,7 @@
 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
@@ -194,6 +196,9 @@
      * @return the container if shown or null.
      */
     public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon");
+        }
         Launcher launcher = Launcher.getLauncher(icon.getContext());
         if (getOpen(launcher) != null) {
             // There is already an items container open, so don't open this one.
@@ -235,6 +240,9 @@
 
     protected void populateAndShow(
             BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow");
+        }
         PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
         populateAndShow(icon,
                 popupDataProvider.getShortcutCountForItem(item),
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 0d1d1a6..d3ec83b 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,9 +15,10 @@
  */
 package com.android.launcher3.testing;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static android.graphics.Bitmap.Config.ARGB_8888;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 19c557c..62bb564 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -84,7 +84,5 @@
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
     public static final String APP_NOT_DISABLED = "b/139891609";
-    public static final String ALL_APPS_UPON_RECENTS = "b/139941530";
-    public static final String STABLE_STATE_MISMATCH = "b/140311911";
-    public static final String WELLBEING_NO_TASK_MENU = "b/141275518";
+    public static final String NO_CONTEXT_MENU = "b/141770616";
 }
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 6cd2b2d..86d2b39 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnLongClickListener;
 
@@ -33,6 +34,9 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.Arrays;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -75,10 +79,19 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick1");
+        }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick2");
+        }
         // When we have exited all apps or are in transition, disregard long clicks
         if (!launcher.isInState(ALL_APPS) && !launcher.isInState(OVERVIEW)) return false;
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_CONTEXT_MENU, "onAllAppsItemLongClick3");
+        }
         if (launcher.getWorkspace().isSwitchingState()) return false;
 
         // Start the drag
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 3777a41..d0edfd8 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -24,12 +24,11 @@
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Utilities;
+
 /**
  * One dimensional scroll/drag/swipe gesture detector.
  *
@@ -41,47 +40,14 @@
 
     private static final boolean DBG = false;
     private static final String TAG = "SwipeDetector";
+    private static final float ANIMATION_DURATION = 1200;
+    /** The minimum release velocity in pixels per millisecond that triggers fling.*/
+    private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
 
-    private int mScrollConditions;
     public static final int DIRECTION_POSITIVE = 1 << 0;
     public static final int DIRECTION_NEGATIVE = 1 << 1;
     public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
 
-    private static final float ANIMATION_DURATION = 1200;
-
-    protected int mActivePointerId = INVALID_POINTER_ID;
-
-    /**
-     * The minimum release velocity in pixels per millisecond that triggers fling..
-     */
-    public static final float RELEASE_VELOCITY_PX_MS = 1.0f;
-
-    /* Scroll state, this is set to true during dragging and animation. */
-    private ScrollState mState = ScrollState.IDLE;
-
-    enum ScrollState {
-        IDLE,
-        DRAGGING,      // onDragStart, onDrag
-        SETTLING       // onDragEnd
-    }
-
-    public static abstract class Direction {
-
-        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
-                boolean isRtl);
-
-        /**
-         * Distance in pixels a touch can wander before we think the user is scrolling.
-         */
-        abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
-
-        abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
-
-        abstract boolean isPositive(float displacement);
-
-        abstract boolean isNegative(float displacement);
-    }
-
     public static final Direction VERTICAL = new Direction() {
 
         @Override
@@ -150,35 +116,54 @@
         }
     };
 
-    //------------------- ScrollState transition diagram -----------------------------------
-    //
-    // IDLE ->      (mDisplacement > mTouchSlop) -> DRAGGING
-    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
-    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
-    // SETTLING -> (View settled) -> IDLE
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final Direction mDir;
+    private final boolean mIsRtl;
+    private final float mTouchSlop;
+    private final float mMaxVelocity;
+    /* Client of this gesture detector can register a callback. */
+    private final Listener mListener;
 
-    private void setState(ScrollState newState) {
-        if (DBG) {
-            Log.d(TAG, "setState:" + mState + "->" + newState);
-        }
-        // onDragStart and onDragEnd is reported ONLY on state transition
-        if (newState == ScrollState.DRAGGING) {
-            initializeDragging();
-            if (mState == ScrollState.IDLE) {
-                reportDragStart(false /* recatch */);
-            } else if (mState == ScrollState.SETTLING) {
-                reportDragStart(true /* recatch */);
-            }
-        }
-        if (newState == ScrollState.SETTLING) {
-            reportDragEnd();
-        }
+    private int mActivePointerId = INVALID_POINTER_ID;
+    private VelocityTracker mVelocityTracker;
+    private float mLastDisplacement;
+    private float mDisplacement;
+    private float mSubtractDisplacement;
+    private boolean mIgnoreSlopWhenSettling;
+    private int mScrollDirections;
+    private ScrollState mState = ScrollState.IDLE;
 
-        mState = newState;
+    private enum ScrollState {
+        IDLE,
+        DRAGGING,      // onDragStart, onDrag
+        SETTLING       // onDragEnd
     }
 
-    public boolean isDraggingOrSettling() {
-        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
+    public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
+        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+    }
+
+    @VisibleForTesting
+    protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+            @NonNull Direction dir, boolean isRtl) {
+        mListener = l;
+        mDir = dir;
+        mIsRtl = isRtl;
+        mTouchSlop = config.getScaledTouchSlop();
+        mMaxVelocity = config.getScaledMaximumFlingVelocity();
+    }
+
+    public static long calculateDuration(float velocity, float progressNeeded) {
+        // TODO: make these values constants after tuning.
+        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
+        float travelDistance = Math.max(0.2f, progressNeeded);
+        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+        if (DBG) {
+            Log.d(TAG, String.format(
+                    "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+        }
+        return duration;
     }
 
     public int getDownX() {
@@ -203,73 +188,31 @@
         return mState == ScrollState.DRAGGING;
     }
 
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-    private final Direction mDir;
-    private final boolean mIsRtl;
-
-    private final float mTouchSlop;
-    private final float mMaxVelocity;
-
-    /* Client of this gesture detector can register a callback. */
-    private final Listener mListener;
-
-    private VelocityTracker mVelocityTracker;
-
-    private float mLastDisplacement;
-    private float mDisplacement;
-
-    private float mSubtractDisplacement;
-    private boolean mIgnoreSlopWhenSettling;
-
-    public interface Listener {
-        void onDragStart(boolean start);
-
-        boolean onDrag(float displacement);
-
-        default boolean onDrag(float displacement, MotionEvent event) {
-            return onDrag(displacement);
-        }
-
-        void onDragEnd(float velocity, boolean fling);
-    }
-
-    public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) {
-        this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
-    }
-
-    @VisibleForTesting
-    protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
-            @NonNull Direction dir, boolean isRtl) {
-        mListener = l;
-        mDir = dir;
-        mIsRtl = isRtl;
-        mTouchSlop = config.getScaledTouchSlop();
-        mMaxVelocity = config.getScaledMaximumFlingVelocity();
+    public boolean isDraggingOrSettling() {
+        return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     }
 
     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
-        mScrollConditions = scrollDirectionFlags;
+        mScrollDirections = scrollDirectionFlags;
         mIgnoreSlopWhenSettling = ignoreSlop;
     }
 
     public int getScrollDirections() {
-        return mScrollConditions;
+        return mScrollDirections;
     }
 
-    private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
-        // reject cases where the angle or slop condition is not met.
-        if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
-                > Math.abs(mDisplacement)) {
-            return false;
-        }
+    public void finishedScrolling() {
+        setState(ScrollState.IDLE);
+    }
 
-        // Check if the client is interested in scroll in current direction.
-        if (((mScrollConditions & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement)) ||
-                ((mScrollConditions & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement))) {
-            return true;
-        }
-        return false;
+    /**
+     * Returns if the start drag was towards the positive direction or negative.
+     *
+     * @see #setDetectableScrollConditions(int, boolean)
+     * @see #DIRECTION_BOTH
+     */
+    public boolean wasInitialTouchPositive() {
+        return mDir.isPositive(mSubtractDisplacement);
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
@@ -338,16 +281,50 @@
         return true;
     }
 
-    public void finishedScrolling() {
-        setState(ScrollState.IDLE);
+    //------------------- ScrollState transition diagram -----------------------------------
+    //
+    // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING
+    // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING
+    // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING
+    // SETTLING -> (View settled) -> IDLE
+
+    private void setState(ScrollState newState) {
+        if (DBG) {
+            Log.d(TAG, "setState:" + mState + "->" + newState);
+        }
+        // onDragStart and onDragEnd is reported ONLY on state transition
+        if (newState == ScrollState.DRAGGING) {
+            initializeDragging();
+            if (mState == ScrollState.IDLE) {
+                reportDragStart(false /* recatch */);
+            } else if (mState == ScrollState.SETTLING) {
+                reportDragStart(true /* recatch */);
+            }
+        }
+        if (newState == ScrollState.SETTLING) {
+            reportDragEnd();
+        }
+
+        mState = newState;
     }
 
-    private boolean reportDragStart(boolean recatch) {
+    private boolean shouldScrollStart(MotionEvent ev, int pointerIndex) {
+        // reject cases where the angle or slop condition is not met.
+        if (Math.max(mDir.getActiveTouchSlop(ev, pointerIndex, mDownPos), mTouchSlop)
+                > Math.abs(mDisplacement)) {
+            return false;
+        }
+
+        // Check if the client is interested in scroll in current direction.
+        return ((mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(mDisplacement))
+                || ((mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(mDisplacement));
+    }
+
+    private void reportDragStart(boolean recatch) {
         mListener.onDragStart(!recatch);
         if (DBG) {
             Log.d(TAG, "onDragStart recatch:" + recatch);
         }
-        return true;
     }
 
     private void initializeDragging() {
@@ -361,26 +338,15 @@
         }
     }
 
-    /**
-     * Returns if the start drag was towards the positive direction or negative.
-     *
-     * @see #setDetectableScrollConditions(int, boolean)
-     * @see #DIRECTION_BOTH
-     */
-    public boolean wasInitialTouchPositive() {
-        return mDir.isPositive(mSubtractDisplacement);
-    }
-
-    private boolean reportDragging(MotionEvent event) {
+    private void reportDragging(MotionEvent event) {
         if (mDisplacement != mLastDisplacement) {
             if (DBG) {
                 Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement));
             }
 
             mLastDisplacement = mDisplacement;
-            return mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
+            mListener.onDrag(mDisplacement - mSubtractDisplacement, event);
         }
-        return true;
     }
 
     private void reportDragEnd() {
@@ -394,14 +360,33 @@
         mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS);
     }
 
-    public static long calculateDuration(float velocity, float progressNeeded) {
-        // TODO: make these values constants after tuning.
-        float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity));
-        float travelDistance = Math.max(0.2f, progressNeeded);
-        long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
-        if (DBG) {
-            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded));
+    /** Listener to receive updates on the swipe. */
+    public interface Listener {
+        void onDragStart(boolean start);
+
+        boolean onDrag(float displacement);
+
+        default boolean onDrag(float displacement, MotionEvent event) {
+            return onDrag(displacement);
         }
-        return duration;
+
+        void onDragEnd(float velocity, boolean fling);
+    }
+
+    public abstract static class Direction {
+
+        abstract float getDisplacement(MotionEvent ev, int pointerIndex, PointF refPoint,
+                boolean isRtl);
+
+        /**
+         * Distance in pixels a touch can wander before we think the user is scrolling.
+         */
+        abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos);
+
+        abstract float getVelocity(VelocityTracker tracker, boolean isRtl);
+
+        abstract boolean isPositive(float displacement);
+
+        abstract boolean isNegative(float displacement);
     }
 }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index da1df3f..9f59d78 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -288,6 +288,7 @@
             anim.addUpdateListener((v) -> invalidate(invalidateRegion));
             getOverlay().add(drawable);
             anim.start();
+            return true;
         }
         return value;
     }
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetManager.java b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
new file mode 100644
index 0000000..f20c83d
--- /dev/null
+++ b/src/com/android/launcher3/widget/custom/CustomWidgetManager.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.custom;
+
+import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Process;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.systemui.plugins.CustomWidgetPlugin;
+import com.android.systemui.plugins.PluginListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * CustomWidgetManager handles custom widgets implemented as a plugin.
+ */
+public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
+
+    public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE =
+            new MainThreadInitializedObject<>(CustomWidgetManager::new);
+
+    /**
+     * auto provider Id is an ever-increasing number that serves as the providerId whenever a new
+     * custom widget has been connected.
+     */
+    private int mAutoProviderId = 0;
+    private final SparseArray<CustomWidgetPlugin> mPlugins;
+    private final SparseArray<WeakReference<Context>> mContexts;
+    private final List<CustomAppWidgetProviderInfo> mCustomWidgets;
+    private final SparseArray<ComponentName> mWidgetsIdMap;
+    private Consumer<PackageUserKey> mWidgetRefreshCallback;
+
+    private CustomWidgetManager(Context context) {
+        mPlugins = new SparseArray<>();
+        mContexts = new SparseArray<>();
+        mCustomWidgets = new ArrayList<>();
+        mWidgetsIdMap = new SparseArray<>();
+        PluginManagerWrapper.INSTANCE.get(context)
+                .addPluginListener(this, CustomWidgetPlugin.class, true);
+    }
+
+    @Override
+    public void onPluginConnected(CustomWidgetPlugin plugin, Context context) {
+        mPlugins.put(mAutoProviderId, plugin);
+        mContexts.put(mAutoProviderId, new WeakReference<>(context));
+        List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
+                .getInstalledProvidersForProfile(Process.myUserHandle());
+        if (providers.isEmpty()) return;
+        Parcel parcel = Parcel.obtain();
+        providers.get(0).writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CustomAppWidgetProviderInfo info = newInfo(mAutoProviderId, plugin, parcel, context);
+        parcel.recycle();
+        mCustomWidgets.add(info);
+        mWidgetsIdMap.put(mAutoProviderId, info.provider);
+        mWidgetRefreshCallback.accept(null);
+        mAutoProviderId++;
+    }
+
+    @Override
+    public void onPluginDisconnected(CustomWidgetPlugin plugin) {
+        int providerId = findProviderId(plugin);
+        if (providerId == -1) return;
+        mPlugins.remove(providerId);
+        mContexts.remove(providerId);
+        mCustomWidgets.remove(getWidgetProvider(providerId));
+        mWidgetsIdMap.remove(providerId);
+    }
+
+    /**
+     * Inject a callback function to refresh the widgets.
+     */
+    public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) {
+        mWidgetRefreshCallback = cb;
+    }
+
+    /**
+     * Callback method to inform a plugin it's corresponding widget has been created.
+     */
+    public void onViewCreated(LauncherAppWidgetHostView view) {
+        CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo();
+        CustomWidgetPlugin plugin = mPlugins.get(info.providerId);
+        WeakReference<Context> context = mContexts.get(info.providerId);
+        if (plugin == null) return;
+        plugin.onViewCreated(context == null ? null : context.get(), view);
+    }
+
+    /**
+     * Returns the list of custom widgets.
+     */
+    @NonNull
+    public List<CustomAppWidgetProviderInfo> getCustomWidgets() {
+        return mCustomWidgets;
+    }
+
+    /**
+     * Returns the widget id for a specific provider.
+     */
+    public int getWidgetIdForCustomProvider(@NonNull ComponentName provider) {
+        int index = mWidgetsIdMap.indexOfValue(provider);
+        if (index >= 0) {
+            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - mWidgetsIdMap.keyAt(index);
+        } else {
+            return AppWidgetManager.INVALID_APPWIDGET_ID;
+        }
+    }
+
+    /**
+     * Returns the widget provider in respect to given widget id.
+     */
+    @Nullable
+    public LauncherAppWidgetProviderInfo getWidgetProvider(int widgetId) {
+        ComponentName cn = mWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
+        for (LauncherAppWidgetProviderInfo info : mCustomWidgets) {
+            if (info.provider.equals(cn)) return info;
+        }
+        return null;
+    }
+
+    private static CustomAppWidgetProviderInfo newInfo(int providerId, CustomWidgetPlugin plugin,
+            Parcel parcel, Context context) {
+        CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(
+                parcel, false, providerId);
+        info.provider = new ComponentName(
+                context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
+
+        info.label = plugin.getLabel(context);
+        info.resizeMode = plugin.getResizeMode(context);
+
+        info.spanX = plugin.getSpanX(context);
+        info.spanY = plugin.getSpanY(context);
+        info.minSpanX = plugin.getMinSpanX(context);
+        info.minSpanY = plugin.getMinSpanY(context);
+        return info;
+    }
+
+    private int findProviderId(CustomWidgetPlugin plugin) {
+        for (int i = 0; i < mPlugins.size(); i++) {
+            int providerId = mPlugins.keyAt(i);
+            if (mPlugins.get(providerId) == plugin) {
+                return providerId;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java b/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
deleted file mode 100644
index 00720c4..0000000
--- a/src/com/android/launcher3/widget/custom/CustomWidgetParser.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.widget.custom;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Parcel;
-import android.os.Process;
-import android.util.SparseArray;
-import android.util.Xml;
-
-import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.launcher3.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX;
-
-/**
- * Utility class to parse {@ink CustomAppWidgetProviderInfo} definitions from xml
- */
-public class CustomWidgetParser {
-
-    private static List<LauncherAppWidgetProviderInfo> sCustomWidgets;
-    private static SparseArray<ComponentName> sWidgetsIdMap;
-
-    public static List<LauncherAppWidgetProviderInfo> getCustomWidgets(Context context) {
-        if (sCustomWidgets == null) {
-            // Synchronization not needed as it it safe to load multiple times
-            parseCustomWidgets(context);
-        }
-
-        return sCustomWidgets;
-    }
-
-    public static int getWidgetIdForCustomProvider(Context context, ComponentName provider) {
-        if (sWidgetsIdMap == null) {
-            parseCustomWidgets(context);
-        }
-        int index = sWidgetsIdMap.indexOfValue(provider);
-        if (index >= 0) {
-            return LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - sWidgetsIdMap.keyAt(index);
-        } else {
-            return AppWidgetManager.INVALID_APPWIDGET_ID;
-        }
-    }
-
-    public static LauncherAppWidgetProviderInfo getWidgetProvider(Context context, int widgetId) {
-        if (sWidgetsIdMap == null || sCustomWidgets == null) {
-            parseCustomWidgets(context);
-        }
-        ComponentName cn = sWidgetsIdMap.get(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - widgetId);
-        for (LauncherAppWidgetProviderInfo info : sCustomWidgets) {
-            if (info.provider.equals(cn)) {
-                return info;
-            }
-        }
-        return null;
-    }
-
-    private static void parseCustomWidgets(Context context) {
-        ArrayList<LauncherAppWidgetProviderInfo> widgets = new ArrayList<>();
-        SparseArray<ComponentName> idMap = new SparseArray<>();
-
-        List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context)
-                .getInstalledProvidersForProfile(Process.myUserHandle());
-        if (providers.isEmpty()) {
-            sCustomWidgets = widgets;
-            sWidgetsIdMap = idMap;
-            return;
-        }
-
-        Parcel parcel = Parcel.obtain();
-        providers.get(0).writeToParcel(parcel, 0);
-
-        try (XmlResourceParser parser = context.getResources().getXml(R.xml.custom_widgets)) {
-            final int depth = parser.getDepth();
-            int type;
-
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-                if ((type == XmlPullParser.START_TAG) && "widget".equals(parser.getName())) {
-                    TypedArray a = context.obtainStyledAttributes(
-                            Xml.asAttributeSet(parser), R.styleable.CustomAppWidgetProviderInfo);
-
-                    parcel.setDataPosition(0);
-                    CustomAppWidgetProviderInfo info = newInfo(a, parcel, context);
-                    widgets.add(info);
-                    a.recycle();
-
-                    idMap.put(info.providerId, info.provider);
-                }
-            }
-        } catch (IOException | XmlPullParserException e) {
-            throw new RuntimeException(e);
-        }
-        parcel.recycle();
-        sCustomWidgets = widgets;
-        sWidgetsIdMap = idMap;
-    }
-
-    private static CustomAppWidgetProviderInfo newInfo(TypedArray a, Parcel parcel, Context context) {
-        int providerId = a.getInt(R.styleable.CustomAppWidgetProviderInfo_providerId, 0);
-        CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false, providerId);
-        info.provider = new ComponentName(context.getPackageName(), CLS_CUSTOM_WIDGET_PREFIX + providerId);
-
-        info.label = a.getString(R.styleable.CustomAppWidgetProviderInfo_android_label);
-        info.initialLayout = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_initialLayout, 0);
-        info.icon = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_icon, 0);
-        info.previewImage = a.getResourceId(R.styleable.CustomAppWidgetProviderInfo_android_previewImage, 0);
-        info.resizeMode = a.getInt(R.styleable.CustomAppWidgetProviderInfo_android_resizeMode, 0);
-
-        info.spanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numColumns, 1);
-        info.spanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numRows, 1);
-        info.minSpanX = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinColumns, 1);
-        info.minSpanY = a.getInt(R.styleable.CustomAppWidgetProviderInfo_numMinRows, 1);
-        return info;
-    }
-}
diff --git a/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
new file mode 100644
index 0000000..47aa94b
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/CustomWidgetPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a custom widget.
+ */
+@ProvidesInterface(action = CustomWidgetPlugin.ACTION, version = CustomWidgetPlugin.VERSION)
+public interface CustomWidgetPlugin extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PLUGIN_CUSTOM_WIDGET";
+    int VERSION = 1;
+
+    /**
+     * The label to display to the user in the AppWidget picker.
+     */
+    String getLabel(Context context);
+
+    /**
+     * The default width of the widget when added to a host, in dp. The widget will get
+     * at least this width, and will often be given more, depending on the host.
+     */
+    int getSpanX(Context context);
+
+    /**
+     * The default height of the widget when added to a host, in dp. The widget will get
+     * at least this height, and will often be given more, depending on the host.
+     */
+    int getSpanY(Context context);
+
+    /**
+     * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+     * is greater than minWidth or if horizontal resizing isn't enabled.
+     */
+    int getMinSpanX(Context context);
+
+    /**
+     * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+     * is greater than minHeight or if vertical resizing isn't enabled.
+     */
+    int getMinSpanY(Context context);
+
+    /**
+     * The rules by which a widget can be resized.
+     */
+    int getResizeMode(Context context);
+
+    /**
+     * Notify the plugin that container of the widget has been rendered, where the custom widget
+     * can be attached to.
+     */
+    void onViewCreated(Context context, AppWidgetHostView parent);
+}
diff --git a/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
new file mode 100644
index 0000000..8d9c0f4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OverviewScreenshotActions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this interface to add action buttons for overview screenshots, e.g. share, edit etc.
+ */
+@ProvidesInterface(
+        action = OverviewScreenshotActions.ACTION, version = OverviewScreenshotActions.VERSION)
+public interface OverviewScreenshotActions extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_OVERVIEW_SCREENSHOT_ACTIONS";
+    int VERSION = 1;
+
+    /**
+     * Setup the actions for the screenshot, including edit, save, etc.
+     * @param parent The parent view to add buttons on.
+     * @param screenshot The screenshot we will do actions on.
+     * @param activity THe host activity.
+     */
+    void setupActions(ViewGroup parent, Bitmap screenshot, Activity activity);
+}
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index 6a6916e..4246096 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -104,7 +104,8 @@
 
             case GET_SYSTEM_HEALTH_MESSAGE: {
                 final Bundle response = new Bundle();
-                response.putString("result", TestHelpers.getSystemHealthMessage(getContext()));
+                response.putString("result",
+                        TestHelpers.getSystemHealthMessage(getContext(), Long.parseLong(arg)));
                 return response;
             }
         }
diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
index e042357..f209fae 100644
--- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java
@@ -25,6 +25,10 @@
 import android.util.Log;
 import android.view.ViewConfiguration;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.launcher3.testcomponent.TouchEventGenerator;
 
 import org.junit.Before;
@@ -33,10 +37,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SwipeDetectorTest {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 17a5f50..0f50009 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -93,8 +93,7 @@
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
-    protected final LauncherInstrumentation mLauncher =
-            new LauncherInstrumentation(getInstrumentation());
+    protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
     protected Context mTargetContext;
     protected String mTargetPackage;
 
@@ -106,8 +105,9 @@
         }
         if (TestHelpers.isInLauncherProcess()) {
             Utilities.enableRunningInTestHarnessForTests();
-            mLauncher.setSystemHealthSupplier(() -> TestCommandReceiver.callCommand(
-                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE).getString("result"));
+            mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
+                    getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -172,8 +172,6 @@
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
-        // Unlock the phone
-        mDevice.executeShellCommand("input keyevent 82");
     }
 
     @After
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 3f35a3a..e1b3ede 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,7 +52,8 @@
 @RunWith(AndroidJUnit4.class)
 public class AddConfigWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     private LauncherAppWidgetProviderInfo mWidgetInfo;
     private AppWidgetManager mAppWidgetManager;
@@ -70,14 +70,12 @@
 
     @Test
     @PortraitLandscape
-    @org.junit.Ignore
     public void testWidgetConfig() throws Throwable {
         runTest(true);
     }
 
     @Test
     @PortraitLandscape
-    @org.junit.Ignore
     public void testConfigCancelled() throws Throwable {
         runTest(false);
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 1edce22..b8ca5de 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -30,7 +30,6 @@
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,11 +41,11 @@
 @RunWith(AndroidJUnit4.class)
 public class AddWidgetTest extends AbstractLauncherUiTest {
 
-    @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+    @Rule
+    public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     @Test
     @PortraitLandscape
-    @org.junit.Ignore
     public void testDragIcon() throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index 8391ae7..d7f41bf 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -37,11 +37,11 @@
     private static final String TAG = "TestStabilityRule";
     private static final Pattern LAUNCHER_BUILD =
             Pattern.compile("^("
-                    + "(?<androidStudio>BuildFromAndroidStudio)|"
-                    + "(?<commandLine>[0-9]+-eng\\.[a-z]+\\.[0-9]+\\.[0-9]+)|"
-                    + "(?<presubmit>[0-9]+-P[0-9]+)|"
-                    + "(?<postsubmit>[0-9]+-[0-9]+|"
-                    + "(?<platform>[0-9]+))"
+                    + "(?<local>(BuildFromAndroidStudio|"
+                    + "([0-9]+|[A-Z])-eng\\.[a-z]+\\.[0-9]+\\.[0-9]+))|"
+                    + "(?<presubmit>([0-9]+|[A-Z])-P[0-9]+)|"
+                    + "(?<postsubmit>([0-9]+|[A-Z])-[0-9]+)|"
+                    + "(?<platform>[0-9]+|[A-Z])"
                     + ")$");
     private static final Pattern PLATFORM_BUILD =
             Pattern.compile("^("
@@ -61,71 +61,7 @@
             return new Statement() {
                 @Override
                 public void evaluate() throws Throwable {
-                    final String launcherVersion =
-                            getInstrumentation().
-                                    getContext().
-                                    getPackageManager().
-                                    getPackageInfo(
-                                            UiDevice.getInstance(getInstrumentation()).
-                                                    getLauncherPackageName(),
-                                            0).
-                                    versionName;
-
-                    final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
-
-                    boolean launcherLocalBuild = false;
-                    boolean launcherUnbundledPresubmit = false;
-                    boolean launcherUnbundledPostsubmit = false;
-                    boolean launcherPlatform = false;
-
-                    if (!launcherBuildMatcher.find()) {
-                        Log.e(TAG, "Match not found");
-                    } else if (launcherBuildMatcher.group("androidStudio") != null
-                            || launcherBuildMatcher.group("commandLine") != null) {
-                        launcherLocalBuild = true;
-                    } else if (launcherBuildMatcher.group("presubmit") != null) {
-                        launcherUnbundledPresubmit = true;
-                    } else if (launcherBuildMatcher.group("postsubmit") != null) {
-                        launcherUnbundledPostsubmit = true;
-                    } else if (launcherBuildMatcher.group("platform") != null) {
-                        launcherPlatform = true;
-                    } else {
-                        Log.e(TAG, "ERROR1");
-                    }
-
-                    boolean platformLocalBuild = false;
-                    boolean platformPresubmit = false;
-                    boolean platformPostsubmit = false;
-
-                    final String platformVersion = Build.VERSION.INCREMENTAL;
-                    final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
-                    if (!platformBuildMatcher.find()) {
-                        Log.e(TAG, "Match not found");
-                    } else if (platformBuildMatcher.group("commandLine") != null) {
-                        platformLocalBuild = true;
-                    } else if (platformBuildMatcher.group("presubmit") != null) {
-                        platformPresubmit = true;
-                    } else if (platformBuildMatcher.group("postsubmit") != null) {
-                        platformPostsubmit = true;
-                    } else {
-                        Log.e(TAG, "ERROR2");
-                    }
-
-                    Log.d(TAG, "Launcher: " + launcherVersion + ", platform: " + platformVersion);
-
-                    if (launcherLocalBuild && (platformLocalBuild || platformPostsubmit)) {
-                        Log.d(TAG, "LOCAL RUN");
-                    } else if (launcherUnbundledPresubmit && platformPostsubmit) {
-                        Log.d(TAG, "UNBUNDLED PRESUBMIT");
-                    } else if (launcherUnbundledPostsubmit && platformPostsubmit) {
-                        Log.d(TAG, "UNBUNDLED POSTSUBMIT");
-                    } else if (launcherPlatform && platformPresubmit) {
-                        Log.d(TAG, "PLATFORM PRESUBMIT");
-                    } else if (launcherPlatform && platformPostsubmit) {
-                        Log.d(TAG, "PLATFORM POSTSUBMIT");
-                    } else {
-                        Log.e(TAG, "ERROR3");
-                    }
+                    getRunFlavor();
 
                     base.evaluate();
                 }
@@ -134,4 +70,50 @@
             return base;
         }
     }
+
+    private static void getRunFlavor() throws Exception {
+        final String launcherVersion = getInstrumentation().
+                getContext().
+                getPackageManager().
+                getPackageInfo(
+                        UiDevice.getInstance(getInstrumentation()).
+                                getLauncherPackageName(),
+                        0).
+                versionName;
+
+        final Matcher launcherBuildMatcher = LAUNCHER_BUILD.matcher(launcherVersion);
+
+        if (!launcherBuildMatcher.find()) {
+            Log.e(TAG, "Match not found");
+        }
+
+        final String platformVersion = Build.VERSION.INCREMENTAL;
+        final Matcher platformBuildMatcher = PLATFORM_BUILD.matcher(platformVersion);
+
+        if (!platformBuildMatcher.find()) {
+            Log.e(TAG, "Match not found");
+        }
+
+        Log.d(TAG, "Launcher: " + launcherVersion + ", platform: " + platformVersion);
+
+        if (launcherBuildMatcher.group("local") != null && (
+                platformBuildMatcher.group("commandLine") != null ||
+                        platformBuildMatcher.group("postsubmit") != null)) {
+            Log.d(TAG, "LOCAL RUN");
+        } else if (launcherBuildMatcher.group("presubmit") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "UNBUNDLED PRESUBMIT");
+        } else if (launcherBuildMatcher.group("postsubmit") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "UNBUNDLED POSTSUBMIT");
+        } else if (launcherBuildMatcher.group("platform") != null
+                && platformBuildMatcher.group("presubmit") != null) {
+            Log.d(TAG, "PLATFORM PRESUBMIT");
+        } else if (launcherBuildMatcher.group("platform") != null
+                && platformBuildMatcher.group("postsubmit") != null) {
+            Log.d(TAG, "PLATFORM POSTSUBMIT");
+        } else {
+            Log.e(TAG, "ERROR3");
+        }
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index f070280..6dced8c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -90,6 +90,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                     "apps_list_view");
+            final UiObject2 searchBox = getSearchBox(allAppsContainer);
             allAppsContainer.setGestureMargins(
                     0,
                     getSearchBox(allAppsContainer).getVisibleBounds().bottom + 1,
@@ -103,7 +104,11 @@
                 int scroll = getScroll(allAppsContainer);
                 try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled")) {
                     while (!hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)) {
-                        mLauncher.scroll(allAppsContainer, Direction.DOWN, 0.8f, null, 50);
+                        mLauncher.scrollToLastVisibleRow(
+                                allAppsContainer,
+                                mLauncher.getObjectsInContainer(allAppsContainer, "icon"),
+                                searchBox.getVisibleBounds().bottom -
+                                        allAppsContainer.getVisibleBounds().top);
                         final int newScroll = getScroll(allAppsContainer);
                         if (newScroll == scroll) break;
 
@@ -145,7 +150,7 @@
                         "Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
                         ++attempts <= MAX_SCROLL_ATTEMPTS);
 
-                mLauncher.scroll(allAppsContainer, Direction.UP, 1, margins, 50);
+                mLauncher.scroll(allAppsContainer, Direction.UP, margins, 50);
             }
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("scrolled up")) {
@@ -172,7 +177,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center to avoid starting at elements near the top.
             mLauncher.scroll(
-                    allAppsContainer, Direction.DOWN, 1, new Rect(0, 0, 0, mHeight / 2), 10);
+                    allAppsContainer, Direction.DOWN, new Rect(0, 0, 0, mHeight / 2), 10);
             verifyActiveContainer();
         }
     }
@@ -186,7 +191,7 @@
             final UiObject2 allAppsContainer = verifyActiveContainer();
             // Start the gesture in the center, for symmetry with forward.
             mLauncher.scroll(
-                    allAppsContainer, Direction.UP, 1, new Rect(0, mHeight / 2, 0, 0), 10);
+                    allAppsContainer, Direction.UP, new Rect(0, mHeight / 2, 0, 0), 10);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 25e6e8c..49c0c89 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -55,7 +55,7 @@
             final int leftMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.LEFT, 1, new Rect(leftMargin, 0, 0, 0), 20);
+            mLauncher.scroll(overview, Direction.LEFT, new Rect(leftMargin, 0, 0, 0), 20);
             verifyActiveContainer();
         }
     }
@@ -93,7 +93,7 @@
             final int rightMargin = mLauncher.getTestInfo(
                     TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
                     getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-            mLauncher.scroll(overview, Direction.RIGHT, 1, new Rect(0, 0, rightMargin, 0), 20);
+            mLauncher.scroll(overview, Direction.RIGHT, new Rect(0, 0, rightMargin, 0), 20);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 15615fc..52971d9 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -37,7 +37,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -52,6 +51,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Configurator;
@@ -60,6 +60,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -68,12 +69,14 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
+import java.util.function.Function;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -84,6 +87,7 @@
     private static final String TAG = "Tapl";
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
     private static final int GESTURE_STEP_MS = 16;
+    private static long START_TIME = System.currentTimeMillis();
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -134,13 +138,22 @@
     private int mExpectedRotation = Surface.ROTATION_0;
     private final Uri mTestProviderUri;
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
-    private Supplier<String> mSystemHealthSupplier;
+    private Function<Long, String> mSystemHealthSupplier;
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
      */
+    public LauncherInstrumentation() {
+        this(InstrumentationRegistry.getInstrumentation());
+    }
+
+    /**
+     * Constructs the root of TAPL hierarchy. You get all other objects from it.
+     * Deprecated: use the constructor without parameters instead.
+     */
+    @Deprecated
     public LauncherInstrumentation(Instrumentation instrumentation) {
         mInstrumentation = instrumentation;
         mDevice = UiDevice.getInstance(instrumentation);
@@ -238,10 +251,6 @@
         return null;
     }
 
-    public static boolean isAvd() {
-        return Build.MODEL.contains("Cuttlefish");
-    }
-
     static void log(String message) {
         Log.d(TAG, message);
     }
@@ -296,7 +305,7 @@
         return "Background";
     }
 
-    public void setSystemHealthSupplier(Supplier<String> supplier) {
+    public void setSystemHealthSupplier(Function<Long, String> supplier) {
         this.mSystemHealthSupplier = supplier;
     }
 
@@ -316,8 +325,8 @@
         }
 
         return mSystemHealthSupplier != null
-                ? mSystemHealthSupplier.get()
-                : TestHelpers.getSystemHealthMessage(getContext());
+                ? mSystemHealthSupplier.apply(START_TIME)
+                : TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
     }
 
     private void fail(String message) {
@@ -541,7 +550,7 @@
                         displaySize.x / 2, displaySize.y - 1,
                         displaySize.x / 2, 0,
                         ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME);
-                assertTrue("Context menu is still visible afterswiping up to home",
+                assertTrue("Context menu is still visible after swiping up to home",
                         !hasLauncherObject("deep_shortcuts_container"));
             }
             if (hasLauncherObject(WORKSPACE_RES_ID)) {
@@ -769,7 +778,36 @@
                 TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD)));
     }
 
-    void scroll(UiObject2 container, Direction direction, float percent, Rect margins, int steps) {
+    int getBottomGestureSize() {
+        return ResourceUtils.getNavbarSize(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
+    }
+
+    int getBottomGestureMargin(UiObject2 container) {
+        return container.getVisibleBounds().bottom - getRealDisplaySize().y +
+                getBottomGestureSize();
+    }
+
+    void scrollToLastVisibleRow(UiObject2 container, Collection<UiObject2> items, int topPadding) {
+        final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
+                Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
+
+        final int gestureStart = lowestItem.getVisibleBounds().top + getTouchSlop();
+        final int distance = gestureStart - container.getVisibleBounds().top - topPadding;
+        final int bottomMargin = container.getVisibleBounds().height() - distance;
+
+        scroll(
+                container,
+                Direction.DOWN,
+                new Rect(
+                        0,
+                        0,
+                        0,
+                        Math.max(bottomMargin, getBottomGestureMargin(container))),
+                150);
+    }
+
+    void scroll(UiObject2 container, Direction direction, Rect margins, int steps) {
         final Rect rect = container.getVisibleBounds();
         if (margins != null) {
             rect.left += margins.left;
@@ -787,7 +825,7 @@
             case UP: {
                 startX = endX = rect.centerX();
                 final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
+                final float halfGestureHeight = rect.height() / 2.0f;
                 startY = (int) (vertCenter - halfGestureHeight) + 1;
                 endY = (int) (vertCenter + halfGestureHeight);
             }
@@ -795,7 +833,7 @@
             case DOWN: {
                 startX = endX = rect.centerX();
                 final int vertCenter = rect.centerY();
-                final float halfGestureHeight = rect.height() * percent / 2.0f;
+                final float halfGestureHeight = rect.height() / 2.0f;
                 startY = (int) (vertCenter + halfGestureHeight) - 1;
                 endY = (int) (vertCenter - halfGestureHeight);
             }
@@ -803,7 +841,7 @@
             case LEFT: {
                 startY = endY = rect.centerY();
                 final int horizCenter = rect.centerX();
-                final float halfGestureWidth = rect.width() * percent / 2.0f;
+                final float halfGestureWidth = rect.width() / 2.0f;
                 startX = (int) (horizCenter - halfGestureWidth) + 1;
                 endX = (int) (horizCenter + halfGestureWidth);
             }
@@ -811,7 +849,7 @@
             case RIGHT: {
                 startY = endY = rect.centerY();
                 final int horizCenter = rect.centerX();
-                final float halfGestureWidth = rect.width() * percent / 2.0f;
+                final float halfGestureWidth = rect.width() / 2.0f;
                 startX = (int) (horizCenter + halfGestureWidth) - 1;
                 endX = (int) (horizCenter - halfGestureWidth);
             }
@@ -844,10 +882,6 @@
         mDevice.waitForIdle();
     }
 
-    float getDisplayDensity() {
-        return mInstrumentation.getTargetContext().getResources().getDisplayMetrics().density;
-    }
-
     int getTouchSlop() {
         return ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index a089a52..0c9fda3 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -101,11 +101,11 @@
         return ret.toString();
     }
 
-    private static String checkCrash(Context context, String label) {
+    private static String checkCrash(Context context, String label, long startTime) {
         DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
         Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
 
-        long timestamp = System.currentTimeMillis() - 5 * 60000;
+        long timestamp = startTime;
         DropBoxManager.Entry entry;
         StringBuilder errorDetails = new StringBuilder();
         while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
@@ -123,7 +123,7 @@
         return errorDetails.length() != 0 ? errorDetails.toString() : null;
     }
 
-    public static String getSystemHealthMessage(Context context) {
+    public static String getSystemHealthMessage(Context context, long startTime) {
         try {
             StringBuilder errors = new StringBuilder();
 
@@ -131,7 +131,6 @@
                     "system_app_anr",
                     "system_app_crash",
                     "system_app_native_crash",
-                    "system_app_wtf",
                     "system_server_anr",
                     "system_server_crash",
                     "system_server_native_crash",
@@ -139,7 +138,7 @@
             };
 
             for (String label : labels) {
-                final String crash = checkCrash(context, label);
+                final String crash = checkCrash(context, label, startTime);
                 if (crash != null) errors.append(crash);
             }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index 7d308af..51239c9 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static org.junit.Assert.fail;
-
 import android.graphics.Point;
 import android.graphics.Rect;
 
@@ -26,13 +24,12 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.ResourceUtils;
+import java.util.Collection;
 
 /**
  * All widgets container.
  */
 public final class Widgets extends LauncherInstrumentation.VisibleContainer {
-    private static final Rect MARGINS = new Rect(100, 100, 100, 100);
     private static final int FLING_STEPS = 10;
 
     Widgets(LauncherInstrumentation launcher) {
@@ -48,7 +45,11 @@
                 "want to fling forward in widgets")) {
             LauncherInstrumentation.log("Widgets.flingForward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
-            mLauncher.scroll(widgetsContainer, Direction.DOWN, 1f, MARGINS, FLING_STEPS);
+            mLauncher.scroll(
+                    widgetsContainer,
+                    Direction.DOWN,
+                    new Rect(0, 0, 0, mLauncher.getBottomGestureMargin(widgetsContainer)),
+                    FLING_STEPS);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
@@ -64,7 +65,11 @@
                 "want to fling backwards in widgets")) {
             LauncherInstrumentation.log("Widgets.flingBackward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
-            mLauncher.scroll(widgetsContainer, Direction.UP, 1f, MARGINS, FLING_STEPS);
+            mLauncher.scroll(
+                    widgetsContainer,
+                    Direction.UP,
+                    new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
+                    FLING_STEPS);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
             }
@@ -78,32 +83,33 @@
     }
 
     public Widget getWidget(String labelText) {
-        final int margin = ResourceUtils.getNavbarSize(
-                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1;
         final UiObject2 widgetsContainer = verifyActiveContainer();
-        widgetsContainer.setGestureMargins(0, 0, 0, margin);
-
         final Point displaySize = mLauncher.getRealDisplaySize();
+        final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
 
         int i = 0;
-        final BySelector selector = By.clazz("android.widget.TextView").text(labelText);
-
         for (; ; ) {
-            final UiObject2 label = mLauncher.tryWaitForLauncherObject(selector, 300);
-            if (label != null) {
+            final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
+                    widgetsContainer, "widgets_cell_list_container");
+            mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
+            for (UiObject2 cell : cells) {
+                final UiObject2 label = cell.findObject(labelSelector);
+                if (label == null) continue;
+
                 final UiObject2 widget = label.getParent().getParent();
                 mLauncher.assertEquals(
                         "View is not WidgetCell",
                         "com.android.launcher3.widget.WidgetCell",
                         widget.getClassName());
 
-                if (widget.getVisibleBounds().bottom <= displaySize.y - margin) {
+                if (widget.getVisibleBounds().bottom <=
+                        displaySize.y - mLauncher.getBottomGestureSize()) {
                     return new Widget(mLauncher, widget);
                 }
             }
 
-            if (++i > 40) fail("Too many attempts");
-            mLauncher.scroll(widgetsContainer, Direction.DOWN, 0.7f, MARGINS, 50);
+            mLauncher.assertTrue("Too many attempts", ++i <= 40);
+            mLauncher.scrollToLastVisibleRow(widgetsContainer, cells, 0);
         }
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 510ea14..0aa36dd 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -38,8 +38,6 @@
  * Operations on the workspace screen.
  */
 public final class Workspace extends Home {
-    private static final float FLING_SPEED =
-            LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F;
     private static final int DRAG_DURACTION = 2000;
     private static final int FLING_STEPS = 10;
     private final UiObject2 mHotseat;
@@ -142,7 +140,7 @@
     }
 
     private boolean isWorkspaceScrollable(UiObject2 workspace) {
-        return workspace.isScrollable();
+        return workspace.getChildCount() > 1;
     }
 
     @NonNull
@@ -182,7 +180,7 @@
      */
     public void flingForward() {
         final UiObject2 workspace = verifyActiveContainer();
-        mLauncher.scroll(workspace, Direction.RIGHT, 1f,
+        mLauncher.scroll(workspace, Direction.RIGHT,
                 new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0),
                 FLING_STEPS);
         verifyActiveContainer();
@@ -194,7 +192,7 @@
      */
     public void flingBackward() {
         final UiObject2 workspace = verifyActiveContainer();
-        mLauncher.scroll(workspace, Direction.LEFT, 1f,
+        mLauncher.scroll(workspace, Direction.LEFT,
                 new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0),
                 FLING_STEPS);
         verifyActiveContainer();