Merge "ShareTargets animations and styling." into sc-dev
diff --git a/Android.bp b/Android.bp
index a720658..9d675a4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -24,7 +24,6 @@
     ],
     srcs: [
         "tests/tapl/**/*.java",
-        "src/com/android/launcher3/util/SecureSettingsObserver.java",
         "src/com/android/launcher3/ResourceUtils.java",
         "src/com/android/launcher3/testing/TestProtocol.java",
     ],
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index b4c6138..fe81b4c 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -29,6 +29,8 @@
     Shortcut shortcut = 3;
     Widget widget = 4;
     FolderIcon folder_icon = 9;
+    Slice slice = 10;
+    SearchActionItem search_action_item = 11;
   }
   // When used for launch event, stores the global predictive rank
   optional int32 rank = 5;
@@ -169,6 +171,17 @@
   optional string label_info = 4;
 }
 
+// Contains Slice details for logging.
+message Slice{
+  optional string uri = 1;
+}
+
+// Represents SearchAction with in launcher
+message SearchActionItem{
+  optional string package_name = 1;
+  optional string title = 2;
+}
+
 //////////////////////////////////////////////
 // Containers
 
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 68c3851..e46eb9e 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -122,12 +122,13 @@
     <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
 
     <!-- Taskbar -->
-    <dimen name="taskbar_size">48dp</dimen>
-    <dimen name="taskbar_icon_size">32dp</dimen>
+    <dimen name="taskbar_size">60dp</dimen>
+    <dimen name="taskbar_icon_size">44dp</dimen>
     <dimen name="taskbar_icon_touch_size">48dp</dimen>
     <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
-    <dimen name="taskbar_icon_spacing">14dp</dimen>
+    <dimen name="taskbar_icon_spacing">8dp</dimen>
     <dimen name="taskbar_divider_thickness">1dp</dimen>
-    <dimen name="taskbar_divider_height">24dp</dimen>
+    <dimen name="taskbar_divider_height">32dp</dimen>
+    <dimen name="taskbar_folder_margin">16dp</dimen>
 </resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5a353f0..df089f6 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -89,6 +89,5 @@
     <!-- Icon displayed on the taskbar -->
     <style name="BaseIcon.Workspace.Taskbar" >
         <item name="iconDisplay">taskbar</item>
-        <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index edcd0a2..5b30143 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -30,7 +30,6 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
-import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -43,7 +42,7 @@
 import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.taskbar.TaskbarContainerView;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.taskbar.TaskbarStateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
@@ -207,6 +206,7 @@
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
 
         addTaskbarIfNecessary();
+        addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
     }
 
     @Override
@@ -222,10 +222,10 @@
             mTaskbarController.cleanup();
             mTaskbarController = null;
         }
-        if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) {
-            TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this)
-                    .inflate(R.layout.taskbar, null, false);
-            mTaskbarController = new TaskbarController(this, taskbarContainer);
+        if (mDeviceProfile.isTaskbarPresent) {
+            TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
+            mTaskbarController = new TaskbarController(this,
+                    taskbarActivityContext.getTaskbarContainerView());
             mTaskbarController.init();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
new file mode 100644
index 0000000..96559cb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+
+import java.util.List;
+
+public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate {
+
+    public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) {
+        super(launcher);
+        mActions.put(PIN_PREDICTION, new LauncherAction(
+                PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P));
+    }
+
+    @Override
+    protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
+        if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) {
+            out.add(new LauncherAction(PIN_PREDICTION, R.string.pin_prediction,
+                    KeyEvent.KEYCODE_P));
+        }
+        super.getSupportedActions(host, item, out);
+    }
+
+    @Override
+    protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
+        QuickstepLauncher launcher = (QuickstepLauncher) mLauncher;
+        if (action == PIN_PREDICTION) {
+            if (launcher.getHotseatPredictionController() == null) {
+                return false;
+            }
+            launcher.getHotseatPredictionController().pinPrediction(item);
+            return true;
+        }
+        return super.performAction(host, item, action, fromKeyboard);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index f53a5ef..7d494c2 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -130,7 +130,7 @@
 
         if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
             mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
-                    false);
+                    false, 0, true, true);
         }
 
         updateVisibility();
@@ -164,7 +164,7 @@
             for (int i = 0; i < childrenCount; i++) {
                 mDecorationHandler.extendBounds(getChildAt(i));
             }
-            mDecorationHandler.onDraw(canvas);
+            mDecorationHandler.onGroupDraw(canvas);
             mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
             mLauncher.getAppsView().getActiveRecyclerView().invalidateItemDecorations();
         }
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 225823e..2ea8bd2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -148,10 +148,13 @@
                         ? (FolderInfo) itemsIdMap.get(info.container) : null;
                 StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
             }
+            additionalSnapshotEvents(instanceId);
             prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
         }
     }
 
+    protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
+
     @Override
     public void validateData() {
         super.validateData();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
new file mode 100644
index 0000000..06372fe
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.taskbar;
+
+import android.content.ContextWrapper;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
+ * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
+ * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
+ */
+public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
+
+    private final DeviceProfile mDeviceProfile;
+    private final LayoutInflater mLayoutInflater;
+    private final TaskbarContainerView mTaskbarContainerView;
+
+    public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
+        super(launcher);
+        mDeviceProfile = launcher.getDeviceProfile().copy(this);
+        float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+        float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+        mDeviceProfile.updateIconSize(iconScale, getResources());
+
+        mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+
+        mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater
+                .inflate(R.layout.taskbar, null, false);
+    }
+
+    public TaskbarContainerView getTaskbarContainerView() {
+        return mTaskbarContainerView;
+    }
+
+    /**
+     * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
+     * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
+     */
+    public LayoutInflater getLayoutInflater() {
+        return mLayoutInflater;
+    }
+
+    @Override
+    public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
+        return mTaskbarContainerView;
+    }
+
+    @Override
+    public DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    @Override
+    public Rect getFolderBoundingBox() {
+        return mTaskbarContainerView.getFolderBoundingBox();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 3b361c4..ddd0d15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -19,19 +19,29 @@
 import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.shared.system.ViewTreeObserverWrapper;
 
 /**
  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
  */
-public class TaskbarContainerView extends FrameLayout {
+public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
+
+    private final int[] mTempLoc = new int[2];
+    private final int mFolderMargin;
+
+    // Initialized in TaskbarController constructor.
+    private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
 
     // Initialized in init.
     private TaskbarView mTaskbarView;
@@ -52,12 +62,23 @@
 
     public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
+        super(context, attrs, 1 /* alphaChannelCount */);
+        mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+    }
+
+    protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) {
+        mControllerCallbacks = callbacks;
     }
 
     protected void init(TaskbarView taskbarView) {
         mTaskbarView = taskbarView;
         mTaskbarInsetsComputer = createTaskbarInsetsComputer();
+        recreateControllers();
+    }
+
+    @Override
+    public void recreateControllers() {
+        mControllers = new TouchController[0];
     }
 
     private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
@@ -70,6 +91,17 @@
                  // We're visible again, accept touches anywhere in our bounds.
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
             }
+
+            // TaskbarContainerView provides insets to other apps based on contentInsets. These
+            // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+            // to show a floating view like Folder. Thus, we set the contentInsets to be where
+            // mTaskbarView is, since its position never changes and insets rather than overlays.
+            int[] loc = mTempLoc;
+            mTaskbarView.getLocationInWindow(loc);
+            insetsInfo.contentInsets.left = loc[0];
+            insetsInfo.contentInsets.top = loc[1];
+            insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
+            insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight());
         };
     }
 
@@ -91,4 +123,30 @@
 
         cleanup();
     }
+
+    @Override
+    protected boolean canFindActiveController() {
+        // Unlike super class, we want to be able to find controllers when touches occur in the
+        // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+        return true;
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        mControllerCallbacks.onViewRemoved();
+    }
+
+    /**
+     * @return Bounds (in our coordinates) where an opened Folder can display.
+     */
+    protected Rect getFolderBoundingBox() {
+        Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+        boundingBox.inset(mFolderMargin, mFolderMargin);
+        return boundingBox;
+    }
+
+    protected TaskbarActivityContext getTaskbarActivityContext() {
+        return mActivity;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 260428d..5dddaf3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -34,11 +34,15 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.ItemClickHandler;
@@ -81,11 +85,11 @@
             TaskbarContainerView taskbarContainerView) {
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
+        mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
         mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-        mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
+        mTaskbarView.construct(createTaskbarViewCallbacks());
         mWindowManager = mLauncher.getWindowManager();
-        mTaskbarSize = new Point(MATCH_PARENT,
-                mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
+        mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
         mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
                 createTaskbarVisibilityControllerCallbacks());
@@ -110,6 +114,18 @@
         };
     }
 
+    private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() {
+        return new TaskbarContainerViewCallbacks() {
+            @Override
+            public void onViewRemoved() {
+                if (mTaskbarContainerView.getChildCount() == 1) {
+                    // Only TaskbarView remains.
+                    setTaskbarWindowFullscreen(false);
+                }
+            }
+        };
+    }
+
     private TaskbarViewCallbacks createTaskbarViewCallbacks() {
         return new TaskbarViewCallbacks() {
             @Override
@@ -120,9 +136,29 @@
                         Task task = (Task) tag;
                         ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                                 ActivityOptions.makeBasic());
+                    } else if (tag instanceof FolderInfo) {
+                        FolderIcon folderIcon = (FolderIcon) view;
+                        Folder folder = folderIcon.getFolder();
+
+                        setTaskbarWindowFullscreen(true);
+
+                        mTaskbarContainerView.post(() -> {
+                            folder.animateOpen();
+
+                            folder.iterateOverItems((itemInfo, itemView) -> {
+                                itemView.setOnClickListener(getItemOnClickListener());
+                                itemView.setOnLongClickListener(getItemOnLongClickListener());
+                                // To play haptic when dragging, like other Taskbar items do.
+                                itemView.setHapticFeedbackEnabled(true);
+                                return false;
+                            });
+                        });
                     } else {
                         ItemClickHandler.INSTANCE.onClick(view);
                     }
+
+                    AbstractFloatingView.closeAllOpenViews(
+                            mTaskbarContainerView.getTaskbarActivityContext());
                 };
             }
 
@@ -196,14 +232,10 @@
     }
 
     private void removeFromWindowManager() {
-        if (mTaskbarContainerView.isAttachedToWindow()) {
-            mWindowManager.removeViewImmediate(mTaskbarContainerView);
-        }
+        mWindowManager.removeViewImmediate(mTaskbarContainerView);
     }
 
     private void addToWindowManager() {
-        removeFromWindowManager();
-
         final int gravity = Gravity.BOTTOM;
 
         mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -341,7 +373,22 @@
      * @return Whether the given View is in the same window as Taskbar.
      */
     public boolean isViewInTaskbar(View v) {
-        return mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+        return mTaskbarContainerView.isAttachedToWindow()
+                && mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+    }
+
+    /**
+     * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+     */
+    private void setTaskbarWindowFullscreen(boolean fullscreen) {
+        if (fullscreen) {
+            mWindowLayoutParams.width = MATCH_PARENT;
+            mWindowLayoutParams.height = MATCH_PARENT;
+        } else {
+            mWindowLayoutParams.width = mTaskbarSize.x;
+            mWindowLayoutParams.height = mTaskbarSize.y;
+        }
+        mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
     }
 
     /**
@@ -361,6 +408,13 @@
     }
 
     /**
+     * Contains methods that TaskbarContainerView can call to interface with TaskbarController.
+     */
+    protected interface TaskbarContainerViewCallbacks {
+        void onViewRemoved();
+    }
+
+    /**
      * Contains methods that TaskbarView can call to interface with TaskbarController.
      */
     protected interface TaskbarViewCallbacks {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d8f3bb5..7a13b89 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -17,12 +17,12 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.DragEvent;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -32,16 +32,20 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
-public class TaskbarView extends LinearLayout {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
 
     private final ColorDrawable mBackgroundDrawable;
     private final int mItemMarginLeftRight;
@@ -51,6 +55,9 @@
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
 
+    // Initialized in TaskbarController constructor.
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
     // Initialized in init().
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
@@ -58,13 +65,13 @@
     private int mRecentsStartIndex;
     private int mRecentsEndIndex;
 
-    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
-
     // Delegate touches to the closest view if within mIconTouchSize.
     private boolean mDelegateTargeted;
     private View mDelegateView;
 
     private boolean mIsDraggingItem;
+    // Only non-null when the corresponding Folder is open.
+    private @Nullable FolderIcon mLeaveBehindFolderIcon;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -90,7 +97,7 @@
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+    protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
         mControllerCallbacks = taskbarViewCallbacks;
     }
 
@@ -130,17 +137,37 @@
 
             // Replace any Hotseat views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
+            boolean isFolder = false;
+            boolean needsReinflate = false;
             if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
                 expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+            } else if (hotseatItemInfo instanceof FolderInfo) {
+                expectedLayoutResId = R.layout.folder_icon;
+                isFolder = true;
+                // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
+                // if the info changes we need to reinflate. This should only happen if a new folder
+                // is dragged to the position that another folder previously existed.
+                needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+                    || needsReinflate) {
                 removeView(hotseatView);
-                BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
-                LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+                TaskbarActivityContext activityContext =
+                        ActivityContext.lookupContext(getContext());
+                if (isFolder) {
+                    FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
+                    FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
+                            activityContext, this, folderInfo);
+                    folderIcon.setTextVisible(false);
+                    hotseatView = folderIcon;
+                } else {
+                    hotseatView = inflate(expectedLayoutResId);
+                }
+                int iconSize = activityContext.getDeviceProfile().iconSizePx;
+                LayoutParams lp = new LayoutParams(iconSize, iconSize);
                 lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
-                hotseatView = btv;
                 addView(hotseatView, hotseatIndex, lp);
             }
 
@@ -153,6 +180,11 @@
                 hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
                 hotseatView.setOnLongClickListener(
                         mControllerCallbacks.getItemOnLongClickListener());
+            } else if (isFolder) {
+                hotseatView.setVisibility(VISIBLE);
+                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                hotseatView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
             } else {
                 hotseatView.setVisibility(GONE);
                 hotseatView.setOnClickListener(null);
@@ -345,6 +377,7 @@
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_STARTED:
                 mIsDraggingItem = true;
+                AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext()));
                 return true;
             case DragEvent.ACTION_DRAG_ENDED:
                 mIsDraggingItem = false;
@@ -357,7 +390,35 @@
         return mIsDraggingItem;
     }
 
+    // FolderIconParent implemented methods.
+
+    @Override
+    public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+        mLeaveBehindFolderIcon = child;
+        invalidate();
+    }
+
+    @Override
+    public void clearFolderLeaveBehind(FolderIcon child) {
+        mLeaveBehindFolderIcon = null;
+        invalidate();
+    }
+
+    // End FolderIconParent implemented methods.
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mLeaveBehindFolderIcon != null) {
+            canvas.save();
+            canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
+            mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas);
+            canvas.restore();
+        }
+    }
+
     private View inflate(@LayoutRes int layoutResId) {
-        return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+        TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
+        return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 2d50125..98551fb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
 import static com.android.launcher3.graphics.IconShape.getShape;
 
 import android.content.Context;
@@ -29,7 +28,6 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.core.graphics.ColorUtils;
 
@@ -37,9 +35,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.graphics.IconPalette;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
@@ -53,8 +49,7 @@
 /**
  * A BubbleTextView with a ring around it's drawable
  */
-public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
-        LauncherAccessibilityDelegate.AccessibilityActionHandler {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView {
 
     private static final int RING_SHADOW_COLOR = 0x99000000;
     private static final float RING_EFFECT_RATIO = 0.095f;
@@ -148,29 +143,6 @@
     }
 
     @Override
-    public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
-        if (!mIsPinned) {
-            accessibilityNodeInfo.addAction(
-                    new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
-                            getContext().getText(R.string.pin_prediction)));
-        }
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, ItemInfo info) {
-        QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
-        if (action == PIN_PREDICTION) {
-            if (launcher == null) {
-                return false;
-            }
-            HotseatPredictionController controller = launcher.getHotseatPredictionController();
-            controller.pinPrediction(info);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public void getIconBounds(Rect outBounds) {
         super.getIconBounds(outBounds);
         if (!mIsPinned && !mIsDrawingDot) {
@@ -179,6 +151,10 @@
         }
     }
 
+    public boolean isPinned() {
+        return mIsPinned;
+    }
+
     private int getOutlineOffsetX() {
         return (getMeasuredWidth() / 2) - mNormalizedIconRadius;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d98e792..55dde45 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -43,7 +43,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepAccessibilityDelegate;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
@@ -134,6 +136,11 @@
         mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
     }
 
+    @Override
+    protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+        return new QuickstepAccessibilityDelegate(this);
+    }
+
     /**
      * Returns Prediction controller for hybrid hotseat
      */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 73f4ff2..c60e257 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -49,6 +49,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
  * Touch controller for handling various state transitions in portrait UI.
@@ -319,4 +320,44 @@
             return baseInterpolator.getInterpolation(v);
         }
     }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                InteractionJankMonitorWrapper.begin(
+                        mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                InteractionJankMonitorWrapper.cancel(
+                        InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+                break;
+        }
+        return super.onControllerInterceptTouchEvent(ev);
+
+    }
+
+    @Override
+    protected void onReinitToState(LauncherState newToState) {
+        super.onReinitToState(newToState);
+        if (newToState != ALL_APPS) {
+            InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+        }
+    }
+
+    @Override
+    protected void onReachedFinalState(LauncherState toState) {
+        super.onReinitToState(toState);
+        if (toState == ALL_APPS) {
+            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+        }
+    }
+
+    @Override
+    protected void clearState() {
+        super.clearState();
+        InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 70b4f20..1c5dc4c 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -191,6 +191,7 @@
             } else {
                 task = new Task(taskKey);
             }
+            task.setLastSnapshotData(rawTask);
             allTasks.add(task);
         }
 
@@ -200,9 +201,7 @@
     private ArrayList<Task> copyOf(ArrayList<Task> tasks) {
         ArrayList<Task> newTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
-            Task t = tasks.get(i);
-            newTasks.add(new Task(t.key, t.colorPrimary, t.colorBackground, t.isDockable,
-                    t.isLocked, t.taskDescription, t.topActivity));
+            newTasks.add(new Task(tasks.get(i)));
         }
         return newTasks;
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4301377..f99b7e6 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,14 +17,16 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
+import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
@@ -44,6 +46,7 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Region;
+import android.net.Uri;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserManager;
@@ -57,11 +60,11 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayHolder;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
@@ -177,33 +180,33 @@
             }
         }
 
+        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
         if (mIsOneHandedModeSupported) {
-            SecureSettingsObserver oneHandedEnabledObserver =
-                    SecureSettingsObserver.newOneHandedSettingsObserver(
-                            mContext, enabled -> mIsOneHandedModeEnabled = enabled);
-            oneHandedEnabledObserver.register();
-            oneHandedEnabledObserver.dispatchOnChange();
-            runOnDestroy(oneHandedEnabledObserver::unregister);
+            Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
+            SettingsCache.OnChangeListener onChangeListener =
+                    enabled -> mIsOneHandedModeEnabled = enabled;
+            settingsCache.register(oneHandedUri, onChangeListener);
+            settingsCache.dispatchOnChange(oneHandedUri);
+            runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
         } else {
             mIsOneHandedModeEnabled = false;
         }
 
-        SecureSettingsObserver swipeBottomEnabledObserver =
-                SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
-                        mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
-        swipeBottomEnabledObserver.register();
-        swipeBottomEnabledObserver.dispatchOnChange();
-        runOnDestroy(swipeBottomEnabledObserver::unregister);
 
-        SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
-                context.getContentResolver(),
-                e -> mIsUserSetupComplete = e,
-                Settings.Secure.USER_SETUP_COMPLETE,
-                0);
-        mIsUserSetupComplete = userSetupObserver.getValue();
+        Uri swipeBottomNotificationUri =
+                Settings.Secure.getUriFor(ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
+        SettingsCache.OnChangeListener onChangeListener =
+                enabled -> mIsSwipeToNotificationEnabled = enabled;
+        settingsCache.register(swipeBottomNotificationUri, onChangeListener);
+        settingsCache.dispatchOnChange(swipeBottomNotificationUri);
+        runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+
+        Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+        mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
         if (!mIsUserSetupComplete) {
-            userSetupObserver.register();
-            runOnDestroy(userSetupObserver::unregister);
+            SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
+            settingsCache.register(setupCompleteUri, userSetupChangeListener);
+            runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 3157865..f336bf5 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -28,7 +28,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED;
 import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -43,7 +43,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -77,11 +77,10 @@
         getPrefs(context).registerOnSharedPreferenceChangeListener(this);
         getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
 
-        SecureSettingsObserver dotsObserver =
-                newNotificationSettingsObserver(context, this::onNotificationDotsChanged);
-        mNotificationDotsEnabled = dotsObserver.getValue();
-        dispatchUserEvent();
-
+        SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
+        mSettingsCache.register(NOTIFICATION_BADGING_URI,
+                this::onNotificationDotsChanged);
+        mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
     }
 
     private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index f9283a4..a762cb7 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,9 @@
 
 package com.android.quickstep.logging;
 
+import static androidx.core.util.Preconditions.checkNotNull;
+import static androidx.core.util.Preconditions.checkState;
+
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
@@ -29,7 +32,9 @@
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
+import androidx.slice.SliceItem;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
@@ -140,6 +145,7 @@
         private Optional<FromState> mFromState = Optional.empty();
         private Optional<ToState> mToState = Optional.empty();
         private Optional<String> mEditText = Optional.empty();
+        private SliceItem mSliceItem;
 
         @Override
         public StatsLogger withItemInfo(ItemInfo itemInfo) {
@@ -177,10 +183,8 @@
 
         @Override
         public StatsLogger withContainerInfo(ContainerInfo containerInfo) {
-            if (mItemInfo != DEFAULT_ITEM_INFO) {
-                throw new IllegalArgumentException(
+            checkState(mItemInfo == DEFAULT_ITEM_INFO,
                         "ItemInfo and ContainerInfo are mutual exclusive; cannot log both.");
-            }
             this.mContainerInfo = Optional.of(containerInfo);
             return this;
         }
@@ -204,12 +208,34 @@
         }
 
         @Override
+        public StatsLogger withSliceItem(@NonNull SliceItem sliceItem) {
+            this.mSliceItem = checkNotNull(sliceItem, "expected valid sliceItem but received null");
+            checkState(mItemInfo == DEFAULT_ITEM_INFO,
+                    "ItemInfo and SliceItem are mutual exclusive; cannot log both.");
+            return this;
+        }
+
+        @Override
         public void log(EventEnum event) {
             if (!Utilities.ATLEAST_R) {
                 return;
             }
 
             LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+
+            if (mSliceItem != null) {
+                Executors.MODEL_EXECUTOR.execute(
+                        () -> {
+                            LauncherAtom.ItemInfo.Builder itemInfoBuilder =
+                                    LauncherAtom.ItemInfo.newBuilder().setSlice(
+                                            LauncherAtom.Slice.newBuilder().setUri(
+                                                    mSliceItem.getSlice().getUri().toString()));
+                            mContainerInfo.ifPresent(itemInfoBuilder::setContainerInfo);
+                            write(event, applyOverwrites(itemInfoBuilder.build()));
+                        });
+                return;
+            }
+
             if (mItemInfo.container < 0 || appState == null) {
                 // Write log on the model thread so that logs do not go out of order
                 // (for eg: drop comes after drag)
@@ -327,6 +353,8 @@
                 return info.getWidget().getPackageName();
             case TASK:
                 return info.getTask().getPackageName();
+            case SEARCH_ACTION_ITEM:
+                return info.getSearchActionItem().getPackageName();
             default:
                 return null;
         }
@@ -342,6 +370,10 @@
                 return info.getWidget().getComponentName();
             case TASK:
                 return info.getTask().getComponentName();
+            case SEARCH_ACTION_ITEM:
+                return info.getSearchActionItem().getTitle();
+            case SLICE:
+                return info.getSlice().getUri();
             default:
                 return null;
         }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a3ee912..215f05a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,22 +23,18 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import static com.android.launcher3.Utilities.newContentObserver;
+import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
-import android.provider.Settings;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
@@ -51,6 +47,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.WindowBounds;
@@ -72,9 +69,6 @@
     private static final String TAG = "RecentsOrientedState";
     private static final boolean DEBUG = false;
 
-    private ContentObserver mSystemAutoRotateObserver =
-            newContentObserver(new Handler(), t -> updateAutoRotateSetting());
-
     @Retention(SOURCE)
     @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
     public @interface SurfaceRotation {}
@@ -118,9 +112,11 @@
                     | FLAG_SWIPE_UP_NOT_RUNNING;
 
     private final Context mContext;
-    private final ContentResolver mContentResolver;
     private final SharedPreferences mSharedPrefs;
     private final OrientationEventListener mOrientationListener;
+    private final SettingsCache mSettingsCache;
+    private final SettingsCache.OnChangeListener mRotationChangeListener =
+            isEnabled -> updateAutoRotateSetting();
 
     private final Matrix mTmpMatrix = new Matrix();
 
@@ -138,7 +134,6 @@
     public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy,
             IntConsumer rotationChangeListener) {
         mContext = context;
-        mContentResolver = context.getContentResolver();
         mSharedPrefs = Utilities.getPrefs(context);
         mOrientationListener = new OrientationEventListener(context) {
             @Override
@@ -162,6 +157,7 @@
             mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
         }
         mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
+        mSettingsCache = SettingsCache.INSTANCE.get(mContext);
         initFlags();
     }
 
@@ -271,8 +267,8 @@
     }
 
     private void updateAutoRotateSetting() {
-        setFlag(FLAG_SYSTEM_ROTATION_ALLOWED, Settings.System.getInt(mContentResolver,
-                Settings.System.ACCELEROMETER_ROTATION, 1) == 1);
+        setFlag(FLAG_SYSTEM_ROTATION_ALLOWED,
+                mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
     }
 
     private void updateHomeRotationSetting() {
@@ -295,9 +291,7 @@
     public void initListeners() {
         if (isMultipleOrientationSupportedByDevice()) {
             mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
-            mContentResolver.registerContentObserver(
-                    Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
-                    false, mSystemAutoRotateObserver);
+            mSettingsCache.register(ROTATION_SETTING_URI, mRotationChangeListener);
         }
         initFlags();
     }
@@ -308,7 +302,7 @@
     public void destroyListeners() {
         if (isMultipleOrientationSupportedByDevice()) {
             mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
-            mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
+            mSettingsCache.unregister(ROTATION_SETTING_URI, mRotationChangeListener);
         }
         setRotationWatcherEnabled(false);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5d492ac..deb1388 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -646,9 +646,9 @@
 
     public TaskView getTaskView(int taskId) {
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView tv = getTaskViewAt(i);
-            if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) {
-                return tv;
+            TaskView taskView = getTaskViewAt(i);
+            if (taskView.hasTaskId(taskId)) {
+                return taskView;
             }
         }
         return null;
@@ -808,6 +808,7 @@
             final Task task = tasks.get(i);
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
             taskView.bind(task, mOrientationState);
+            taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId));
         }
 
         if (mNextPage == INVALID_PAGE) {
@@ -942,7 +943,8 @@
         // Force TaskView to update size from thumbnail
         final int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).updateTaskSize();
+            TaskView taskView = getTaskViewAt(i);
+            taskView.updateTaskSize(!taskView.hasTaskId(mRunningTaskId));
         }
     }
 
@@ -1252,6 +1254,7 @@
             // gesture and the task list is loaded and applied
             mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
             taskView.bind(mTmpRunningTask, mOrientationState);
+            taskView.updateTaskSize(false);
 
             // Measure and layout immediately so that the scroll values is updated instantly
             // as the user might be quick-switching
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 3bd883d..f2f4bc1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -111,9 +111,6 @@
     private boolean mOverlayEnabled;
     private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
 
-    // TODO(b/179466077): Remove when proper API is ready.
-    private Float mThumbnailRatio = null;
-
     public TaskThumbnailView(Context context) {
         this(context, null);
     }
@@ -454,31 +451,6 @@
         return mThumbnailData.isRealSnapshot;
     }
 
-    // TODO(b/179466077): Remove when proper API is ready.
-    public float getThumbnailRatio() {
-        // API is ready.
-        if (mThumbnailRatio != null) {
-            return mThumbnailRatio;
-        }
-
-        if (mThumbnailData == null || mThumbnailData.thumbnail == null) {
-            final float[] thumbnailRatios =
-                    new float[]{0.8882452f, 1.2834098f, 0.5558415f, 2.15625f};
-            // Use key's hash code to return a deterministic thumbnail ratio.
-            mThumbnailRatio = thumbnailRatios[mTask.key.hashCode() % thumbnailRatios.length];
-            return mThumbnailRatio;
-        }
-
-        float surfaceWidth = mThumbnailData.thumbnail.getWidth() / mThumbnailData.scale;
-        float surfaceHeight = mThumbnailData.thumbnail.getHeight() / mThumbnailData.scale;
-        float availableWidth = surfaceWidth
-                - (mThumbnailData.insets.left + mThumbnailData.insets.right);
-        float availableHeight = surfaceHeight
-                - (mThumbnailData.insets.top + mThumbnailData.insets.bottom);
-        mThumbnailRatio = availableWidth / availableHeight;
-        return mThumbnailRatio;
-    }
-
     /**
      * Utility class to position the thumbnail in the TaskView
      */
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a1b5533..e891c95 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -48,6 +48,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Outline;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -449,7 +450,6 @@
         cancelPendingLoadTasks();
         mTask = task;
         mSnapshotView.bind(task);
-        updateTaskSize();
         setOrientationState(orientedState);
     }
 
@@ -457,6 +457,10 @@
         return mTask;
     }
 
+    public boolean hasTaskId(int taskId) {
+        return mTask != null && mTask.key != null && mTask.key.id == taskId;
+    }
+
     public TaskThumbnailView getThumbnail() {
         return mSnapshotView;
     }
@@ -1077,9 +1081,11 @@
                 previewPositionHelper);
     }
 
-    void updateTaskSize() {
+    void updateTaskSize(boolean variableWidth) {
         ViewGroup.LayoutParams params = getLayoutParams();
-        if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+        float thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio() : 0f;
+        if (variableWidth && mActivity.getDeviceProfile().isTablet
+                && FeatureFlags.ENABLE_OVERVIEW_GRID.get() && thumbnailRatio != 0f) {
             final int thumbnailPadding = (int) getResources().getDimension(
                     R.dimen.task_thumbnail_top_margin);
 
@@ -1087,7 +1093,6 @@
             int taskWidth = lastComputedTaskSize.width();
             int taskHeight = lastComputedTaskSize.height();
             int boxLength = Math.max(taskWidth, taskHeight);
-            float thumbnailRatio = mSnapshotView.getThumbnailRatio();
 
             int expectedWidth;
             int expectedHeight;
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 0f6671d..35383d2 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -168,7 +168,7 @@
 
         Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay enable-exclusive " + overlayPackage);
+                "cmd overlay enable-exclusive --category " + overlayPackage);
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
diff --git a/res/drawable/ic_widget_height_decrease.xml b/res/drawable/ic_widget_height_decrease.xml
new file mode 100644
index 0000000..df704ba
--- /dev/null
+++ b/res/drawable/ic_widget_height_decrease.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2021 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary">
+  <path
+      android:fillColor="#FFFFFF"
+      android:pathData="M8,19h3v3h2v-3h3l-4,-4 -4,4zM16,4h-3L13,1h-2v3L8,4l4,4 4,-4zM4,9v2h16L20,9L4,9zM4,12h16v2H4z"/>
+</vector>
diff --git a/res/drawable/ic_widget_height_increase.xml b/res/drawable/ic_widget_height_increase.xml
new file mode 100644
index 0000000..c263a4b
--- /dev/null
+++ b/res/drawable/ic_widget_height_increase.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2021 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/textColorPrimary">
+  <path
+      android:fillColor="#FFFFFF"
+      android:pathData="M4,20h16v2L4,22zM4,2h16v2L4,4zM13,9h3l-4,-4 -4,4h3v6L8,15l4,4 4,-4h-3z"/>
+</vector>
diff --git a/res/drawable/ic_widget_width_decrease.xml b/res/drawable/ic_widget_width_decrease.xml
new file mode 100644
index 0000000..2a2fad7
--- /dev/null
+++ b/res/drawable/ic_widget_width_decrease.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2021 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.
+-->
+<rotate
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:drawable="@drawable/ic_widget_height_decrease"
+  android:pivotX="50%"
+  android:pivotY="50%"
+  android:fromDegrees="90"
+  android:toDegrees="90" />
diff --git a/res/drawable/ic_widget_width_increase.xml b/res/drawable/ic_widget_width_increase.xml
new file mode 100644
index 0000000..89b9f40
--- /dev/null
+++ b/res/drawable/ic_widget_width_increase.xml
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2021 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.
+-->
+<rotate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ic_widget_height_increase"
+    android:pivotX="50%"
+    android:pivotY="50%"
+    android:fromDegrees="90"
+    android:toDegrees="90" />
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 0041c9a..8ed16c7 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -40,35 +40,7 @@
 
         <include layout="@layout/floating_header_content" />
 
-        <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
-            android:id="@+id/tabs"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/all_apps_header_tab_height"
-            android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
-            android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
-            android:orientation="horizontal"
-            style="@style/TextHeadline">
-
-            <Button
-                android:id="@+id/tab_personal"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="?android:attr/selectableItemBackground"
-                android:text="@string/all_apps_personal_tab"
-                android:textColor="@color/all_apps_tab_text"
-                android:textSize="14sp" />
-
-            <Button
-                android:id="@+id/tab_work"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="?android:attr/selectableItemBackground"
-                android:text="@string/all_apps_work_tab"
-                android:textColor="@color/all_apps_tab_text"
-                android:textSize="14sp" />
-        </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+        <include layout="@layout/personal_work_tabs" />
     </com.android.launcher3.allapps.FloatingHeaderView>
 
     <include
diff --git a/res/layout/live_preview_widget_cell.xml b/res/layout/live_preview_widget_cell.xml
new file mode 100644
index 0000000..7a42d19
--- /dev/null
+++ b/res/layout/live_preview_widget_cell.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.dragndrop.LivePreviewWidgetCell
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:focusable="true"
+    android:background="?android:attr/colorPrimaryDark"
+    android:gravity="center_horizontal">
+
+    <include layout="@layout/widget_cell_content"  />
+
+</com.android.launcher3.dragndrop.LivePreviewWidgetCell>
\ No newline at end of file
diff --git a/res/layout/personal_work_tabs.xml b/res/layout/personal_work_tabs.xml
new file mode 100644
index 0000000..8f29997
--- /dev/null
+++ b/res/layout/personal_work_tabs.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+
+<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tabs"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/all_apps_header_tab_height"
+    android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+    android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+    android:orientation="horizontal"
+    style="@style/TextHeadline">
+
+    <Button
+        android:id="@+id/tab_personal"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/all_apps_personal_tab"
+        android:textColor="@color/all_apps_tab_text"
+        android:textSize="14sp" />
+
+    <Button
+        android:id="@+id/tab_work"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/all_apps_work_tab"
+        android:textColor="@color/all_apps_tab_text"
+        android:textSize="14sp" />
+</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
index fdf4446..e3c60ec 100644
--- a/res/layout/secondary_launcher.xml
+++ b/res/layout/secondary_launcher.xml
@@ -67,7 +67,7 @@
             android:paddingTop="@dimen/all_apps_header_top_padding"
             android:orientation="vertical" >
 
-            <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+            <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
                 android:id="@+id/tabs"
                 android:layout_width="match_parent"
                 android:layout_height="@dimen/all_apps_header_tab_height"
@@ -97,7 +97,7 @@
                     android:textAllCaps="true"
                     android:textColor="@color/all_apps_tab_text"
                     android:textSize="14sp" />
-            </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+            </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
         </com.android.launcher3.allapps.FloatingHeaderView>
 
         <com.android.launcher3.allapps.search.AppsSearchContainerLayout
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 923352e..c230dad 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -27,8 +27,8 @@
         android:clipToPadding="false"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingLeft="8dp"
-        android:paddingRight="8dp"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp"
         android:paddingTop="16dp"
         launcher:pageIndicator="@+id/folder_page_indicator" />
 
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 6e7cf0f..28a8c6f 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -27,12 +27,6 @@
         android:background="?android:attr/colorPrimary"
         android:elevation="4dp">
 
-        <com.android.launcher3.widget.picker.WidgetsRecyclerView
-            android:id="@+id/widgets_list_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:clipToPadding="false" />
-
         <!-- Fast scroller popup -->
         <TextView
             android:id="@+id/fast_scroller_popup"
diff --git a/res/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
new file mode 100644
index 0000000..cfbb6dd
--- /dev/null
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+    <include layout="@layout/personal_work_tabs" />
+
+    <com.android.launcher3.workprofile.PersonalWorkPagedView
+        android:id="@+id/widgets_view_pager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@+id/tabs"
+        android:clipToPadding="false"
+        android:descendantFocusability="afterDescendants"
+        launcher:pageIndicator="@+id/tabs">
+
+        <com.android.launcher3.widget.picker.WidgetsRecyclerView
+            android:id="@+id/primary_widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false" />
+
+        <com.android.launcher3.widget.picker.WidgetsRecyclerView
+            android:id="@+id/work_widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false" />
+
+    </com.android.launcher3.workprofile.PersonalWorkPagedView>
+
+</merge>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
new file mode 100644
index 0000000..fbe559c
--- /dev/null
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.widget.picker.WidgetsRecyclerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/primary_widgets_list_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false" />
\ No newline at end of file
diff --git a/res/values-sw720dp/styles.xml b/res/values-sw720dp/styles.xml
index f322e9f..c1e6eca 100644
--- a/res/values-sw720dp/styles.xml
+++ b/res/values-sw720dp/styles.xml
@@ -18,16 +18,6 @@
 -->
 
 <resources>
-
-    <style name="BaseLauncherTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:colorBackgroundCacheHint">@null</item>
-        <item name="android:windowShowWallpaper">true</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowActionModeOverlay">true</item>
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-    </style>
-
     <!-- Workspace -->
     <style name="DropTargetButton" parent="DropTargetButtonBase">
         <item name="android:paddingLeft">60dp</item>
@@ -36,5 +26,4 @@
         <item name="android:shadowDy">0.0</item>
         <item name="android:shadowRadius">2.0</item>
     </style>
-
 </resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index b8600a6..6baf39e 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -17,17 +17,17 @@
 */
 -->
 <resources>
-    <color name="popup_color_primary_light">@android:color/system_main_50</color>
-    <color name="popup_color_secondary_light">@android:color/system_main_100</color>
-    <color name="popup_color_tertiary_light">@android:color/system_main_300</color>
-    <color name="popup_color_primary_dark">@android:color/system_main_800</color>
-    <color name="popup_color_secondary_dark">@android:color/system_main_900</color>
-    <color name="popup_color_tertiary_dark">@android:color/system_main_700</color>
+    <color name="popup_color_primary_light">@android:color/system_primary_50</color>
+    <color name="popup_color_secondary_light">@android:color/system_primary_100</color>
+    <color name="popup_color_tertiary_light">@android:color/system_primary_300</color>
+    <color name="popup_color_primary_dark">@android:color/system_primary_800</color>
+    <color name="popup_color_secondary_dark">@android:color/system_primary_900</color>
+    <color name="popup_color_tertiary_dark">@android:color/system_primary_700</color>
 
-    <color name="workspace_text_color_light">@android:color/system_main_50</color>
-    <color name="workspace_text_color_dark">@android:color/system_main_900</color>
+    <color name="workspace_text_color_light">@android:color/system_primary_50</color>
+    <color name="workspace_text_color_dark">@android:color/system_primary_900</color>
 
-    <color name="text_color_primary_dark">@android:color/system_main_50</color>
-    <color name="text_color_secondary_dark">@android:color/system_main_200</color>
-    <color name="text_color_tertiary_dark">@android:color/system_main_400</color>
+    <color name="text_color_primary_dark">@android:color/system_primary_50</color>
+    <color name="text_color_secondary_dark">@android:color/system_primary_200</color>
+    <color name="text_color_tertiary_dark">@android:color/system_primary_400</color>
 </resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1ce7840..acc6466 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -149,9 +149,9 @@
     <dimen name="folder_cell_x_padding">9dp</dimen>
     <dimen name="folder_cell_y_padding">6dp</dimen>
     <dimen name="folder_child_text_size">13sp</dimen>
-    <dimen name="folder_label_padding_top">4dp</dimen>
+    <dimen name="folder_label_padding_top">12dp</dimen>
     <dimen name="folder_label_padding_bottom">12dp</dimen>
-    <dimen name="folder_label_text_size">14sp</dimen>
+    <dimen name="folder_label_text_size">16sp</dimen>
 
 <!-- Sizes for managed profile badges -->
     <dimen name="profile_badge_size">24dp</dimen>
@@ -170,7 +170,8 @@
 
 <!-- Deep shortcuts -->
     <dimen name="deep_shortcuts_elevation">9dp</dimen>
-    <dimen name="bg_popup_item_width">260dp</dimen>
+    <!-- also update deep_shortcuts_divider_width -->
+    <dimen name="bg_popup_item_width">234dp</dimen>
     <dimen name="bg_popup_item_height">56dp</dimen>
     <dimen name="bg_popup_item_condensed_height">48dp</dimen>
     <dimen name="pre_drag_view_scale">6dp</dimen>
@@ -193,7 +194,7 @@
     <!-- popup_padding_start + icon_size + 10dp -->
     <dimen name="deep_shortcuts_text_padding_start">56dp</dimen>
     <!-- popup_item_width - deep_shortcuts_text_padding_start -->
-    <dimen name="deep_shortcuts_divider_width">164dp</dimen>
+    <dimen name="deep_shortcuts_divider_width">178dp</dimen>
     <dimen name="system_shortcut_icon_size">24dp</dimen>
     <!-- popup_arrow_center_start - system_shortcut_icon_size / 2 -->
     <dimen name="system_shortcut_margin_start">16dp</dimen>
@@ -253,10 +254,15 @@
 
     <!-- Search related -->
     <dimen name="search_hero_title_size">16sp</dimen>
-    <dimen name="search_hero_subtitle_size">15sp</dimen>
+    <dimen name="search_hero_subtitle_size">14sp</dimen>
     <dimen name="search_hero_inline_button_size">12sp</dimen>
     <dimen name="search_settings_icon_size">36dp</dimen>
     <dimen name="search_settings_icon_vertical_offset">16dp</dimen>
     <dimen name="search_line_spacing">4dp</dimen>
+    <dimen name="search_decoration_corner_radius">28dp</dimen>
+    <dimen name="search_decoration_padding">1dp</dimen>
+
+<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
+    <dimen name="taskbar_size">0dp</dimen>
 
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c30019b..5a9def7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -37,8 +37,6 @@
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
     <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
     <string name="home_screen">Home</string>
-    <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
-    <string name="custom_actions">Custom actions</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
new file mode 100644
index 0000000..fbf4c63
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/SettingsCacheTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+public class SettingsCacheTest {
+
+    public static final Uri KEY_SYSTEM_URI_TEST1 = Uri.parse("content://settings/system/test1");
+    public static final Uri KEY_SYSTEM_URI_TEST2 = Uri.parse("content://settings/system/test2");;
+
+    private SettingsCache.OnChangeListener mChangeListener;
+    private SettingsCache mSettingsCache;
+
+    @Before
+    public void setup() {
+        mChangeListener = mock(SettingsCache.OnChangeListener.class);
+        Context targetContext = RuntimeEnvironment.application;
+        mSettingsCache = SettingsCache.INSTANCE.get(targetContext);
+        mSettingsCache.register(KEY_SYSTEM_URI_TEST1, mChangeListener);
+    }
+
+    @Test
+    public void listenerCalledOnChange() {
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+        verify(mChangeListener, times(1)).onSettingsChanged(true);
+    }
+
+    @Test
+    public void getValueRespectsDefaultValue() {
+        // Case of key not found
+        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+        assertFalse(val);
+    }
+
+    @Test
+    public void getValueHitsCache() {
+        mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+        assertTrue(val);
+    }
+
+    @Test
+    public void getValueUpdatedCache() {
+        // First ensure there's nothing in cache
+        boolean val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+        assertFalse(val);
+
+        mSettingsCache.setKeyCache(Collections.singletonMap(KEY_SYSTEM_URI_TEST1, true));
+        val = mSettingsCache.getValue(KEY_SYSTEM_URI_TEST1, 0);
+        assertTrue(val);
+    }
+
+    @Test
+    public void multipleListenersSingleKey() {
+        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+        mSettingsCache.register(KEY_SYSTEM_URI_TEST1, secondListener);
+
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+        verify(mChangeListener, times(1)).onSettingsChanged(true);
+        verify(secondListener, times(1)).onSettingsChanged(true);
+    }
+
+    @Test
+    public void singleListenerMultipleKeys() {
+        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+        mSettingsCache.register(KEY_SYSTEM_URI_TEST2, secondListener);
+
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+        verify(mChangeListener, times(1)).onSettingsChanged(true);
+        verify(secondListener, times(1)).onSettingsChanged(true);
+    }
+
+    @Test
+    public void sameListenerMultipleKeys() {
+        SettingsCache.OnChangeListener secondListener = mock(SettingsCache.OnChangeListener.class);
+        mSettingsCache.register(KEY_SYSTEM_URI_TEST2, mChangeListener);
+
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST1);
+        mSettingsCache.onChange(true, KEY_SYSTEM_URI_TEST2);
+        verify(mChangeListener, times(2)).onSettingsChanged(true);
+        verify(secondListener, times(0)).onSettingsChanged(true);
+    }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 6037c96..95cdbdd 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -59,7 +59,7 @@
             TYPE_SNACKBAR,
             TYPE_LISTENER,
             TYPE_ALL_APPS_EDU,
-
+            TYPE_DRAG_DROP_POPUP,
             TYPE_TASK_MENU,
             TYPE_OPTIONS_POPUP,
             TYPE_ICON_SURFACE
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 8a03fac..062ab71 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -31,7 +31,6 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 
 import androidx.annotation.IntDef;
 
@@ -333,7 +332,7 @@
     public static <T extends BaseActivity> T fromContext(Context context) {
         if (context instanceof BaseActivity) {
             return (T) context;
-        } else if (context instanceof ContextThemeWrapper) {
+        } else if (context instanceof ContextWrapper) {
             return fromContext(((ContextWrapper) context).getBaseContext());
         } else {
             throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index edc7e9b..ee4d7ec 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -53,7 +53,6 @@
 
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.allapps.AllAppsSectionDecorator;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DraggableView;
@@ -84,7 +83,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
+        IconLabelDotView, DraggableView, Reorderable {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -505,7 +504,7 @@
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
-        if (mDisplay == DISPLAY_TASKBAR) {
+        if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) {
             // TODO: support notification dots in Taskbar
             return;
         }
@@ -954,16 +953,4 @@
             setCompoundDrawables(null, newIcon, null, null);
         }
     }
-
-    @Override
-    public void decorate(int color) {
-        mHighlightColor = color;
-        invalidate();
-    }
-
-    @Override
-    public void removeDecoration() {
-        mHighlightColor = Color.TRANSPARENT;
-        invalidate();
-    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 29812fd..947388b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -19,6 +19,7 @@
 import static android.animation.ValueAnimator.areAnimatorsEnabled;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -309,6 +310,8 @@
         setImportantForAccessibility(accessibilityFlag);
         getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
 
+        // ExploreByTouchHelper sets focusability. Clear it when the delegate is cleared.
+        setFocusable(delegate != null);
         // Invalidate the accessibility hierarchy
         if (getParent() != null) {
             getParent().notifySubtreeAccessibilityStateChanged(
@@ -597,7 +600,9 @@
         if (child instanceof BubbleTextView) {
             BubbleTextView bubbleChild = (BubbleTextView) child;
             bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
-            bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
+            if (ENABLE_FOUR_COLUMNS.get()) {
+                bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
+            }
         }
 
         child.setScaleX(mChildScale);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 19915b7..634093c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,9 +25,12 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.Surface;
+import android.view.WindowInsets;
+import android.view.WindowManager;
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
@@ -149,6 +152,10 @@
     public DotRenderer mDotRendererWorkSpace;
     public DotRenderer mDotRendererAllApps;
 
+     // Taskbar
+    public boolean isTaskbarPresent;
+    public int taskbarSize;
+
     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
             Point minSize, Point maxSize, int width, int height, boolean isLandscape,
             boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
@@ -163,12 +170,13 @@
         // Determine sizes.
         widthPx = width;
         heightPx = height;
+        int nonFinalAvailableHeightPx;
         if (isLandscape) {
             availableWidthPx = maxSize.x;
-            availableHeightPx = minSize.y;
+            nonFinalAvailableHeightPx = minSize.y;
         } else {
             availableWidthPx = minSize.x;
-            availableHeightPx = maxSize.y;
+            nonFinalAvailableHeightPx = maxSize.y;
         }
 
         mInfo = info;
@@ -192,6 +200,22 @@
                 : Configuration.ORIENTATION_PORTRAIT);
         final Resources res = context.getResources();
 
+        isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get();
+        if (isTaskbarPresent) {
+            // Taskbar will be added later, but provides bottom insets that we should subtract
+            // from availableHeightPx.
+            taskbarSize = res.getDimensionPixelSize(R.dimen.taskbar_size);
+            WindowInsets windowInsets = DisplayController.INSTANCE.get(context).getHolder(mInfo.id)
+                    .getDisplayContext().getSystemService(WindowManager.class)
+                    .getCurrentWindowMetrics().getWindowInsets();
+            int nonOverlappingTaskbarInset =
+                    taskbarSize - windowInsets.getSystemWindowInsetBottom();
+            if (nonOverlappingTaskbarInset > 0) {
+                nonFinalAvailableHeightPx -= nonOverlappingTaskbarInset;
+            }
+        }
+        availableHeightPx = nonFinalAvailableHeightPx;
+
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
 
@@ -375,7 +399,7 @@
      * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
      */
-    private void updateIconSize(float scale, Resources res) {
+    public void updateIconSize(float scale, Resources res) {
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
         float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
@@ -648,8 +672,13 @@
     public boolean updateIsSeascape(Context context) {
         if (isVerticalBarLayout()) {
             // Check an up-to-date info.
-            boolean isSeascape = DisplayController.getDefaultDisplay(context)
-                    .createInfoForContext(context).rotation == Surface.ROTATION_270;
+            DisplayController.Info displayInfo = DisplayController.getDefaultDisplay(context)
+                    .createInfoForContext(context);
+            if (displayInfo == null) {
+                return false;
+            }
+
+            boolean isSeascape = displayInfo.rotation == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
                 return true;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 51e7c7d..49adf1f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -37,6 +37,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -74,6 +75,7 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -106,6 +108,7 @@
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -122,7 +125,6 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.FileLog;
@@ -167,7 +169,6 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.PendingRequestArgs;
 import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
@@ -394,7 +395,7 @@
         idp.addOnChangeListener(this);
         mSharedPrefs = Utilities.getPrefs(this);
         mIconCache = app.getIconCache();
-        mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
+        mAccessibilityDelegate = createAccessibilityDelegate();
 
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
@@ -1797,6 +1798,43 @@
         return newFolder;
     }
 
+    @Override
+    public Rect getFolderBoundingBox() {
+        // We need to bound the folder to the currently visible workspace area
+        Rect folderBoundingBox = new Rect();
+        getWorkspace().getPageAreaRelativeToDragLayer(folderBoundingBox);
+        return folderBoundingBox;
+    }
+
+    @Override
+    public void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+        int left = inOutPosition[0];
+        int top = inOutPosition[1];
+        DeviceProfile grid = getDeviceProfile();
+        int distFromEdgeOfScreen = getWorkspace().getPaddingLeft();
+        if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
+            // Center the folder if it is very close to being centered anyway, by virtue of
+            // filling the majority of the viewport. ie. remove it from the uncanny valley
+            // of centeredness.
+            left = (grid.availableWidthPx - width) / 2;
+        } else if (width >= bounds.width()) {
+            // If the folder doesn't fit within the bounds, center it about the desired bounds
+            left = bounds.left + (bounds.width() - width) / 2;
+        }
+        if (height >= bounds.height()) {
+            // Folder height is greater than page height, center on page
+            top = bounds.top + (bounds.height() - height) / 2;
+        } else {
+            // Folder height is less than page height, so bound it to the absolute open folder
+            // bounds if necessary
+            Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
+            left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
+            top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
+        }
+        inOutPosition[0] = left;
+        inOutPosition[1] = top;
+    }
+
     /**
      * Unbinds the view for the specified item, and removes the item and all its children.
      *
@@ -2656,19 +2694,9 @@
             shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
                     KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
         }
-        final View currentFocus = getCurrentFocus();
-        if (currentFocus != null) {
-            if (new CustomActionsPopup(this, currentFocus).canShow()) {
-                shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.custom_actions),
-                        KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON));
-            }
-            if (currentFocus.getTag() instanceof ItemInfo
-                    && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) {
+        getSupportedActions(this,  getCurrentFocus()).forEach(la ->
                 shortcutInfos.add(new KeyboardShortcutInfo(
-                        getString(R.string.shortcuts_menu_with_notifications_description),
-                        KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON));
-            }
-        }
+                        la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
         if (!shortcutInfos.isEmpty()) {
             data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
         }
@@ -2686,30 +2714,18 @@
                         return true;
                     }
                     break;
-                case KeyEvent.KEYCODE_S: {
-                    View focusedView = getCurrentFocus();
-                    if (focusedView instanceof BubbleTextView
-                            && focusedView.getTag() instanceof ItemInfo
-                            && mAccessibilityDelegate.performAction(focusedView,
-                            (ItemInfo) focusedView.getTag(),
-                            LauncherAccessibilityDelegate.DEEP_SHORTCUTS,
-                            true)) {
-                        PopupContainerWithArrow.getOpen(this).requestFocus();
-                        return true;
-                    }
-                    break;
-                }
-                case KeyEvent.KEYCODE_O:
-                    if (new CustomActionsPopup(this, getCurrentFocus()).show()) {
-                        return true;
-                    }
-                    break;
                 case KeyEvent.KEYCODE_W:
                     if (isInState(NORMAL)) {
                         OptionsPopupView.openWidgets(this);
                         return true;
                     }
                     break;
+                default:
+                    for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
+                        if (la.keyCode == keyCode) {
+                            return la.invokeFromKeyboard(getCurrentFocus());
+                        }
+                    }
             }
         }
         return super.onKeyShortcut(keyCode, event);
@@ -2767,6 +2783,9 @@
         return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
+    protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
+        return new LauncherAccessibilityDelegate(this);
+    }
 
     /**
      * @see LauncherState#getOverviewScaleAndOffset(Launcher)
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a4181c5..57d7600 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,8 +17,8 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,10 +37,10 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.InstallSessionTracker;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
@@ -57,8 +57,9 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
+    private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
 
-    private SecureSettingsObserver mNotificationDotsObserver;
+    private SettingsCache mSettingsCache;
     private InstallSessionTracker mInstallSessionTracker;
     private SimpleBroadcastReceiver mModelChangeReceiver;
     private SafeCloseable mCalendarChangeTracker;
@@ -108,10 +109,11 @@
                 .registerInstallTracker(mModel);
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
-        mNotificationDotsObserver =
-                newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
-        mNotificationDotsObserver.register();
-        mNotificationDotsObserver.dispatchOnChange();
+        mSettingsCache = SettingsCache.INSTANCE.get(mContext);
+        mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+        mSettingsCache.register(NOTIFICATION_BADGING_URI,
+                mNotificationSettingsChangedListener);
+        mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
     }
 
     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -166,8 +168,9 @@
         }
         CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
 
-        if (mNotificationDotsObserver != null) {
-            mNotificationDotsObserver.unregister();
+        if (mSettingsCache != null) {
+            mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+                    mNotificationSettingsChangedListener);
         }
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index ada297f..af2d94a 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -96,6 +96,8 @@
     private static final int MIN_FLING_VELOCITY = 250;
 
     private boolean mFreeScroll = false;
+    /** If {@code false}, disable swipe gesture to switch between pages. */
+    private boolean mSwipeGestureEnabled = true;
 
     protected final int mFlingThresholdVelocity;
     protected final int mEasyFlingThresholdVelocity;
@@ -858,6 +860,14 @@
     }
 
     /**
+     * If {@code enableSwipeGesture} is {@code true}, enables swipe gesture to navigate between
+     * pages. Otherwise, disables the navigation gesture.
+     */
+    public void setSwipeGestureEnabled(boolean swipeGestureEnabled) {
+        mSwipeGestureEnabled = swipeGestureEnabled;
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -879,6 +889,8 @@
          * scrolling there.
          */
 
+        if (!mSwipeGestureEnabled) return false;
+
         // Skip touch handling if there are no pages to swipe
         if (getChildCount() <= 0) return false;
 
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 92b88e6..7276887 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -37,6 +37,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.ItemInfo;
@@ -203,9 +205,13 @@
         d.dragSource = new DeferredOnComplete(d.dragSource, getContext());
 
         super.onDrop(d, options);
-        StatsLogger logger = mStatsLogManager.logger().withInstanceId(d.logInstanceId);
-        if (d.originalDragInfo != null) {
-            logger.withItemInfo(d.originalDragInfo);
+        doLog(d.logInstanceId, d.originalDragInfo);
+    }
+
+    private void doLog(InstanceId logInstanceId, ItemInfo itemInfo) {
+        StatsLogger logger = mStatsLogManager.logger().withInstanceId(logInstanceId);
+        if (itemInfo != null) {
+            logger.withItemInfo(itemInfo);
         }
         if (mCurrentAccessibilityAction == UNINSTALL) {
             logger.log(LAUNCHER_ITEM_DROPPED_ON_UNINSTALL);
@@ -296,6 +302,7 @@
 
     @Override
     public void onAccessibilityDrop(View view, ItemInfo item) {
+        doLog(new InstanceIdSequence().newInstanceId(), item);
         performDropAction(view, item);
     }
 
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 1c5081c..eab8272 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -26,10 +26,11 @@
 import android.view.ViewGroup;
 
 import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
-public class ShortcutAndWidgetContainer extends ViewGroup {
+public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
     static final String TAG = "ShortcutAndWidgetContainer";
 
     // These are temporary variables to prevent having to allocate a new object just to
@@ -228,4 +229,24 @@
             child.cancelLongPress();
         }
     }
+
+    @Override
+    public void drawFolderLeaveBehindForIcon(FolderIcon child) {
+        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+        // While the folder is open, the position of the icon cannot change.
+        lp.canReorder = false;
+        if (mContainerType == CellLayout.HOTSEAT) {
+            CellLayout cl = (CellLayout) getParent();
+            cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+        }
+    }
+
+    @Override
+    public void clearFolderLeaveBehind(FolderIcon child) {
+        ((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
+        if (mContainerType == CellLayout.HOTSEAT) {
+            CellLayout cl = (CellLayout) getParent();
+            cl.clearFolderLeaveBehind();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 51d8e66..c440303 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -605,7 +605,6 @@
                 outObj[0] = activityInfo;
                 return activityInfo.getFullResIcon(appState.getIconCache());
             }
-            if (info.getIntent() == null || info.getIntent().getPackage() == null) return null;
             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
                     .buildRequest(launcher)
                     .query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6fac79a..cd4616a 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -3,17 +3,18 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 
-import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
-import android.content.DialogInterface;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -25,7 +26,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
@@ -33,7 +33,6 @@
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
-import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.KeyboardDragAndDropView;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
@@ -41,14 +40,19 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.views.OptionsPopupView.OptionItem;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
 
@@ -78,89 +82,105 @@
         public View item;
     }
 
-    protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
-    @Thunk final Launcher mLauncher;
+    protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
+    protected final Launcher mLauncher;
 
     private DragInfo mDragInfo = null;
 
     public LauncherAccessibilityDelegate(Launcher launcher) {
         mLauncher = launcher;
 
-        mActions.put(REMOVE, new AccessibilityAction(REMOVE,
-                launcher.getText(R.string.remove_drop_target_label)));
-        mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
-                launcher.getText(R.string.uninstall_drop_target_label)));
-        mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
-                launcher.getText(R.string.dismiss_prediction_label)));
-        mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
-                launcher.getText(R.string.gadget_setup_text)));
-        mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
-                launcher.getText(R.string.action_add_to_workspace)));
-        mActions.put(MOVE, new AccessibilityAction(MOVE,
-                launcher.getText(R.string.action_move)));
-        mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
-                launcher.getText(R.string.action_move_to_workspace)));
-        mActions.put(RESIZE, new AccessibilityAction(RESIZE,
-                        launcher.getText(R.string.action_resize)));
-        mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS,
-                launcher.getText(R.string.action_deep_shortcut)));
-        mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new AccessibilityAction(DEEP_SHORTCUTS,
-                launcher.getText(R.string.shortcuts_menu_with_notifications_description)));
+        mActions.put(REMOVE, new LauncherAction(
+                REMOVE, R.string.remove_drop_target_label, KeyEvent.KEYCODE_X));
+        mActions.put(UNINSTALL, new LauncherAction(
+                UNINSTALL, R.string.uninstall_drop_target_label, KeyEvent.KEYCODE_U));
+        mActions.put(DISMISS_PREDICTION, new LauncherAction(DISMISS_PREDICTION,
+                R.string.dismiss_prediction_label, KeyEvent.KEYCODE_X));
+        mActions.put(RECONFIGURE, new LauncherAction(
+                RECONFIGURE, R.string.gadget_setup_text, KeyEvent.KEYCODE_E));
+        mActions.put(ADD_TO_WORKSPACE, new LauncherAction(
+                ADD_TO_WORKSPACE, R.string.action_add_to_workspace, KeyEvent.KEYCODE_P));
+        mActions.put(MOVE, new LauncherAction(
+                MOVE, R.string.action_move, KeyEvent.KEYCODE_M));
+        mActions.put(MOVE_TO_WORKSPACE, new LauncherAction(MOVE_TO_WORKSPACE,
+                R.string.action_move_to_workspace, KeyEvent.KEYCODE_P));
+        mActions.put(RESIZE, new LauncherAction(
+                RESIZE, R.string.action_resize, KeyEvent.KEYCODE_R));
+        mActions.put(DEEP_SHORTCUTS, new LauncherAction(DEEP_SHORTCUTS,
+                R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
+        mActions.put(SHORTCUTS_AND_NOTIFICATIONS, new LauncherAction(DEEP_SHORTCUTS,
+                R.string.shortcuts_menu_with_notifications_description, KeyEvent.KEYCODE_S));
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(host, info);
-        addSupportedActions(host, info, false);
+        if (host.getTag() instanceof ItemInfo) {
+            ItemInfo item = (ItemInfo) host.getTag();
+
+            List<LauncherAction> actions = new ArrayList<>();
+            getSupportedActions(host, item, actions);
+            actions.forEach(la -> info.addAction(la.accessibilityAction));
+
+            if (!itemSupportsLongClick(host, item)) {
+                info.setLongClickable(false);
+                info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
+            }
+        }
     }
 
-    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
-        if (!(host.getTag() instanceof ItemInfo)) return;
-        ItemInfo item = (ItemInfo) host.getTag();
-
-        if (host instanceof AccessibilityActionHandler) {
-            ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info);
-        }
-
+    /**
+     * Adds all the accessibility actions that can be handled.
+     */
+    protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
-        if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
-            info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null
+        if (ShortcutUtil.supportsShortcuts(item)) {
+            out.add(mActions.get(NotificationListener.getInstanceIfConnected() != null
                     ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS));
         }
 
         for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
             if (target.supportsAccessibilityDrop(item, host)) {
-                info.addAction(mActions.get(target.getAccessibilityAction()));
+                out.add(mActions.get(target.getAccessibilityAction()));
             }
         }
 
         // Do not add move actions for keyboard request as this uses virtual nodes.
         if (itemSupportsAccessibleDrag(item)) {
-            info.addAction(mActions.get(MOVE));
+            out.add(mActions.get(MOVE));
 
             if (item.container >= 0) {
-                info.addAction(mActions.get(MOVE_TO_WORKSPACE));
+                out.add(mActions.get(MOVE_TO_WORKSPACE));
             } else if (item instanceof LauncherAppWidgetInfo) {
                 if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
-                    info.addAction(mActions.get(RESIZE));
+                    out.add(mActions.get(RESIZE));
                 }
             }
         }
 
-        if (!fromKeyboard && !itemSupportsLongClick(host, item)) {
-            info.setLongClickable(false);
-            info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
-        }
-
         if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
-            info.addAction(mActions.get(ADD_TO_WORKSPACE));
+            out.add(mActions.get(ADD_TO_WORKSPACE));
         }
     }
 
+    /**
+     * Returns all the accessibility actions that can be handled by the host.
+     */
+    public static List<LauncherAction> getSupportedActions(Launcher launcher, View host) {
+        if (host == null || !(host.getTag() instanceof  ItemInfo)) {
+            return Collections.emptyList();
+        }
+        PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
+        LauncherAccessibilityDelegate delegate = container != null
+                ? container.getAccessibilityDelegate() : launcher.getAccessibilityDelegate();
+        List<LauncherAction> result = new ArrayList<>();
+        delegate.getSupportedActions(host, (ItemInfo) host.getTag(), result);
+        return result;
+    }
+
     private boolean itemSupportsLongClick(View host, ItemInfo info) {
-        return PopupContainerWithArrow.canShow(host, info)
-                || new CustomActionsPopup(mLauncher, host).canShow();
+        return PopupContainerWithArrow.canShow(host, info);
     }
 
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
@@ -184,7 +204,7 @@
     /**
      * Performs the provided action on the host
      */
-    public boolean performAction(final View host, final ItemInfo item, int action,
+    protected boolean performAction(final View host, final ItemInfo item, int action,
             boolean fromKeyboard) {
         if (action == ACTION_LONG_CLICK) {
             if (PopupContainerWithArrow.canShow(host, item)) {
@@ -193,19 +213,8 @@
                 // standard long press path does.
                 PopupContainerWithArrow.showForIcon((BubbleTextView) host);
                 return true;
-            } else {
-                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
-                if (popup.canShow()) {
-                    popup.show();
-                    return true;
-                }
             }
-        }
-        if (host instanceof AccessibilityActionHandler
-                && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) {
-            return true;
-        }
-        if (action == MOVE) {
+        } else if (action == MOVE) {
             return beginAccessibleDrag(host, item, fromKeyboard);
         } else if (action == ADD_TO_WORKSPACE) {
             final int[] coordinates = new int[2];
@@ -220,9 +229,7 @@
                                 Favorites.CONTAINER_DESKTOP,
                                 screenId, coordinates[0], coordinates[1]);
 
-                        ArrayList<ItemInfo> itemList = new ArrayList<>();
-                        itemList.add(info);
-                        mLauncher.bindItems(itemList, true);
+                        mLauncher.bindItems(Collections.singletonList(info), true);
                         announceConfirmation(R.string.item_added_to_workspace);
                     } else if (item instanceof PendingAddItemInfo) {
                         PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -243,47 +250,31 @@
             final int[] coordinates = new int[2];
             final int screenId = findSpaceOnWorkspace(item, coordinates);
             mLauncher.getModelWriter().moveItemInDatabase(info,
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                    Favorites.CONTAINER_DESKTOP,
                     screenId, coordinates[0], coordinates[1]);
 
             // Bind the item in next frame so that if a new workspace page was created,
             // it will get laid out.
-            new Handler().post(new Runnable() {
-
-                @Override
-                public void run() {
-                    ArrayList<ItemInfo> itemList = new ArrayList<>();
-                    itemList.add(item);
-                    mLauncher.bindItems(itemList, true);
-                    announceConfirmation(R.string.item_moved);
-                }
+            new Handler().post(() -> {
+                mLauncher.bindItems(Collections.singletonList(item), true);
+                announceConfirmation(R.string.item_moved);
             });
+            return true;
         } else if (action == RESIZE) {
             final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
-            final IntArray actions = getSupportedResizeActions(host, info);
-            CharSequence[] labels = new CharSequence[actions.size()];
-            for (int i = 0; i < actions.size(); i++) {
-                labels[i] = mLauncher.getText(actions.get(i));
-            }
-
-            new AlertDialog.Builder(mLauncher)
-                .setTitle(R.string.action_resize)
-                .setItems(labels, new DialogInterface.OnClickListener() {
-
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        performResizeAction(actions.get(which), host, info);
-                        dialog.dismiss();
-                    }
-                })
-                .show();
+            List<OptionItem> actions = getSupportedResizeActions(host, info);
+            Rect pos = new Rect();
+            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(host, pos);
+            ArrowPopup popup = OptionsPopupView.show(mLauncher, new RectF(pos), actions);
+            popup.requestFocus();
+            popup.setOnCloseCallback(host::requestFocus);
             return true;
-        } else if (action == DEEP_SHORTCUTS) {
+        } else if (action == DEEP_SHORTCUTS || action == SHORTCUTS_AND_NOTIFICATIONS) {
             return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
         } else {
             for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
-                if (dropTarget.supportsAccessibilityDrop(item, host) &&
-                        action == dropTarget.getAccessibilityAction()) {
+                if (dropTarget.supportsAccessibilityDrop(item, host)
+                        && action == dropTarget.getAccessibilityAction()) {
                     dropTarget.onAccessibilityDrop(host, item);
                     return true;
                 }
@@ -292,9 +283,8 @@
         return false;
     }
 
-    private IntArray getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
-        IntArray actions = new IntArray();
-
+    private List<OptionItem> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
+        List<OptionItem> actions = new ArrayList<>();
         AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
         if (providerInfo == null) {
             return actions;
@@ -304,28 +294,40 @@
         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
             if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
                     layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
-                actions.add(R.string.action_increase_width);
+                actions.add(new OptionItem(
+                        R.string.action_increase_width, R.drawable.ic_widget_width_increase,
+                        IGNORE,
+                        v -> performResizeAction(R.string.action_increase_width, host, info)));
             }
 
             if (info.spanX > info.minSpanX && info.spanX > 1) {
-                actions.add(R.string.action_decrease_width);
+                actions.add(new OptionItem(
+                        R.string.action_decrease_width, R.drawable.ic_widget_width_decrease,
+                        IGNORE,
+                        v -> performResizeAction(R.string.action_decrease_width, host, info)));
             }
         }
 
         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
             if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
                     layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
-                actions.add(R.string.action_increase_height);
+                actions.add(new OptionItem(
+                        R.string.action_increase_height, R.drawable.ic_widget_height_increase,
+                        IGNORE,
+                        v -> performResizeAction(R.string.action_increase_height, host, info)));
             }
 
             if (info.spanY > info.minSpanY && info.spanY > 1) {
-                actions.add(R.string.action_decrease_height);
+                actions.add(new OptionItem(
+                        R.string.action_decrease_height, R.drawable.ic_widget_height_decrease,
+                        IGNORE,
+                        v -> performResizeAction(R.string.action_decrease_height, host, info)));
             }
         }
         return actions;
     }
 
-    @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
+    private boolean performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
         CellLayout layout = (CellLayout) host.getParent().getParent();
         layout.markCellsAsUnoccupiedForView(host);
@@ -362,6 +364,7 @@
         host.requestLayout();
         mLauncher.getModelWriter().updateItemInDatabase(info);
         announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+        return true;
     }
 
     @Thunk void announceConfirmation(int resId) {
@@ -489,19 +492,28 @@
         return screenId;
     }
 
-    /**
-     * An interface allowing views to handle their own action.
-     */
-    public interface AccessibilityActionHandler {
+    public class LauncherAction {
+        public final int keyCode;
+        public final AccessibilityAction accessibilityAction;
+
+        private final LauncherAccessibilityDelegate mDelegate;
+
+        public LauncherAction(int id, int labelRes, int keyCode) {
+            this.keyCode = keyCode;
+            accessibilityAction = new AccessibilityAction(id, mLauncher.getString(labelRes));
+            mDelegate = LauncherAccessibilityDelegate.this;
+        }
 
         /**
-         * performs accessibility action and returns true on success
+         * Invokes the action for the provided host
          */
-        boolean performAccessibilityAction(int action, ItemInfo itemInfo);
-
-        /**
-         * adds all the accessibility actions that can be handled.
-         */
-        void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo);
+        public boolean invokeFromKeyboard(View host) {
+            if (host != null && host.getTag() instanceof ItemInfo) {
+                return mDelegate.performAction(
+                        host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
+            } else {
+                return false;
+            }
+        }
     }
 }
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index aaaff98..1733e5d 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -18,9 +18,8 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
+import android.view.KeyEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
@@ -31,7 +30,8 @@
 import com.android.launcher3.notification.NotificationMainView;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Extension of {@link LauncherAccessibilityDelegate} with actions specific to shortcuts in
@@ -43,23 +43,23 @@
 
     public ShortcutMenuAccessibilityDelegate(Launcher launcher) {
         super(launcher);
-        mActions.put(DISMISS_NOTIFICATION, new AccessibilityAction(DISMISS_NOTIFICATION,
-                launcher.getText(R.string.action_dismiss_notification)));
+        mActions.put(DISMISS_NOTIFICATION, new LauncherAction(DISMISS_NOTIFICATION,
+                R.string.action_dismiss_notification, KeyEvent.KEYCODE_X));
     }
 
     @Override
-    public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
+    protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
         if ((host.getParent() instanceof DeepShortcutView)) {
-            info.addAction(mActions.get(ADD_TO_WORKSPACE));
+            out.add(mActions.get(ADD_TO_WORKSPACE));
         } else if (host instanceof NotificationMainView) {
             if (((NotificationMainView) host).canChildBeDismissed()) {
-                info.addAction(mActions.get(DISMISS_NOTIFICATION));
+                out.add(mActions.get(DISMISS_NOTIFICATION));
             }
         }
     }
 
     @Override
-    public boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
+    protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
         if (action == ADD_TO_WORKSPACE) {
             if (!(host.getParent() instanceof DeepShortcutView)) {
                 return false;
@@ -73,9 +73,7 @@
                     mLauncher.getModelWriter().addItemToDatabase(info,
                             LauncherSettings.Favorites.CONTAINER_DESKTOP,
                             screenId, coordinates[0], coordinates[1]);
-                    ArrayList<ItemInfo> itemList = new ArrayList<>();
-                    itemList.add(info);
-                    mLauncher.bindItems(itemList, true);
+                    mLauncher.bindItems(Collections.singletonList(info), true);
                     AbstractFloatingView.closeAllOpenViews(mLauncher);
                     announceConfirmation(R.string.item_added_to_workspace);
                 }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 505e6d8..746bfba 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -67,12 +67,13 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
 /**
  * The all apps view container.
  */
 public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
-        Insettable, OnDeviceProfileChangeListener {
+        Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener {
 
     private static final float FLING_VELOCITY_MULTIPLIER = 135f;
     // Starts the springs after at least 55% of the animation has passed.
@@ -434,7 +435,7 @@
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
             findViewById(R.id.tab_work)
                     .setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
-            onTabChanged(mViewPager.getNextPage());
+            onActivePageChanged(mViewPager.getNextPage());
         } else {
             mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
             mAH[AdapterHolder.WORK].recyclerView = null;
@@ -483,7 +484,7 @@
         if (showTabs) {
             mViewPager = (AllAppsPagedView) newView;
             mViewPager.initParentViews(this);
-            mViewPager.getPageIndicator().setContainerView(this);
+            mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
         } else {
             mViewPager = null;
         }
@@ -493,14 +494,15 @@
         return mViewPager != null ? mViewPager : findViewById(R.id.apps_list_view);
     }
 
-    public void onTabChanged(int pos) {
-        mHeader.setMainActive(pos == 0);
-        if (mAH[pos].recyclerView != null) {
-            mAH[pos].recyclerView.bindFastScrollbar();
+    @Override
+    public void onActivePageChanged(int currentActivePage) {
+        mHeader.setMainActive(currentActivePage == 0);
+        if (mAH[currentActivePage].recyclerView != null) {
+            mAH[currentActivePage].recyclerView.bindFastScrollbar();
         }
         reset(true /* animate */);
         if (mWorkModeSwitch != null) {
-            mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
+            mWorkModeSwitch.setWorkTabVisible(currentActivePage == AdapterHolder.WORK
                     && mAllAppsStore.hasModelFlag(
                     FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 715c142..5030c5e 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -42,8 +42,7 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -109,7 +108,7 @@
         // The index of this app not including sections
         public int appIndex = -1;
         // Search section associated to result
-        public SearchSectionInfo searchSectionInfo = null;
+        public SectionDecorationInfo sectionDecorationInfo = null;
 
         /**
          * Factory method for AppIcon AdapterItem
@@ -372,10 +371,6 @@
 
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
-                && holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
-            ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
-        }
         switch (holder.getItemViewType()) {
             case VIEW_TYPE_ICON:
                 AdapterItem adapterItem = mApps.getAdapterItems().get(position);
@@ -409,10 +404,6 @@
     @Override
     public void onViewRecycled(@NonNull ViewHolder holder) {
         super.onViewRecycled(holder);
-        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
-        if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
-            ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index e2550f5..647402b 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -17,17 +17,17 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
 
-public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
-
-    static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
-    static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
-    static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
+/**
+ *  A {@link PagedView} for showing different views for the personal and work profile respectively
+ *  in the {@link AllAppsContainerView}.
+ */
+public class AllAppsPagedView extends PersonalWorkPagedView {
 
     public AllAppsPagedView(Context context) {
         this(context, null);
@@ -44,52 +44,4 @@
                         R.dimen.all_apps_header_top_padding);
         setPadding(0, topPadding, 0, 0);
     }
-
-    @Override
-    protected String getCurrentPageDescription() {
-        // Not necessary, tab-bar already has two tabs with their own descriptions.
-        return "";
-    }
-
-    @Override
-    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
-        super.onScrollChanged(l, t, oldl, oldt);
-        mPageIndicator.setScroll(l, mMaxScroll);
-    }
-
-    @Override
-    protected void determineScrollingStart(MotionEvent ev) {
-        float absDeltaX = Math.abs(ev.getX() - getDownMotionX());
-        float absDeltaY = Math.abs(ev.getY() - getDownMotionY());
-
-        if (Float.compare(absDeltaX, 0f) == 0) return;
-
-        float slope = absDeltaY / absDeltaX;
-        float theta = (float) Math.atan(slope);
-
-        if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
-            cancelCurrentPageLongPress();
-        }
-
-        if (theta > MAX_SWIPE_ANGLE) {
-            return;
-        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
-            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
-            float extraRatio = (float)
-                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
-            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
-        } else {
-            super.determineScrollingStart(ev);
-        }
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    @Override
-    protected boolean canScroll(float absVScroll, float absHScroll) {
-        return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 6c95992..9328a3d 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -18,16 +18,18 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.RectF;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
@@ -45,52 +47,51 @@
 
     @Override
     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
-        // Iterate through views in recylerview and draw bounds around views in the same section.
-        // Since views in the same section will follow each other, we can skip to a last view in
-        // a section to get the bounds of the section without having to iterate on every item.
-        int itemCount = parent.getChildCount();
         List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
-        SectionDecorationHandler lastDecorationHandler = null;
-        int i = 0;
-        while (i < itemCount) {
+        boolean drawFallbackFocusedView = true;
+        for (int i = 0; i < parent.getChildCount(); i++) {
             View view = parent.getChildAt(i);
-            if (view instanceof SelfDecoratingView) {
-                ((SelfDecoratingView) view).removeDecoration();
-            }
             int position = parent.getChildAdapterPosition(view);
             AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
-            if (adapterItem.searchSectionInfo != null) {
-                SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
-                int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
+            if (adapterItem.sectionDecorationInfo != null) {
+                SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
                 SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
-                if (decorationHandler != lastDecorationHandler && lastDecorationHandler != null) {
-                    drawDecoration(c, lastDecorationHandler, parent);
-                }
-                lastDecorationHandler = decorationHandler;
                 if (decorationHandler != null) {
                     decorationHandler.extendBounds(view);
-                }
-
-                if (endIndex > i) {
-                    i = endIndex;
-                    continue;
+                    if (sectionInfo.isFocusedView()) {
+                        decorationHandler.onFocusDraw(c, view);
+                        drawFallbackFocusedView = false;
+                    } else {
+                        decorationHandler.onGroupDraw(c);
+                    }
                 }
             }
-            i++;
         }
-        if (lastDecorationHandler != null) {
-            drawDecoration(c, lastDecorationHandler, parent);
+        // fallback logic in case none of the SearchTarget is labeled as focused item
+        if (drawFallbackFocusedView) {
+            for (int i = 0; i < parent.getChildCount(); i++) {
+                View view = parent.getChildAt(i);
+                int position = parent.getChildAdapterPosition(view);
+                AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
+                if (adapterItem.sectionDecorationInfo != null) {
+                    SectionDecorationInfo sectionInfo = adapterItem.sectionDecorationInfo;
+                    SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
+                    if (decorationHandler != null) {
+                        drawDecoration(c, decorationHandler, parent);
+                    }
+                }
+            }
         }
     }
 
-    private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
-            RecyclerView parent) {
-        if (decorationHandler == null) return;
+    // Fallback logic in case non of the SearchTarget is labeled as focused item.
+    private void drawDecoration(@NonNull Canvas c,
+            @NonNull SectionDecorationHandler decorationHandler,
+            @NonNull RecyclerView parent) {
         if (decorationHandler.mIsFullWidth) {
             decorationHandler.mBounds.left = parent.getPaddingLeft();
             decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
         }
-        decorationHandler.onDraw(c);
         if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
                 && mAppsView.getApps().getFocusedChild() != null) {
             int index = mAppsView.getApps().getFocusedChildIndex();
@@ -109,23 +110,41 @@
      * Handles grouping and drawing of items in the same all apps sections.
      */
     public static class SectionDecorationHandler {
-        private static final int FILL_ALPHA = 0;
-
         protected RectF mBounds = new RectF();
         private final boolean mIsFullWidth;
         private final float mRadius;
 
-        protected int mFocusColor;
-        protected int mFillcolor;
+        protected final int mFocusColor; // main focused item color
+        protected final int mFillcolor; // grouping color
+
         private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private final boolean mIsTopRound;
+        private final boolean mIsBottomRound;
+        private float [] mCorners;
+        private float mFillSpacing;
 
+        public SectionDecorationHandler(Context context, boolean isFullWidth, int fillAlpha,
+                boolean isTopRound, boolean isBottomRound) {
 
-        public SectionDecorationHandler(Context context, boolean isFullWidth) {
             mIsFullWidth = isFullWidth;
             int endScrim = Themes.getColorBackground(context);
-            mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
+            mFillcolor = ColorUtils.setAlphaComponent(endScrim, fillAlpha);
             mFocusColor = endScrim;
-            mRadius = Themes.getDialogCornerRadius(context);
+
+            mIsTopRound = isTopRound;
+            mIsBottomRound = isBottomRound;
+
+            mRadius = context.getResources().getDimensionPixelSize(
+                    R.dimen.search_decoration_corner_radius);
+            mFillSpacing = context.getResources().getDimensionPixelSize(
+                    R.dimen.search_decoration_padding);
+            mCorners = new float[]{
+                    mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top left radius in px
+                    mIsTopRound ? mRadius : 0, mIsTopRound ? mRadius : 0, // Top right radius in px
+                    mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0, // Bottom right
+                    mIsBottomRound ? mRadius : 0, mIsBottomRound ? mRadius : 0  // Bottom left
+            };
+
         }
 
         /**
@@ -147,9 +166,9 @@
         /**
          * Draw bounds onto canvas.
          */
-        public void onDraw(Canvas canvas) {
+        public void onGroupDraw(Canvas canvas) {
             mPaint.setColor(mFillcolor);
-            canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+            onDraw(canvas);
         }
 
         /**
@@ -159,13 +178,20 @@
             if (view == null) {
                 return;
             }
-            if (view instanceof SelfDecoratingView) {
-                ((SelfDecoratingView) view).decorate(mFocusColor);
-                return;
-            }
             mPaint.setColor(mFocusColor);
-            canvas.drawRoundRect(view.getLeft(), view.getTop(),
-                    view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
+            mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+            onDraw(canvas);
+        }
+
+
+        private void onDraw(Canvas canvas) {
+            final Path path = new Path();
+            RectF finalBounds = new RectF(mBounds.left + mFillSpacing,
+                    mBounds.top + mFillSpacing,
+                    mBounds.right - mFillSpacing,
+                    mBounds.bottom - mFillSpacing);
+            path.addRoundRect(finalBounds, mCorners, Path.Direction.CW);
+            canvas.drawPath(path, mPaint);
         }
 
         /**
@@ -175,19 +201,4 @@
             mBounds.setEmpty();
         }
     }
-
-    /**
-     * An interface for a view to draw highlight indicator
-     */
-    public interface SelfDecoratingView {
-        /**
-         * Removes decorations drawing if focus is acquired by another view
-         */
-        void removeDecoration();
-
-        /**
-         * Draws highlight indicator on view.
-         */
-        void decorate(int focusColor);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 769cb5e..355ccad 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -21,6 +21,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -78,6 +80,11 @@
         return (mModelFlags & mask) != 0;
     }
 
+    /**
+     * Returns {@link AppInfo} if any apps matches with provided {@link ComponentKey}, otherwise
+     * null.
+     */
+    @Nullable
     public AppInfo getApp(ComponentKey key) {
         mTempInfo.componentName = key.componentName;
         mTempInfo.user = key.user;
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 1dc10fe..fefd97a 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -20,7 +20,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.allapps.search.SectionDecorationInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -185,7 +185,7 @@
         if (results == null || mSearchResults != results) {
             boolean same = mSearchResults != null && mSearchResults.equals(results);
             mSearchResults = results;
-            onAppsUpdated();
+            updateAdapterItems();
             return !same;
         }
         return false;
@@ -201,20 +201,11 @@
     }
 
     void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
-        SearchSectionInfo lastSection = null;
         for (int i = 0; i < list.size(); i++) {
             AdapterItem adapterItem = list.get(i);
             adapterItem.position = offset + i;
             mAdapterItems.add(adapterItem);
-            if (adapterItem.searchSectionInfo != lastSection) {
-                if (adapterItem.searchSectionInfo != null) {
-                    adapterItem.searchSectionInfo.setPosStart(adapterItem.position);
-                }
-                if (lastSection != null) {
-                    lastSection.setPosEnd(adapterItem.position - 1);
-                }
-                lastSection = adapterItem.searchSectionInfo;
-            }
+
             if (adapterItem.isCountedForAccessibility()) {
                 mAccessibilityResultsCount++;
             }
@@ -266,11 +257,13 @@
         }
 
         // Recompose the set of adapter items from the current set of apps
-        updateAdapterItems();
+        if (mSearchResults == null) {
+            updateAdapterItems();
+        }
     }
 
     /**
-     * Updates the set of filtered apps with the current filter.  At this point, we expect
+     * Updates the set of filtered apps with the current filter. At this point, we expect
      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
      */
     private void updateAdapterItems() {
@@ -295,16 +288,16 @@
         mFastScrollerSections.clear();
         mAdapterItems.clear();
 
-        SearchSectionInfo appSection = new SearchSectionInfo();
+        SectionDecorationInfo appSection = new SectionDecorationInfo();
         appSection.setDecorationHandler(
-                new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true));
+                new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true,
+                        0, false, false));
 
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
 
         if (!hasFilter()) {
             mAccessibilityResultsCount = mApps.size();
-            appSection.setPosStart(position);
             for (AppInfo info : mApps) {
                 String sectionName = info.sectionName;
 
@@ -321,11 +314,10 @@
                     lastFastScrollerSectionInfo.fastScrollToItem = appItem;
                 }
                 if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                    appItem.searchSectionInfo = appSection;
+                    appItem.sectionDecorationInfo = appSection;
                 }
                 mAdapterItems.add(appItem);
             }
-            appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
         } else {
             updateSearchAdapterItems(mSearchResults, 0);
             if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 4876298..bc2e66c 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -87,17 +87,18 @@
     }
 
     @Override
-    public void onTabChanged(int pos) {
-        super.onTabChanged(pos);
+    public void onActivePageChanged(int currentActivePage) {
+        super.onActivePageChanged(currentActivePage);
         if (mUsingTabs) {
             // Log tab switches only when the launcher is in AllApps state
             if (mLauncher.getStateManager().getCurrentStableState() == LauncherState.ALL_APPS) {
                 mLauncher.getStatsLogManager().logger()
-                        .log(pos == AdapterHolder.WORK ? LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB
+                        .log(currentActivePage == AdapterHolder.WORK
+                                ? LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB
                                 : LAUNCHER_ALLAPPS_SWITCHED_TO_MAIN_TAB);
             }
 
-            if (pos == AdapterHolder.WORK) {
+            if (currentActivePage == AdapterHolder.WORK) {
                 WorkEduView.showWorkEduIfNeeded(mLauncher);
             } else {
                 mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index 84688e1..f9fb22e 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -37,14 +37,14 @@
 
     private static final int MAX_RESULTS_COUNT = 5;
 
-    private final SearchSectionInfo mSearchSectionInfo;
+    private final SectionDecorationInfo mSearchSectionInfo;
     private final LauncherAppState mLauncherAppState;
 
     public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
         mLauncherAppState = launcherAppState;
-        mSearchSectionInfo = new SearchSectionInfo();
+        mSearchSectionInfo = new SectionDecorationInfo();
         mSearchSectionInfo.setDecorationHandler(
-                new SectionDecorationHandler(context, true));
+                new SectionDecorationHandler(context, true, 0, true, true));
     }
 
     @Override
@@ -81,7 +81,7 @@
         ArrayList<AdapterItem> items = new ArrayList<>();
         for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
             AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i);
-            appItem.searchSectionInfo = mSearchSectionInfo;
+            appItem.sectionDecorationInfo = mSearchSectionInfo;
             items.add(appItem);
         }
 
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
similarity index 73%
rename from src/com/android/launcher3/allapps/search/SearchSectionInfo.java
rename to src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
index 464df68..0b64fca 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SectionDecorationInfo.java
@@ -18,37 +18,30 @@
 import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
 
 /**
- * Info class for a search section
+ * Info class for a search section that is primarily used for decoration.
  */
-public class SearchSectionInfo {
+public class SectionDecorationInfo {
+
+    public static final int QUICK_LAUNCH = 1 << 0;
+    public static final int GROUPING = 1 << 1;
 
     private String mSectionId;
+    private boolean mFocused;
     private SectionDecorationHandler mDecorationHandler;
 
-    public int getPosStart() {
-        return mPosStart;
+    public boolean isFocusedView() {
+        return mFocused;
     }
 
-    public void setPosStart(int posStart) {
-        mPosStart = posStart;
+    public void setFocusedView(boolean focused) {
+        mFocused = focused;
     }
 
-    public int getPosEnd() {
-        return mPosEnd;
-    }
-
-    public void setPosEnd(int posEnd) {
-        mPosEnd = posEnd;
-    }
-
-    private int mPosStart;
-    private int mPosEnd;
-
-    public SearchSectionInfo() {
+    public SectionDecorationInfo() {
         this(null);
     }
 
-    public SearchSectionInfo(String sectionId) {
+    public SectionDecorationInfo(String sectionId) {
         mSectionId = sectionId;
     }
 
@@ -56,7 +49,6 @@
         mDecorationHandler = sectionDecorationHandler;
     }
 
-
     public SectionDecorationHandler getDecorationHandler() {
         return mDecorationHandler;
     }
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
index a9389bc..71e10a8 100644
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -10,7 +10,9 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.widget.WidgetCell;
 
 /**
@@ -36,6 +38,15 @@
         mPreview = view;
     }
 
+    public RemoteViews getPreview() {
+        return mPreview;
+    }
+
+    /** Resets any resource. This should be called before recycling this view. */
+    public void reset() {
+        mPreview = null;
+    }
+
     @Override
     public void ensurePreview() {
         if (mPreview != null && mActiveRequest == null) {
@@ -49,6 +60,18 @@
         super.ensurePreview();
     }
 
+    @Override
+    public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
+        if (mPreview == null
+                && item.widgetInfo != null
+                && item.widgetInfo.previewLayout != View.NO_ID) {
+            mPreview = new RemoteViews(item.widgetInfo.provider.getPackageName(),
+                    item.widgetInfo.previewLayout);
+        }
+
+        super.applyFromCellItem(item, loader);
+    }
+
     /**
      * Generates a bitmap by inflating {@param views}.
      * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 74d8dca..504b29e 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -46,6 +46,7 @@
 import android.util.Pair;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -82,7 +83,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
@@ -96,6 +96,8 @@
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.views.ClipPathView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -165,7 +167,11 @@
     private AnimatorSet mCurrentAnimator;
     private boolean mIsAnimatingClosed = false;
 
+    // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar).
+    // Anything specific to Launcher should use mLauncher, otherwise should use mActivityContext.
     protected final Launcher mLauncher;
+    protected final ActivityContext mActivityContext;
+
     protected DragController mDragController;
     public FolderInfo mInfo;
     private CharSequence mFromTitle;
@@ -228,6 +234,7 @@
         setAlwaysDrawnWithCacheEnabled(false);
 
         mLauncher = Launcher.getLauncher(context);
+        mActivityContext = ActivityContext.lookupContext(context);
         mStatsLogManager = StatsLogManager.newInstance(context);
         // We need this view to be focusable in touch mode so that when text editing of the folder
         // name is complete, we have something to focus on, thus hiding the cursor and giving
@@ -457,9 +464,9 @@
         Collections.sort(children, ITEM_POS_COMPARATOR);
         updateItemLocationsInDatabaseBatch(true);
 
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
         if (lp == null) {
-            lp = new DragLayer.LayoutParams(0, 0);
+            lp = new BaseDragLayer.LayoutParams(0, 0);
             lp.customPosition = true;
             setLayoutParams(lp);
         }
@@ -513,13 +520,14 @@
     /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
-     * @param launcher The main activity.
+     * @param activityContext The main ActivityContext in which to inflate this Folder. It must also
+     *                        be an instance or ContextWrapper around the Launcher activity context.
      *
      * @return A new UserFolder.
      */
     @SuppressLint("InflateParams")
-    static Folder fromXml(Launcher launcher) {
-        return (Folder) launcher.getLayoutInflater()
+    static <T extends Context & ActivityContext> Folder fromXml(T activityContext) {
+        return (Folder) LayoutInflater.from(activityContext).cloneInContext(activityContext)
                 .inflate(R.layout.user_folder_icon_normalized, null);
     }
 
@@ -597,7 +605,7 @@
      * is played.
      */
     private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
-        Folder openFolder = getOpen(mLauncher);
+        Folder openFolder = getOpen(mActivityContext);
         if (openFolder != null && openFolder != this) {
             // Close any open folder before opening a folder.
             openFolder.close(true);
@@ -610,7 +618,7 @@
 
         mIsOpen = true;
 
-        DragLayer dragLayer = mLauncher.getDragLayer();
+        BaseDragLayer dragLayer = mActivityContext.getDragLayer();
         // Just verify that the folder hasn't already been added to the DragLayer.
         // There was a one-off crash where the folder had a parent already.
         if (getParent() == null) {
@@ -724,7 +732,7 @@
 
         // Notify the accessibility manager that this folder "window" has disappeared and no
         // longer occludes the workspace items
-        mLauncher.getDragLayer().sendAccessibilityEvent(
+        mActivityContext.getDragLayer().sendAccessibilityEvent(
                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
     }
 
@@ -772,7 +780,7 @@
 
     private void closeComplete(boolean wasAnimated) {
         // TODO: Clear all active animations.
-        DragLayer parent = (DragLayer) getParent();
+        BaseDragLayer parent = (BaseDragLayer) getParent();
         if (parent != null) {
             parent.removeView(this);
         }
@@ -1011,7 +1019,7 @@
 
     private void updateItemLocationsInDatabaseBatch(boolean isBind) {
         FolderGridOrganizer verifier = new FolderGridOrganizer(
-                mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+                mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
 
         ArrayList<ItemInfo> items = new ArrayList<>();
         int total = mInfo.contents.size();
@@ -1048,10 +1056,8 @@
     }
 
     private void centerAboutIcon() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-
-        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        DragLayer parent = mLauncher.getDragLayer();
+        BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
+        BaseDragLayer parent = mActivityContext.getDragLayer();
         int width = getFolderWidth();
         int height = getFolderHeight();
 
@@ -1061,38 +1067,13 @@
         int centeredLeft = centerX - width / 2;
         int centeredTop = centerY - height / 2;
 
-        // We need to bound the folder to the currently visible workspace area
-        if (mLauncher.getStateManager().getState().overviewUi) {
-            parent.getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(), sTempRect);
-        } else {
-            mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
-        }
-        int left = Math.min(Math.max(sTempRect.left, centeredLeft),
-                sTempRect.right- width);
-        int top = Math.min(Math.max(sTempRect.top, centeredTop),
-                sTempRect.bottom - height);
-
-        int distFromEdgeOfScreen = mLauncher.getWorkspace().getPaddingLeft() + getPaddingLeft();
-
-        if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
-            // Center the folder if it is very close to being centered anyway, by virtue of
-            // filling the majority of the viewport. ie. remove it from the uncanny valley
-            // of centeredness.
-            left = (grid.availableWidthPx - width) / 2;
-        } else if (width >= sTempRect.width()) {
-            // If the folder doesn't fit within the bounds, center it about the desired bounds
-            left = sTempRect.left + (sTempRect.width() - width) / 2;
-        }
-        if (height >= sTempRect.height()) {
-            // Folder height is greater than page height, center on page
-            top = sTempRect.top + (sTempRect.height() - height) / 2;
-        } else {
-            // Folder height is less than page height, so bound it to the absolute open folder
-            // bounds if necessary
-            Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
-            left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
-            top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
-        }
+        sTempRect.set(mActivityContext.getFolderBoundingBox());
+        int left = Utilities.boundToRange(centeredLeft, sTempRect.left, sTempRect.right - width);
+        int top = Utilities.boundToRange(centeredTop, sTempRect.top, sTempRect.bottom - height);
+        int[] inOutPosition = new int[] {left, top};
+        mActivityContext.updateOpenFolderPosition(inOutPosition, sTempRect, width, height);
+        left = inOutPosition[0];
+        top = inOutPosition[1];
 
         int folderPivotX = width / 2 + (centeredLeft - left);
         int folderPivotY = height / 2 + (centeredTop - top);
@@ -1106,7 +1087,7 @@
     }
 
     protected int getContentAreaHeight() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
+        DeviceProfile grid = mActivityContext.getDeviceProfile();
         int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y
                 - mFooterHeight;
         int height = Math.min(maxContentAreaHeight,
@@ -1384,7 +1365,7 @@
     @Override
     public void onAdd(WorkspaceItemInfo item, int rank) {
         FolderGridOrganizer verifier = new FolderGridOrganizer(
-                mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo);
+                mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo);
         verifier.updateRankAndPos(item, rank);
         mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX,
                 item.cellY);
@@ -1591,8 +1572,8 @@
     /**
      * Returns a folder which is already open or null
      */
-    public static Folder getOpen(Launcher launcher) {
-        return getOpenView(launcher, TYPE_FOLDER);
+    public static Folder getOpen(ActivityContext activityContext) {
+        return getOpenView(activityContext, TYPE_FOLDER);
     }
 
     /**
@@ -1611,7 +1592,7 @@
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            DragLayer dl = mLauncher.getDragLayer();
+            BaseDragLayer dl = (BaseDragLayer) getParent();
 
             if (isEditingName()) {
                 if (!dl.isEventOverView(mFolderName, ev)) {
@@ -1635,6 +1616,11 @@
         return false;
     }
 
+    @Override
+    public boolean canInterceptEventsInSystemGestureRegion() {
+        return true;
+    }
+
     /**
      * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of
      * rounded rect.
@@ -1663,9 +1649,9 @@
 
     /** Returns the height of the current folder's bottom edge from the bottom of the screen. */
     private int getHeightFromBottom() {
-        DragLayer.LayoutParams layoutParams = (DragLayer.LayoutParams) getLayoutParams();
+        BaseDragLayer.LayoutParams layoutParams = (BaseDragLayer.LayoutParams) getLayoutParams();
         int folderBottomPx = layoutParams.y + layoutParams.height;
-        int windowBottomPx = mLauncher.getDeviceProfile().heightPx;
+        int windowBottomPx = mActivityContext.getDeviceProfile().heightPx;
 
         return windowBottomPx - folderBottomPx;
     }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 3d72b49..1cac31e 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -39,14 +39,13 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.List;
 
@@ -69,7 +68,6 @@
     private PreviewBackground mPreviewBackground;
 
     private Context mContext;
-    private Launcher mLauncher;
 
     private final boolean mIsOpening;
 
@@ -92,8 +90,7 @@
         mPreviewBackground = mFolderIcon.mBackground;
 
         mContext = folder.getContext();
-        mLauncher = folder.mLauncher;
-        mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
+        mPreviewVerifier = new FolderGridOrganizer(folder.mActivityContext.getDeviceProfile().inv);
 
         mIsOpening = isOpening;
 
@@ -114,14 +111,15 @@
      * Prepares the Folder for animating between open / closed states.
      */
     public AnimatorSet getAnimator() {
-        final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams();
+        final BaseDragLayer.LayoutParams lp =
+                (BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
         mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
         ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
         final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
 
         // Match position of the FolderIcon
         final Rect folderIconPos = new Rect();
-        float scaleRelativeToDragLayer = mLauncher.getDragLayer()
+        float scaleRelativeToDragLayer = mFolder.mActivityContext.getDragLayer()
                 .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos);
         int scaledRadius = mPreviewBackground.getScaledRadius();
         float initialSize = (scaledRadius * 2) * scaleRelativeToDragLayer;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6477de3..6b02021 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -166,19 +166,19 @@
         mDotParams = new DotRenderer.DrawParams();
     }
 
-    public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
-            FolderInfo folderInfo) {
-        Folder folder = Folder.fromXml(launcher);
-        folder.setDragController(launcher.getDragController());
+    public static <T extends Context & ActivityContext> FolderIcon inflateFolderAndIcon(int resId,
+            T activityContext, ViewGroup group, FolderInfo folderInfo) {
+        Folder folder = Folder.fromXml(activityContext);
+        folder.setDragController(folder.mLauncher.getDragController());
 
-        FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
+        FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
         icon.setFolder(folder);
 
-        icon.setOnFocusChangeListener(launcher.getFocusHandler());
+        icon.setOnFocusChangeListener(folder.mLauncher.getFocusHandler());
         icon.mBackground.paddingY = icon.isInHotseat()
-                ? 0 : launcher.getDeviceProfile().cellYPaddingPx;
+                ? 0 : activityContext.getDeviceProfile().cellYPaddingPx;
         return icon;
     }
 
@@ -754,20 +754,14 @@
     }
 
     public void clearLeaveBehindIfExists() {
-        ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
-        if (isInHotseat()) {
-            CellLayout cl = (CellLayout) getParent().getParent();
-            cl.clearFolderLeaveBehind();
+        if (getParent() instanceof FolderIconParent) {
+            ((FolderIconParent) getParent()).clearFolderLeaveBehind(this);
         }
     }
 
     public void drawLeaveBehindIfExists() {
-        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
-        // While the folder is open, the position of the icon cannot change.
-        lp.canReorder = false;
-        if (isInHotseat()) {
-            CellLayout cl = (CellLayout) getParent().getParent();
-            cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+        if (getParent() instanceof FolderIconParent) {
+            ((FolderIconParent) getParent()).drawFolderLeaveBehindForIcon(this);
         }
     }
 
@@ -836,4 +830,19 @@
                     MAX_NUM_ITEMS_IN_PREVIEW);
         }
     }
+
+    /**
+     * Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon.
+     */
+    public interface FolderIconParent {
+        /**
+         * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open and leaving a
+         * gap where the FolderIcon would be when the Folder is closed.
+         */
+        void drawFolderLeaveBehindForIcon(FolderIcon child);
+        /**
+         * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder is closed.
+         */
+        void clearFolderLeaveBehind(FolderIcon child);
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index df3e92c..0235dfa 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
@@ -230,7 +229,7 @@
     }
 
     private CellLayout createAndAddNewPage() {
-        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+        DeviceProfile grid = mFolder.mActivityContext.getDeviceProfile();
         CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
         page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
         page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
@@ -624,7 +623,7 @@
 
     @Override
     protected boolean canScroll(float absVScroll, float absHScroll) {
-        return AbstractFloatingView.getTopOpenViewWithType(mFolder.mLauncher,
+        return AbstractFloatingView.getTopOpenViewWithType(mFolder.mActivityContext,
                 TYPE_ALL & ~TYPE_FOLDER) == null;
     }
 
diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java
deleted file mode 100644
index 77ce4a8..0000000
--- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2016 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.keyboard;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.PopupMenu;
-import android.widget.PopupMenu.OnMenuItemClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.popup.PopupContainerWithArrow;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Handles showing a popup menu with available custom actions for a launcher icon.
- * This allows exposing various custom actions using keyboard shortcuts.
- */
-public class CustomActionsPopup implements OnMenuItemClickListener {
-
-    private final Launcher mLauncher;
-    private final LauncherAccessibilityDelegate mDelegate;
-    private final View mIcon;
-
-    public CustomActionsPopup(Launcher launcher, View icon) {
-        mLauncher = launcher;
-        mIcon = icon;
-        PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher);
-        if (container != null) {
-            mDelegate = container.getAccessibilityDelegate();
-        } else {
-            mDelegate = launcher.getAccessibilityDelegate();
-        }
-    }
-
-    private List<AccessibilityAction> getActionList() {
-        if (mIcon == null || !(mIcon.getTag() instanceof ItemInfo)) {
-            return Collections.EMPTY_LIST;
-        }
-
-        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-        mDelegate.addSupportedActions(mIcon, info, true);
-        List<AccessibilityAction> result = new ArrayList<>(info.getActionList());
-        info.recycle();
-        return result;
-    }
-
-    public boolean canShow() {
-        return !getActionList().isEmpty();
-    }
-
-    public boolean show() {
-        List<AccessibilityAction> actions = getActionList();
-        if (actions.isEmpty()) {
-            return false;
-        }
-
-        PopupMenu popup = new PopupMenu(mLauncher, mIcon);
-        popup.setOnMenuItemClickListener(this);
-        Menu menu = popup.getMenu();
-        for (AccessibilityAction action : actions) {
-            menu.add(Menu.NONE, action.getId(), Menu.NONE, action.getLabel());
-        }
-        popup.show();
-        return true;
-    }
-
-    @Override
-    public boolean onMenuItemClick(MenuItem menuItem) {
-        return mDelegate.performAction(mIcon, (ItemInfo) mIcon.getTag(), menuItem.getItemId(),
-                true);
-    }
-}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 1266bb4..bf420d9 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 
 import androidx.annotation.Nullable;
+import androidx.slice.SliceItem;
 
 import com.android.launcher3.R;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
@@ -31,8 +32,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import java.util.Optional;
-
 /**
  * Handles the user event logging in R+.
  *
@@ -368,6 +367,31 @@
 
         @UiEvent(doc = "User switched to Work tab in AllApps screen.")
         LAUNCHER_ALLAPPS_SWITCHED_TO_WORK_TAB(696),
+
+        @UiEvent(doc = "Default event when dedicated UI event is not available for the user action"
+                + " on slice .")
+        LAUNCHER_SLICE_DEFAULT_ACTION(700),
+
+        @UiEvent(doc = "User toggled-on a Slice item.")
+        LAUNCHER_SLICE_TOGGLE_ON(701),
+
+        @UiEvent(doc = "User toggled-off a Slice item.")
+        LAUNCHER_SLICE_TOGGLE_OFF(702),
+
+        @UiEvent(doc = "User acted on a Slice item with a button.")
+        LAUNCHER_SLICE_BUTTON_ACTION(703),
+
+        @UiEvent(doc = "User acted on a Slice item with a slider.")
+        LAUNCHER_SLICE_SLIDER_ACTION(704),
+
+        @UiEvent(doc = "User tapped on the entire row of a Slice.")
+        LAUNCHER_SLICE_CONTENT_ACTION(705),
+
+        @UiEvent(doc = "User tapped on the see more button of a Slice.")
+        LAUNCHER_SLICE_SEE_MORE_ACTION(706),
+
+        @UiEvent(doc = "User selected from a selection row of Slice.")
+        LAUNCHER_SLICE_SELECTION_ACTION(707),
         ;
 
         // ADD MORE
@@ -475,6 +499,13 @@
         }
 
         /**
+         * Sets logging fields from provided {@link SliceItem}.
+         */
+        default StatsLogger withSliceItem(SliceItem sliceItem) {
+            return this;
+        }
+
+        /**
          * Builds the final message and logs it as {@link EventEnum}.
          */
         default void log(EventEnum event) {
@@ -486,7 +517,9 @@
      */
     public StatsLogger logger() {
         StatsLogger logger = createLogger();
-        Optional.ofNullable(mInstanceId).ifPresent(logger::withInstanceId);
+        if (mInstanceId != null) {
+            return logger.withInstanceId(mInstanceId);
+        }
         return logger;
     }
 
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index e3e4b69..92b5885 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -22,7 +22,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.os.LocaleList;
@@ -137,7 +136,7 @@
         if (findAppInfo(info.componentName, info.user) != null) {
             return;
         }
-        mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */);
+        mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
         info.sectionName = mIndex.computeSectionName(info.title);
 
         data.add(info);
@@ -145,10 +144,9 @@
     }
 
     public void addPromiseApp(Context context, PackageInstallInfo installInfo) {
-        ApplicationInfo applicationInfo = new PackageManagerHelper(context)
-                .getApplicationInfo(installInfo.packageName, installInfo.user, 0);
         // only if not yet installed
-        if (applicationInfo == null) {
+        if (!new PackageManagerHelper(context)
+                .isAppInstalled(installInfo.packageName, installInfo.user)) {
             AppInfo info = new AppInfo(installInfo);
             mIconCache.getTitleAndIcon(info, info.usingLowResIcon());
             info.sectionName = mIndex.computeSectionName(info.title);
@@ -282,7 +280,7 @@
                 } else {
                     Intent launchIntent = AppInfo.makeLaunchIntent(info);
 
-                    mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */);
+                    mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
                     applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
                     applicationInfo.setProgressLevel(
                             PackageManagerHelper.getLoadingProgress(info),
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index e3e8769..434776c 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.model;
 
-import android.content.ComponentName;
+import android.content.Intent;
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherAppState;
@@ -66,8 +66,8 @@
         final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
-                ComponentName cn = si.getTargetComponent();
-                if ((cn != null) && cn.getPackageName().equals(mPackageName)) {
+                Intent intent = si.getIntent();
+                if ((intent != null) && mPackageName.equals(intent.getPackage())) {
                     si.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
                     si.setProgressLevel(downloadInfo);
                     updatedWorkspaceItems.add(si);
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 8215edd..1380e9e 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.model;
 
-import android.content.ComponentName;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
@@ -72,9 +72,9 @@
         synchronized (dataModel) {
             final HashSet<ItemInfo> updates = new HashSet<>();
             dataModel.forAllWorkspaceItemInfos(mInstallInfo.user, si -> {
-                ComponentName cn = si.getTargetComponent();
-                if (si.hasPromiseIconUi() && (cn != null)
-                        && cn.getPackageName().equals(mInstallInfo.packageName)) {
+                Intent intent = si.getIntent();
+                if (si.hasPromiseIconUi() && (intent != null)
+                        && mInstallInfo.packageName.equals(intent.getPackage())) {
                     int installProgress = mInstallInfo.progress;
 
                     si.setProgressLevel(installProgress, PackageInstallInfo.STATUS_INSTALLING);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index f13a109..7bfa3ef 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -30,6 +30,7 @@
 import android.util.Log;
 
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
@@ -228,7 +229,8 @@
                                 isTargetValid = context.getSystemService(LauncherApps.class)
                                         .isActivityEnabled(cn, mUser);
                             }
-                            if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
+                            if (!isTargetValid && si.hasStatusFlag(
+                                    FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) {
                                 if (updateWorkspaceItemIntent(context, si, packageName)) {
                                     infoUpdated = true;
                                 } else if (si.hasPromiseIconUi()) {
@@ -250,8 +252,7 @@
                             }
                         }
 
-                        if (isNewApkAvailable
-                                && si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                        if (isNewApkAvailable) {
                             List<LauncherActivityInfo> activities = activitiesLists.get(
                                     packageName);
                             si.setProgressLevel(
@@ -260,8 +261,10 @@
                                             : PackageManagerHelper.getLoadingProgress(
                                                     activities.get(0)),
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
-                            iconCache.getTitleAndIcon(si, si.usingLowResIcon());
-                            infoUpdated = true;
+                            if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
+                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
+                                infoUpdated = true;
+                            }
                         }
 
                         int oldRuntimeFlags = si.runtimeStatusFlags;
@@ -353,6 +356,11 @@
      */
     private boolean updateWorkspaceItemIntent(Context context,
             WorkspaceItemInfo si, String packageName) {
+        if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            // Do not update intent for deep shortcuts as they contain additional information
+            // about the shortcut.
+            return false;
+        }
         // Try to find the best match activity.
         Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
         if (intent != null) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 6fedad1..4296d32 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -66,6 +67,14 @@
         }
 
         if (!matchingWorkspaceItems.isEmpty()) {
+            if (mShortcuts.isEmpty()) {
+                // Verify that the app is indeed installed.
+                if (!new PackageManagerHelper(app.getContext())
+                        .isAppInstalled(mPackageName, mUser)) {
+                    // App is not installed, ignoring package events
+                    return;
+                }
+            }
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
             List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
                     .map(WorkspaceItemInfo::getDeepShortcutId)
diff --git a/src/com/android/launcher3/model/data/PackageItemInfo.java b/src/com/android/launcher3/model/data/PackageItemInfo.java
index b70d0d4..7617d7e 100644
--- a/src/com/android/launcher3/model/data/PackageItemInfo.java
+++ b/src/com/android/launcher3/model/data/PackageItemInfo.java
@@ -60,6 +60,6 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(packageName);
+        return Objects.hash(packageName, user);
     }
 }
diff --git a/src/com/android/launcher3/model/data/SearchActionItemInfo.java b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
index 25355c9..e3b3b09 100644
--- a/src/com/android/launcher3/model/data/SearchActionItemInfo.java
+++ b/src/com/android/launcher3/model/data/SearchActionItemInfo.java
@@ -25,6 +25,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.logger.LauncherAtom.ItemInfo;
+import com.android.launcher3.logger.LauncherAtom.SearchActionItem;
+
 /**
  * Represents a SearchAction with in launcher
  */
@@ -39,12 +42,15 @@
     private int mFlags = 0;
     private final Icon mIcon;
 
+    // If true title does not contain any personal info and eligible for logging.
+    private final boolean mIsPersonalTitle;
     private Intent mIntent;
 
     private PendingIntent mPendingIntent;
 
     public SearchActionItemInfo(Icon icon, String packageName, UserHandle user,
-            CharSequence title) {
+            CharSequence title, boolean isPersonalTitle) {
+        mIsPersonalTitle = isPersonalTitle;
         this.user = user == null ? Process.myUserHandle() : user;
         this.title = title;
         this.container = EXTENDED_CONTAINERS;
@@ -59,6 +65,7 @@
         mFlags = info.mFlags;
         title = info.title;
         this.container = EXTENDED_CONTAINERS;
+        this.mIsPersonalTitle = info.mIsPersonalTitle;
     }
 
     /**
@@ -112,4 +119,18 @@
     public ItemInfoWithIcon clone() {
         return new SearchActionItemInfo(this);
     }
+
+    @Override
+    public ItemInfo buildProto(FolderInfo fInfo) {
+        SearchActionItem.Builder itemBuilder = SearchActionItem.newBuilder()
+                .setPackageName(mFallbackPackageName);
+
+        if (!mIsPersonalTitle) {
+            itemBuilder.setTitle(title.toString());
+        }
+        return getDefaultItemInfoBuilder()
+                .setSearchActionItem(itemBuilder)
+                .setContainerInfo(getContainerInfo())
+                .build();
+    }
 }
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 059ad18..2905dc3 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -16,9 +16,9 @@
 
 package com.android.launcher3.notification;
 
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.annotation.TargetApi;
 import android.app.Notification;
@@ -37,8 +37,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.SecureSettingsObserver;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,7 +81,8 @@
     /** The last notification key that was dismissed from launcher UI */
     private String mLastKeyDismissedByLauncher;
 
-    private SecureSettingsObserver mNotificationDotsObserver;
+    private SettingsCache mSettingsCache;
+    private SettingsCache.OnChangeListener mNotificationSettingsChangedListener;
 
     public NotificationListener() {
         mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
@@ -207,10 +208,12 @@
         super.onListenerConnected();
         sIsConnected = true;
 
-        mNotificationDotsObserver =
-                newNotificationSettingsObserver(this, this::onNotificationSettingsChanged);
-        mNotificationDotsObserver.register();
-        mNotificationDotsObserver.dispatchOnChange();
+        // Register an observer to rebind the notification listener when dots are re-enabled.
+        mSettingsCache = SettingsCache.INSTANCE.get(this);
+        mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
+        mSettingsCache.register(NOTIFICATION_BADGING_URI,
+                mNotificationSettingsChangedListener);
+        mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
 
         onNotificationFullRefresh();
     }
@@ -229,7 +232,7 @@
     public void onListenerDisconnected() {
         super.onListenerDisconnected();
         sIsConnected = false;
-        mNotificationDotsObserver.unregister();
+        mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
         onNotificationFullRefresh();
     }
 
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index fa25114..0091af1 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -217,8 +217,8 @@
                 && sessionInfo.getAppIcon() != null
                 && !TextUtils.isEmpty(sessionInfo.getAppLabel())
                 && !promiseIconAddedForId(sessionInfo.getSessionId())
-                && new PackageManagerHelper(mAppContext).getApplicationInfo(
-                        sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
+                && !new PackageManagerHelper(mAppContext).isAppInstalled(
+                        sessionInfo.getAppPackageName(), getUserHandle(sessionInfo))) {
             ItemInstallQueue.INSTANCE.get(mAppContext)
                     .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 90285c4..56438d0 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,6 +40,8 @@
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.InsettableFrameLayout;
@@ -82,6 +84,8 @@
     private final Rect mStartRect = new Rect();
     private final Rect mEndRect = new Rect();
 
+    private Runnable mOnCloseCallback = () -> { };
+
     public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mInflater = LayoutInflater.from(context);
@@ -555,6 +559,14 @@
         mDeferContainerRemoval = false;
         getPopupContainer().removeView(this);
         getPopupContainer().removeView(mArrow);
+        mOnCloseCallback.run();
+    }
+
+    /**
+     * Callback to be called when the popup is closed
+     */
+    public void setOnCloseCallback(@NonNull Runnable callback) {
+        mOnCloseCallback = callback;
     }
 
     protected BaseDragLayer getPopupContainer() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 59930ff..a1ba747 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -206,6 +206,7 @@
                         .filter(Objects::nonNull)
                         .collect(Collectors.toList()));
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
+        container.requestFocus();
         return container;
     }
 
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index a91303a..a354169 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -35,14 +35,14 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.launcher3.util.SettingsCache;
 
 /**
  * A {@link Preference} for indicating notification dots status.
  * Also has utility methods for updating UI based on dots status changes.
  */
 public class NotificationDotsPreference extends Preference
-        implements SecureSettingsObserver.OnChangeListener {
+        implements SettingsCache.OnChangeListener {
 
     private boolean mWidgetFrameVisible = false;
 
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 922425f..ac8dac5 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,13 +18,13 @@
 
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
+import static com.android.launcher3.util.SettingsCache.NOTIFICATION_ENABLED_LISTENERS;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
-import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
 import android.content.SharedPreferences;
 import android.os.Bundle;
-import android.provider.Settings;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
@@ -45,8 +45,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.util.SecureSettingsObserver;
 
 /**
  * Settings activity for Launcher. Currently implements the following setting: Allow rotation
@@ -59,8 +59,6 @@
     private static final String FLAGS_PREFERENCE_KEY = "flag_toggler";
 
     private static final String NOTIFICATION_DOTS_PREFERENCE_KEY = "pref_icon_badging";
-    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
-    private static final String NOTIFICATION_ENABLED_LISTENERS = "enabled_notification_listeners";
 
     public static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
     public static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
@@ -126,10 +124,11 @@
      */
     public static class LauncherSettingsFragment extends PreferenceFragmentCompat {
 
-        private SecureSettingsObserver mNotificationDotsObserver;
+        private SettingsCache mSettingsCache;
 
         private String mHighLightKey;
         private boolean mPreferenceHighlighted = false;
+        private NotificationDotsPreference mNotificationSettingsChangedListener;
 
         @Override
         public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -177,14 +176,16 @@
                     }
 
                     // Listen to system notification dot settings while this UI is active.
-                    mNotificationDotsObserver = newNotificationSettingsObserver(
-                            getActivity(), (NotificationDotsPreference) preference);
-                    mNotificationDotsObserver.register();
+                    mSettingsCache = SettingsCache.INSTANCE.get(getActivity());
+                    mNotificationSettingsChangedListener =
+                            ((NotificationDotsPreference) preference);
+                    mSettingsCache.register(NOTIFICATION_BADGING_URI,
+                            (NotificationDotsPreference) mNotificationSettingsChangedListener);
                     // Also listen if notification permission changes
-                    mNotificationDotsObserver.getResolver().registerContentObserver(
-                            Settings.Secure.getUriFor(NOTIFICATION_ENABLED_LISTENERS), false,
-                            mNotificationDotsObserver);
-                    mNotificationDotsObserver.dispatchOnChange();
+                    mSettingsCache.register(NOTIFICATION_ENABLED_LISTENERS,
+                            mNotificationSettingsChangedListener);
+                    mSettingsCache.dispatchOnChange(NOTIFICATION_BADGING_URI);
+                    mSettingsCache.dispatchOnChange(NOTIFICATION_ENABLED_LISTENERS);
                     return true;
 
                 case ALLOW_ROTATION_PREFERENCE_KEY:
@@ -251,9 +252,11 @@
 
         @Override
         public void onDestroy() {
-            if (mNotificationDotsObserver != null) {
-                mNotificationDotsObserver.unregister();
-                mNotificationDotsObserver = null;
+            if (mSettingsCache != null) {
+                mSettingsCache.unregister(NOTIFICATION_BADGING_URI,
+                        mNotificationSettingsChangedListener);
+                mSettingsCache.unregister(NOTIFICATION_ENABLED_LISTENERS,
+                        mNotificationSettingsChangedListener);
             }
             super.onDestroy();
         }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 601e117..7abb653 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -150,8 +150,8 @@
 
     private void handleDeferredResume() {
         if (hasBeenResumed() && !getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)) {
-            onDeferredResumed();
             addActivityFlags(ACTIVITY_STATE_DEFERRED_RESUMED);
+            onDeferredResumed();
 
             mDeferredResumePending = false;
         } else {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 25ecea5..31adc08 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -124,7 +124,7 @@
     protected abstract boolean canInterceptTouch(MotionEvent ev);
 
     @Override
-    public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
@@ -193,6 +193,8 @@
                 : reachedToState ? mToState : mFromState;
         LauncherState newToState = getTargetState(newFromState, isDragTowardPositive);
 
+        onReinitToState(newToState);
+
         if (newFromState == mFromState && newToState == mToState || (newFromState == newToState)) {
             return false;
         }
@@ -231,6 +233,12 @@
         return true;
     }
 
+    protected void onReinitToState(LauncherState newToState) {
+    }
+
+    protected void onReachedFinalState(LauncherState newToState) {
+    }
+
     protected boolean goingBetweenNormalAndOverview(LauncherState fromState,
             LauncherState toState) {
         return (fromState == NORMAL || fromState == OVERVIEW)
@@ -527,6 +535,7 @@
             mAtomicComponentsController.getAnimationPlayer().end();
             mAtomicComponentsController = null;
         }
+        onReachedFinalState(mToState);
         clearState();
         boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
         if (shouldGoToTargetState) {
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 2647d6f..098d90d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
@@ -272,6 +273,7 @@
                         Toast.LENGTH_SHORT).show();
             }
         }
+        launcher.getStatsLogManager().logger().withItemInfo(itemInfo).log(LAUNCHER_APP_LAUNCH_TAP);
     }
 
     private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 3b7bcc2..d0e8bb1 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
@@ -182,20 +183,30 @@
         }
 
         /** Creates and up-to-date DisplayController.Info for the given context. */
+        @Nullable
         public Info createInfoForContext(Context context) {
-            Display display = Utilities.ATLEAST_R
-                    ? context.getDisplay()
-                    : context
-                        .getSystemService(DisplayManager.class)
-                        .getDisplay(mId);
-            return display == null
-                    ? new Info(context)
-                    : new Info(context, display);
+            Display display = Utilities.ATLEAST_R ? context.getDisplay() : null;
+            if (display == null) {
+                display = context.getSystemService(DisplayManager.class).getDisplay(mId);
+            }
+            if (display == null) {
+                return null;
+            }
+            // Refresh the Context the prevent stale DisplayMetrics.
+            Context displayContext = context.getApplicationContext().createDisplayContext(display);
+            return new Info(displayContext, display);
+        }
+
+        public Context getDisplayContext() {
+            return mDisplayContext;
         }
 
         protected void handleOnChange() {
             Info oldInfo = mInfo;
             Info newInfo = createInfoForContext(mDisplayContext);
+            if (newInfo == null) {
+                return;
+            }
 
             int change = 0;
             if (newInfo.hasDifferentSize(oldInfo)) {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 7b26427..08ec591 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -92,6 +92,14 @@
     }
 
     /**
+     * Returns whether the target app is installed for a given user
+     */
+    public boolean isAppInstalled(String packageName, UserHandle user) {
+        ApplicationInfo info = getApplicationInfo(packageName, user, 0);
+        return info != null;
+    }
+
+    /**
      * Returns the application info for the provided package or null
      */
     public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
@@ -105,7 +113,7 @@
     }
 
     public boolean isSafeMode() {
-        return mContext.getPackageManager().isSafeMode();
+        return mPm.isSafeMode();
     }
 
     public Intent getAppLaunchIntent(String pkg, UserHandle user) {
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
deleted file mode 100644
index 9fe72ad..0000000
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ /dev/null
@@ -1,104 +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.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-/**
- * Utility class to listen for secure settings changes
- */
-public class SecureSettingsObserver extends ContentObserver {
-
-    /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
-    public static final String NOTIFICATION_BADGING = "notification_badging";
-    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
-    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
-    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
-    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
-            "swipe_bottom_to_notification_enabled";
-
-    private final ContentResolver mResolver;
-    private final String mKeySetting;
-    private final int mDefaultValue;
-    private final OnChangeListener mOnChangeListener;
-
-    public SecureSettingsObserver(ContentResolver resolver, OnChangeListener listener,
-            String keySetting, int defaultValue) {
-        super(new Handler());
-
-        mResolver = resolver;
-        mOnChangeListener = listener;
-        mKeySetting = keySetting;
-        mDefaultValue = defaultValue;
-    }
-
-    @Override
-    public void onChange(boolean selfChange) {
-        mOnChangeListener.onSettingsChanged(getValue());
-    }
-
-    public boolean getValue() {
-        return Settings.Secure.getInt(mResolver, mKeySetting, mDefaultValue) == 1;
-    }
-
-    public void register() {
-        mResolver.registerContentObserver(Settings.Secure.getUriFor(mKeySetting), false, this);
-    }
-
-    public ContentResolver getResolver() {
-        return mResolver;
-    }
-
-    public void dispatchOnChange() {
-        onChange(true);
-    }
-
-    public void unregister() {
-        mResolver.unregisterContentObserver(this);
-    }
-
-    public interface OnChangeListener {
-        void onSettingsChanged(boolean isEnabled);
-    }
-
-    public static SecureSettingsObserver newNotificationSettingsObserver(Context context,
-            OnChangeListener listener) {
-        return new SecureSettingsObserver(
-                context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
-    }
-
-    public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
-            OnChangeListener listener) {
-        return new SecureSettingsObserver(
-                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 0);
-    }
-
-    /**
-     * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
-     * preference.
-     */
-    public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
-            OnChangeListener listener) {
-        return new SecureSettingsObserver(
-                context.getContentResolver(), listener,
-                ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
-    }
-}
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
new file mode 100644
index 0000000..22b4d38
--- /dev/null
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * ContentObserver over Settings keys that also has a caching layer.
+ * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
+ * {@link #unregister(Uri, OnChangeListener)} methods.
+ *
+ * This can be used as a normal cache without any listeners as well via the
+ * {@link #getValue(Uri, int)} and {@link #dispatchOnChange(Uri)} to update (and subsequently call
+ * get)
+ *
+ * The cache will be invalidated/updated through the normal
+ * {@link ContentObserver#onChange(boolean)} calls
+ * or can be force updated by calling {@link #dispatchOnChange(Uri)}.
+ *
+ * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
+ */
+public class SettingsCache extends ContentObserver {
+
+    /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
+    public static final Uri NOTIFICATION_BADGING_URI =
+            Settings.Secure.getUriFor("notification_badging");
+    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+            "swipe_bottom_to_notification_enabled";
+    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+    public static final Uri NOTIFICATION_ENABLED_LISTENERS =
+            Settings.Secure.getUriFor("enabled_notification_listeners");
+    public static final Uri ROTATION_SETTING_URI =
+            Settings.System.getUriFor(ACCELEROMETER_ROTATION);
+
+    private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
+
+    /**
+     * Caches the last seen value for registered keys.
+     */
+    private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+    protected final ContentResolver mResolver;
+
+
+    /**
+     * Singleton instance
+     */
+    public static MainThreadInitializedObject<SettingsCache> INSTANCE =
+            new MainThreadInitializedObject<>(SettingsCache::new);
+
+    private SettingsCache(Context context) {
+        super(new Handler());
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        // We use default of 1, but if we're getting an onChange call, can assume a non-default
+        // value will exist
+        boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
+        if (!mListenerMap.containsKey(uri)) {
+            return;
+        }
+
+        for (OnChangeListener listener : mListenerMap.get(uri)) {
+            listener.onSettingsChanged(newVal);
+        }
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * {@link #updateValue(Uri, int)} to fetch.
+     */
+    public boolean getValue(Uri keySetting, int defaultValue) {
+        if (mKeyCache.containsKey(keySetting)) {
+            return mKeyCache.get(keySetting);
+        } else {
+            return updateValue(keySetting, defaultValue);
+        }
+    }
+
+    /**
+     * Does not de-dupe if you add same listeners for the same key multiple times.
+     * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
+     */
+    public void register(Uri uri, OnChangeListener changeListener) {
+        if (mListenerMap.containsKey(uri)) {
+            mListenerMap.get(uri).add(changeListener);
+        } else {
+            CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
+            l.add(changeListener);
+            mListenerMap.put(uri, l);
+            mResolver.registerContentObserver(uri, false, this);
+        }
+    }
+
+    private boolean updateValue(Uri keyUri, int defaultValue) {
+        String key = keyUri.getLastPathSegment();
+        boolean newVal;
+        if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
+            newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
+        } else { // SETTING_SECURE
+            newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
+        }
+
+        mKeyCache.put(keyUri, newVal);
+        return newVal;
+    }
+
+    /**
+     * Force update a change for a given URI and have all listeners for that URI receive callbacks
+     * even if the value is unchanged.
+     */
+    public void dispatchOnChange(Uri uri) {
+        onChange(true, uri);
+    }
+
+    /**
+     * Call to stop receiving updates on the given {@param listener}.
+     * This Uri/Listener pair must correspond to the same pair called with for
+     * {@link #register(Uri, OnChangeListener)}
+     */
+    public void unregister(Uri uri, OnChangeListener listener) {
+        List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
+        if (!listenersToRemoveFrom.contains(listener)) {
+            return;
+        }
+
+        listenersToRemoveFrom.remove(listener);
+        if (listenersToRemoveFrom.isEmpty()) {
+            mListenerMap.remove(uri);
+        }
+    }
+
+    /**
+     * Don't use this. Ever.
+     * @param keyCache Cache to replace {@link #mKeyCache}
+     */
+    @VisibleForTesting
+    void setKeyCache(Map<Uri, Boolean> keyCache) {
+        mKeyCache = keyCache;
+    }
+
+    public interface OnChangeListener {
+        void onSettingsChanged(boolean isEnabled);
+    }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index ae459e1..505c6ce 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -17,7 +17,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.view.ContextThemeWrapper;
+import android.graphics.Rect;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile;
@@ -49,6 +49,23 @@
         return null;
     }
 
+    default Rect getFolderBoundingBox() {
+        return getDeviceProfile().getAbsoluteOpenFolderBounds();
+    }
+
+    /**
+     * After calling {@link #getFolderBoundingBox()}, we calculate a (left, top) position for a
+     * Folder of size width x height to be within those bounds. However, the chosen position may
+     * not be visually ideal (e.g. uncanny valley of centeredness), so here's a chance to update it.
+     * @param inOutPosition A 2-size array where the first element is the left position of the open
+     *     folder and the second element is the top position. Should be updated in place if desired.
+     * @param bounds The bounds that the open folder should fit inside.
+     * @param width The width of the open folder.
+     * @param height The height of the open folder.
+     */
+    default void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
+    }
+
     /**
      * The root view to support drag-and-drop and popup support.
      */
@@ -56,10 +73,10 @@
 
     DeviceProfile getDeviceProfile();
 
-    static ActivityContext lookupContext(Context context) {
+    static <T extends ActivityContext> T lookupContext(Context context) {
         if (context instanceof ActivityContext) {
-            return (ActivityContext) context;
-        } else if (context instanceof ContextThemeWrapper) {
+            return (T) context;
+        } else if (context instanceof ContextWrapper) {
             return lookupContext(((ContextWrapper) context).getBaseContext());
         } else {
             throw new IllegalArgumentException("Cannot find ActivityContext in parent tree");
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 15f7730..1939d15 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -206,15 +206,19 @@
 
     protected boolean findActiveController(MotionEvent ev) {
         mActiveController = null;
-        if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
-                | TOUCH_DISPATCHING_FROM_PROXY)) == 0) {
-            // Only look for controllers if we are not dispatching from gesture area and proxy is
-            // not active
+        if (canFindActiveController()) {
             mActiveController = findControllerToHandleTouch(ev);
         }
         return mActiveController != null;
     }
 
+    protected boolean canFindActiveController() {
+        // Only look for controllers if we are not dispatching from gesture area and proxy is
+        // not active
+        return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION
+                | TOUCH_DISPATCHING_FROM_PROXY)) == 0;
+    }
+
     @Override
     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
         // Shortcuts can appear above folder
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index addaf9c..899dcf7 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -118,7 +118,8 @@
         mTargetRect.roundOut(outPos);
     }
 
-    public static void show(Launcher launcher, RectF targetRect, List<OptionItem> items) {
+    public static OptionsPopupView show(
+            Launcher launcher, RectF targetRect, List<OptionItem> items) {
         OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater()
                 .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false);
         popup.mTargetRect = targetRect;
@@ -134,6 +135,7 @@
             popup.mItemMap.put(view, item);
         }
         popup.show();
+        return popup;
     }
 
     @VisibleForTesting
@@ -156,6 +158,14 @@
      */
     public static ArrayList<OptionItem> getOptions(Launcher launcher) {
         ArrayList<OptionItem> options = new ArrayList<>();
+        options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
+                LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
+                OptionsPopupView::startSettings));
+        if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+            options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
+                    LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
+                    OptionsPopupView::onWidgetsClicked));
+        }
         int resString = Utilities.existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
         int resDrawable = Utilities.existsStyleWallpapers(launcher) ?
@@ -163,15 +173,6 @@
         options.add(new OptionItem(resString, resDrawable,
                 IGNORE,
                 OptionsPopupView::startWallpaperPicker));
-        if (!WidgetsModel.GO_DISABLE_WIDGETS) {
-            options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget,
-                    LAUNCHER_WIDGETSTRAY_BUTTON_TAP_OR_LONGPRESS,
-                    OptionsPopupView::onWidgetsClicked));
-        }
-        options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting,
-                LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
-                OptionsPopupView::startSettings));
-
         return options;
     }
 
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index a38e90d..15566a4 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -99,12 +100,16 @@
             return false;
         }
 
+        PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
+        if (v instanceof LivePreviewWidgetCell) {
+            dragHelper.setPreview(((LivePreviewWidgetCell) v).getPreview());
+        }
+
         int[] loc = new int[2];
         getPopupContainer().getLocationInDragLayer(image, loc);
 
-        new PendingItemDragHelper(v).startDrag(
-                image.getBitmapBounds(), image.getBitmap().getWidth(), image.getWidth(),
-                new Point(loc[0], loc[1]), this, new DragOptions());
+        dragHelper.startDrag(image.getBitmapBounds(), image.getBitmap().getWidth(),
+                image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
         close(true);
         return true;
     }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 3c11274..8fe42f4 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -25,6 +25,8 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.Launcher;
@@ -48,14 +50,14 @@
     private final PendingAddItemInfo mAddInfo;
     private int[] mEstimatedCellSize;
 
-    private RemoteViews mPreview;
+    @Nullable private RemoteViews mPreview;
 
     public PendingItemDragHelper(View view) {
         super(view);
         mAddInfo = (PendingAddItemInfo) view.getTag();
     }
 
-    public void setPreview(RemoteViews preview) {
+    public void setPreview(@Nullable RemoteViews preview) {
         mPreview = preview;
     }
 
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 3585a18..223cda2 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.PackageUserKey;
@@ -136,8 +137,8 @@
     }
 
     protected WidgetCell addItemCell(ViewGroup parent) {
-        WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext()).inflate(
-                R.layout.widget_cell, parent, false);
+        LivePreviewWidgetCell widget = (LivePreviewWidgetCell) LayoutInflater.from(
+                getContext()).inflate(R.layout.live_preview_widget_cell, parent, false);
 
         widget.setOnClickListener(this);
         widget.setOnLongClickListener(this);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 03623d5..81cd73a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -22,16 +22,22 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.graphics.Rect;
+import android.os.Process;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
@@ -43,30 +49,39 @@
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.workprofile.PersonalWorkPagedView;
+import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
+
+import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Popup for showing the full list of available widgets
  */
 public class WidgetsFullSheet extends BaseWidgetSheet
-        implements Insettable, ProviderChangedListener {
+        implements Insettable, ProviderChangedListener, OnActivePageChangedListener {
 
     private static final long DEFAULT_OPEN_DURATION = 267;
     private static final long FADE_IN_DURATION = 150;
     private static final float VERTICAL_START_POSITION = 0.3f;
 
     private final Rect mInsets = new Rect();
+    private final boolean mHasWorkProfile;
+    private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
+    private final UserHandle mCurrentUser = Process.myUserHandle();
+    private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
+            mCurrentUser.equals(entry.mPkgItem.user);
+    private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
+            mPrimaryWidgetsFilter.negate();
 
-    private final WidgetsListAdapter mAdapter;
-
-    private WidgetsRecyclerView mRecyclerView;
+    @Nullable private PersonalWorkPagedView mViewPager;
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        LauncherAppState apps = LauncherAppState.getInstance(context);
-        mAdapter = new WidgetsListAdapter(context,
-                LayoutInflater.from(context), apps.getWidgetCache(), apps.getIconCache(),
-                this, this);
-
+        mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
+        mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
+        mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
     }
 
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -77,25 +92,51 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mContent = findViewById(R.id.container);
-
-        mRecyclerView = findViewById(R.id.widgets_list_view);
-        mRecyclerView.setAdapter(mAdapter);
-        mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
-
         TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
-        springLayout.addSpringView(R.id.widgets_list_view);
-        mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
+
+        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+        int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
+                : R.layout.widgets_full_sheet_recyclerview;
+        layoutInflater.inflate(contentLayoutRes,  springLayout, true);
+
+        if (mHasWorkProfile) {
+            mViewPager = findViewById(R.id.widgets_view_pager);
+            // Temporarily disable swipe gesture until widgets list horizontal scrollviews per
+            // app are replaced by gird views.
+            mViewPager.setSwipeGestureEnabled(false);
+            mViewPager.initParentViews(this);
+            mViewPager.getPageIndicator().setOnActivePageChangedListener(this);
+            mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.PRIMARY);
+            findViewById(R.id.tab_personal)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(0));
+            findViewById(R.id.tab_work)
+                    .setOnClickListener((View view) -> mViewPager.snapToPage(1));
+            springLayout.addSpringView(R.id.primary_widgets_list_view);
+            springLayout.addSpringView(R.id.work_widgets_list_view);
+        } else {
+            mViewPager = null;
+            springLayout.addSpringView(R.id.primary_widgets_list_view);
+        }
+
         onWidgetsBound();
     }
 
+    @Override
+    public void onActivePageChanged(int currentActivePage) {
+        mAdapters.get(currentActivePage).mWidgetsRecyclerView.bindFastScrollbar();
+    }
+
     @VisibleForTesting
     public WidgetsRecyclerView getRecyclerView() {
-        return mRecyclerView;
+        if (!mHasWorkProfile || mViewPager.getCurrentPage() == AdapterHolder.PRIMARY) {
+            return mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
+        }
+        return mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView;
     }
 
     @Override
     protected Pair<View, String> getAccessibilityTarget() {
-        return Pair.create(mRecyclerView, getContext().getString(
+        return Pair.create(getRecyclerView(), getContext().getString(
                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
     }
 
@@ -116,9 +157,10 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
 
-        mRecyclerView.setPadding(
-                mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(),
-                mRecyclerView.getPaddingRight(), insets.bottom);
+        setBottomPadding(mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView, insets.bottom);
+        if (mHasWorkProfile) {
+            setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
+        }
         if (insets.bottom > 0) {
             setupNavBarColor();
         } else {
@@ -129,6 +171,14 @@
         requestLayout();
     }
 
+    private void setBottomPadding(RecyclerView recyclerView, int bottomPadding) {
+        recyclerView.setPadding(
+                recyclerView.getPaddingLeft(),
+                recyclerView.getPaddingTop(),
+                recyclerView.getPaddingRight(),
+                bottomPadding);
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int widthUsed;
@@ -168,7 +218,17 @@
 
     @Override
     public void onWidgetsBound() {
-        mAdapter.setWidgets(mLauncher.getPopupDataProvider().getAllWidgets());
+        List<WidgetsListBaseEntry> allWidgets = mLauncher.getPopupDataProvider().getAllWidgets();
+
+        AdapterHolder primaryUserAdapterHolder = mAdapters.get(AdapterHolder.PRIMARY);
+        primaryUserAdapterHolder.setup(findViewById(R.id.primary_widgets_list_view));
+        primaryUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+        if (mHasWorkProfile) {
+            AdapterHolder workUserAdapterHolder = mAdapters.get(AdapterHolder.WORK);
+            workUserAdapterHolder.setup(findViewById(R.id.work_widgets_list_view));
+            workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
+            onActivePageChanged(mViewPager.getCurrentPage());
+        }
     }
 
     private void open(boolean animate) {
@@ -183,12 +243,9 @@
                     .setDuration(DEFAULT_OPEN_DURATION)
                     .setInterpolator(AnimationUtils.loadInterpolator(
                             getContext(), android.R.interpolator.linear_out_slow_in));
-            mRecyclerView.setLayoutFrozen(true);
             mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    mRecyclerView.setLayoutFrozen(false);
-                    mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
                     mOpenCloseAnimator.removeListener(this);
                 }
             });
@@ -198,7 +255,6 @@
             });
         } else {
             setTranslationShift(TRANSLATION_SHIFT_OPENED);
-            mAdapter.setApplyBitmapDeferred(false, mRecyclerView);
             post(this::announceAccessibilityChanges);
         }
     }
@@ -218,12 +274,12 @@
         // Disable swipe down when recycler view is scrolling
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = false;
-            RecyclerViewFastScroller scroller = mRecyclerView.getScrollbar();
+            RecyclerViewFastScroller scroller = getRecyclerView().getScrollbar();
             if (scroller.getThumbOffsetY() >= 0
                     && getPopupContainer().isEventOverView(scroller, ev)) {
                 mNoIntercept = true;
             } else if (getPopupContainer().isEventOverView(mContent, ev)) {
-                mNoIntercept = !mRecyclerView.shouldContainerScroll(ev, getPopupContainer());
+                mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
             }
         }
         return super.onControllerInterceptTouchEvent(ev);
@@ -242,14 +298,14 @@
     /** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
     @VisibleForTesting
     public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
-        return launcher.findViewById(R.id.widgets_list_view);
+        return launcher.findViewById(R.id.primary_widgets_list_view);
     }
 
     @Override
     public void addHintCloseAnim(
             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
-        target.setFloat(mRecyclerView, VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
-        target.setViewAlpha(mRecyclerView, 0.5f, interpolator);
+        target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
+        target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
     }
 
     @Override
@@ -257,4 +313,39 @@
         super.onCloseComplete();
         AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
     }
+
+    /** A holder class for holding adapters & their corresponding recycler view. */
+    private final class AdapterHolder {
+        static final int PRIMARY = 0;
+        static final int WORK = 1;
+
+        private final int mAdapterType;
+        private final WidgetsListAdapter mWidgetsListAdapter;
+
+        private WidgetsRecyclerView mWidgetsRecyclerView;
+
+        AdapterHolder(int adapterType) {
+            mAdapterType = adapterType;
+
+            Context context = getContext();
+            LauncherAppState apps = LauncherAppState.getInstance(context);
+            mWidgetsListAdapter = new WidgetsListAdapter(
+                    context,
+                    LayoutInflater.from(context),
+                    apps.getWidgetCache(),
+                    apps.getIconCache(),
+                    /* iconClickListener= */ WidgetsFullSheet.this,
+                    /* iconLongClickListener= */ WidgetsFullSheet.this);
+            mWidgetsListAdapter.setFilter(
+                    mAdapterType == PRIMARY ? mPrimaryWidgetsFilter : mWorkWidgetsFilter);
+        }
+
+        void setup(WidgetsRecyclerView recyclerView) {
+            mWidgetsRecyclerView = recyclerView;
+            mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+            mWidgetsRecyclerView.setEdgeEffectFactory(
+                    ((TopRoundedCornerView) mContent).createEdgeEffectFactory());
+            mWidgetsListAdapter.setApplyBitmapDeferred(false, mWidgetsRecyclerView);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 5ec7f3b..72b4a02 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -43,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
@@ -74,6 +75,11 @@
     private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
     @Nullable private String mWidgetsContentVisiblePackage = null;
 
+    private Predicate<WidgetsListBaseEntry> mHeaderAndSelectedContentFilter = entry ->
+            entry instanceof WidgetsListHeaderEntry
+                    || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage);
+    @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
+
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
             WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
             OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
@@ -85,6 +91,10 @@
                 new WidgetsListHeaderViewHolderBinder(layoutInflater, this::onHeaderClicked));
     }
 
+    public void setFilter(Predicate<WidgetsListBaseEntry> filter) {
+        mFilter = filter;
+    }
+
     /**
      * Defers applying bitmap on all the {@link WidgetCell} in the {@param rv}.
      *
@@ -132,8 +142,8 @@
             }
         });
         List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
-                .filter(entry -> entry instanceof WidgetsListHeaderEntry
-                        || entry.mPkgItem.packageName.equals(mWidgetsContentVisiblePackage))
+                .filter(entry -> (mFilter == null || mFilter.test(entry))
+                        && mHeaderAndSelectedContentFilter.test(entry))
                 .collect(Collectors.toList());
         mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator);
     }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
index cec6b80..bd78777 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListRowViewHolderBinder.java
@@ -25,6 +25,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.widget.WidgetCell;
@@ -107,8 +108,8 @@
                     mLayoutInflater.inflate(R.layout.widget_list_divider, row);
                 } else {
                     // Add cell for even index
-                    WidgetCell widget = (WidgetCell) mLayoutInflater.inflate(
-                            R.layout.widget_cell, row, false);
+                    LivePreviewWidgetCell widget = (LivePreviewWidgetCell) mLayoutInflater.inflate(
+                            R.layout.live_preview_widget_cell, row, false);
 
                     // set up touch.
                     widget.setOnClickListener(mIconClickListener);
@@ -124,7 +125,8 @@
 
         // Bind the view in the widget horizontal tray region.
         for (int i = 0; i < infoList.size(); i++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
+            LivePreviewWidgetCell widget = (LivePreviewWidgetCell) row.getChildAt(2 * i);
+            widget.reset();
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
             widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
             widget.ensurePreview();
diff --git a/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java b/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java
new file mode 100644
index 0000000..8b05a0d
--- /dev/null
+++ b/src/com/android/launcher3/workprofile/PersonalWorkPagedView.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.workprofile;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.PagedView;
+
+/**
+ *  A {@link PagedView} for showing different views for the personal and work profile respectively.
+ */
+public class PersonalWorkPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
+
+    static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+    static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+    static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
+
+    public PersonalWorkPagedView(Context context) {
+        this(context, null);
+    }
+
+    public PersonalWorkPagedView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PersonalWorkPagedView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected String getCurrentPageDescription() {
+        // Not necessary, tab-bar already has two tabs with their own descriptions.
+        return "";
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mPageIndicator.setScroll(l, mMaxScroll);
+    }
+
+    @Override
+    protected void determineScrollingStart(MotionEvent ev) {
+        float absDeltaX = Math.abs(ev.getX() - getDownMotionX());
+        float absDeltaY = Math.abs(ev.getY() - getDownMotionY());
+
+        if (Float.compare(absDeltaX, 0f) == 0) return;
+
+        float slope = absDeltaY / absDeltaX;
+        float theta = (float) Math.atan(slope);
+
+        if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
+            cancelCurrentPageLongPress();
+        }
+
+        if (theta > MAX_SWIPE_ANGLE) {
+            return;
+        } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
+            theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
+            float extraRatio = (float)
+                    Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
+            super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
+        } else {
+            super.determineScrollingStart(ev);
+        }
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    @Override
+    protected boolean canScroll(float absVScroll, float absHScroll) {
+        return (absHScroll > absVScroll) && super.canScroll(absVScroll, absHScroll);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
similarity index 85%
rename from src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
rename to src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
index 2de425e..3a3028f 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.allapps;
+package com.android.launcher3.workprofile;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -35,9 +35,6 @@
  * Supports two indicator colors, dedicated for personal and work tabs.
  */
 public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageIndicator {
-    private static final int POSITION_PERSONAL = 0;
-    private static final int POSITION_WORK = 1;
-
     private final Paint mSelectedIndicatorPaint;
     private final Paint mDividerPaint;
 
@@ -47,7 +44,7 @@
     private float mScrollOffset;
     private int mSelectedPosition = 0;
 
-    private AllAppsContainerView mContainerView;
+    private OnActivePageChangedListener mOnActivePageChangedListener;
     private int mLastActivePage = 0;
     private boolean mIsRtl;
 
@@ -123,7 +120,7 @@
         float y = getHeight() - mDividerPaint.getStrokeWidth();
         canvas.drawLine(getPaddingLeft(), y, getWidth() - getPaddingRight(), y, mDividerPaint);
         canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
-            mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
+                mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
     }
 
     @Override
@@ -135,15 +132,15 @@
     @Override
     public void setActiveMarker(int activePage) {
         updateTabTextColor(activePage);
-        if (mContainerView != null && mLastActivePage != activePage) {
+        if (mOnActivePageChangedListener != null && mLastActivePage != activePage) {
             updateIndicatorPosition(activePage);
-            mContainerView.onTabChanged(activePage);
+            mOnActivePageChangedListener.onActivePageChanged(activePage);
         }
         mLastActivePage = activePage;
     }
 
-    public void setContainerView(AllAppsContainerView containerView) {
-        mContainerView = containerView;
+    public void setOnActivePageChangedListener(OnActivePageChangedListener listener) {
+        mOnActivePageChangedListener = listener;
     }
 
     @Override
@@ -153,4 +150,12 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    /**
+     * Interface definition for a callback to be invoked when an active page has been changed.
+     */
+    public interface OnActivePageChangedListener {
+        /** Called when the active page has been changed. */
+        void onActivePageChanged(int currentActivePage);
+    }
 }
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 30c9b5f..a7e3472 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -9,7 +9,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
 
@@ -115,7 +114,7 @@
                 widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
                 updatedItems.add(info);
             }
-            setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
+            setWidgetsAndShortcuts(widgetsAndShortcuts, app);
         } catch (Exception e) {
             if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
                 // the returned value may be incomplete and will not be refreshed until the next
@@ -132,52 +131,28 @@
     }
 
     private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
-            LauncherAppState app, @Nullable PackageUserKey packageUser) {
+            LauncherAppState app) {
         if (DEBUG) {
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
 
         // Temporary list for {@link PackageItemInfos} to avoid having to go through
         // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
-        HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
+        HashMap<PackageUserKey, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();
 
         // clear the lists.
-        if (packageUser == null) {
-            mWidgetsList.clear();
-        } else {
-            PackageItemInfo packageItem = mWidgetsList.keySet()
-                    .stream()
-                    .filter(item -> item.packageName.equals(packageUser.mPackageName))
-                    .findFirst()
-                    .orElse(null);
-            if (packageItem != null) {
-                // We want to preserve the user that was on the packageItem previously,
-                // so add it to tmpPackageItemInfos here to avoid creating a new entry.
-                tmpPackageItemInfos.put(packageItem.packageName, packageItem);
-
-                // Add the widgets for other users in the rawList as it only contains widgets for
-                // packageUser
-                List<WidgetItem> otherUserItems = mWidgetsList.remove(packageItem);
-                otherUserItems.removeIf(w -> w.user.equals(packageUser.mUser));
-                rawWidgetsShortcuts.addAll(otherUserItems);
-            }
-        }
-
-        UserHandle myUser = Process.myUserHandle();
-
+        mWidgetsList.clear();
         // add and update.
         mWidgetsList.putAll(rawWidgetsShortcuts.stream()
                 .filter(new WidgetValidityCheck(app))
                 .collect(Collectors.groupingBy(item -> {
-                    String packageName = item.componentName.getPackageName();
-                    PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+                    PackageUserKey packageUserKey = new PackageUserKey(
+                            item.componentName.getPackageName(), item.user);
+                    PackageItemInfo pInfo = tmpPackageItemInfos.get(packageUserKey);
                     if (pInfo == null) {
-                        pInfo = new PackageItemInfo(packageName);
+                        pInfo = new PackageItemInfo(packageUserKey.mPackageName);
                         pInfo.user = item.user;
-                        tmpPackageItemInfos.put(packageName,  pInfo);
-                    } else if (!myUser.equals(pInfo.user)) {
-                        // Keep updating the user, until we get the primary user.
-                        pInfo.user = item.user;
+                        tmpPackageItemInfos.put(packageUserKey,  pInfo);
                     }
                     return pInfo;
                 })));
diff --git a/tests/Android.mk b/tests/Android.mk
index 3d9077d..43d51fc 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -32,7 +32,6 @@
 
     LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \
         ../src/com/android/launcher3/ResourceUtils.java \
-        ../src/com/android/launcher3/util/SecureSettingsObserver.java \
         ../src/com/android/launcher3/testing/TestProtocol.java
 endif
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0c8f610..6afadfa 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -153,7 +153,7 @@
     private static final String WORKSPACE_RES_ID = "workspace";
     private static final String APPS_RES_ID = "apps_view";
     private static final String OVERVIEW_RES_ID = "overview_panel";
-    private static final String WIDGETS_RES_ID = "widgets_list_view";
+    private static final String WIDGETS_RES_ID = "primary_widgets_list_view";
     private static final String CONTEXT_MENU_RES_ID = "deep_shortcuts_container";
     public static final int WAIT_TIME_MS = 10000;
     public static final int LONG_WAIT_TIME_MS = 60000;