Merge "Revert "Use GestureDetector in WorkspaceTouchListener"" into ub-launcher3-master
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index 6c50950..ff3dcef9 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -24,5 +24,15 @@
         android:id="@+id/recent_task_recycler_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:scrollbars="none"/>
+        android:scrollbars="none"
+        android:visibility="gone"/>
+    <TextView
+        android:id="@+id/recent_task_empty_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:text="@string/recents_empty_message"
+        android:textColor="@android:color/white"
+        android:textSize="25sp"
+        android:visibility="gone"/>
 </com.android.quickstep.views.IconRecentsView>
\ No newline at end of file
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
new file mode 100644
index 0000000..ee67d49
--- /dev/null
+++ b/go/quickstep/res/layout/task_item_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.TaskItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <FrameLayout
+        android:id="@+id/task_icon_and_thumbnail"
+        android:layout_width="@dimen/task_item_height"
+        android:layout_height="@dimen/task_item_height"
+        android:layout_gravity="center_vertical"
+        android:layout_marginHorizontal="8dp"
+        android:layout_marginVertical="@dimen/task_item_half_vert_margin">
+        <ImageView
+            android:id="@+id/task_thumbnail"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="top|start"/>
+        <ImageView
+            android:id="@+id/task_icon"
+            android:layout_width="@dimen/task_icon_size"
+            android:layout_height="@dimen/task_icon_size"
+            android:layout_gravity="bottom|end"/>
+    </FrameLayout>
+    <TextView
+        android:id="@+id/task_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginHorizontal="8dp"
+        android:singleLine="true"
+        android:textColor="@android:color/white"
+        android:textSize="24sp"/>
+</com.android.quickstep.views.TaskItemView>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..28cc1eb
--- /dev/null
+++ b/go/quickstep/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <dimen name="task_item_height">60dp</dimen>
+    <dimen name="task_item_half_vert_margin">8dp</dimen>
+    <dimen name="task_icon_size">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index abb9242..bba08a4 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -26,7 +26,6 @@
 
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.views.IconRecentsView;
 
@@ -110,11 +109,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(RecentsActivity activity) {
-        return activity.getDragLayer().getAlphaProperty(0);
-    }
-
-    @Override
     public int getContainerType() {
         return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
     }
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 76685f3..d55d2e5 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -22,9 +22,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.views.IconRecentsView;
 
 import java.util.function.BiPredicate;
@@ -94,11 +92,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(Launcher activity) {
-        return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
index 57cd60a..4f3d1e4 100644
--- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java
+++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java
@@ -15,15 +15,17 @@
  */
 package com.android.quickstep;
 
+import android.view.LayoutInflater;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView.Adapter;
 
+import com.android.launcher3.R;
+import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 
-import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Recycler view adapter that dynamically inflates and binds {@link TaskHolder} instances with the
@@ -34,21 +36,28 @@
     private static final int MAX_TASKS_TO_DISPLAY = 6;
     private static final String TAG = "TaskAdapter";
     private final TaskListLoader mLoader;
+    private TaskInputController mInputController;
 
     public TaskAdapter(@NonNull TaskListLoader loader) {
         mLoader = loader;
     }
 
+    public void setInputController(TaskInputController inputController) {
+        mInputController = inputController;
+    }
+
     @Override
     public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        // TODO: Swap in an actual task view here (view w/ icon, label, etc.)
-        TextView stubView = new TextView(parent.getContext());
-        return new TaskHolder(stubView);
+        TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.task_item_view, parent, false);
+        TaskHolder holder = new TaskHolder(itemView);
+        itemView.setOnClickListener(view -> mInputController.onTaskClicked(holder));
+        return holder;
     }
 
     @Override
     public void onBindViewHolder(TaskHolder holder, int position) {
-        ArrayList<Task> tasks = mLoader.getCurrentTaskList();
+        List<Task> tasks = mLoader.getCurrentTaskList();
         if (position >= tasks.size()) {
             // Task list has updated.
             return;
diff --git a/go/quickstep/src/com/android/quickstep/TaskHolder.java b/go/quickstep/src/com/android/quickstep/TaskHolder.java
index 1ea6d76..8d5e4d5 100644
--- a/go/quickstep/src/com/android/quickstep/TaskHolder.java
+++ b/go/quickstep/src/com/android/quickstep/TaskHolder.java
@@ -15,10 +15,10 @@
  */
 package com.android.quickstep;
 
-import android.widget.TextView;
-
+import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
+import com.android.quickstep.views.TaskItemView;
 import com.android.systemui.shared.recents.model.Task;
 
 /**
@@ -27,22 +27,33 @@
  */
 final class TaskHolder extends ViewHolder {
 
-    // TODO: Implement the actual task view to be held.
-    // For now, we just use a simple text view.
-    private final TextView mStubView;
+    private final TaskItemView mTaskItemView;
+    private Task mTask;
 
-    public TaskHolder(TextView stubView) {
-        super(stubView);
-        mStubView = stubView;
+    public TaskHolder(TaskItemView itemView) {
+        super(itemView);
+        mTaskItemView = itemView;
     }
 
     /**
      * Bind task content to the view. This includes the task icon and title as well as binding
      * input handlers such as which task to launch/remove.
      *
-     * @param task the task to bind to the view this
+     * @param task the task to bind to the view
      */
     public void bindTask(Task task) {
-        mStubView.setText("Stub task view: " + task.titleDescription);
+        mTask = task;
+        mTaskItemView.setLabel(task.titleDescription);
+        mTaskItemView.setIcon(task.icon);
+        mTaskItemView.setThumbnail(task.thumbnail.thumbnail);
+    }
+
+    /**
+     * Gets the task currently bound to this view
+     *
+     * @return the current task
+     */
+    public @NonNull Task getTask() {
+        return mTask;
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskInputController.java b/go/quickstep/src/com/android/quickstep/TaskInputController.java
new file mode 100644
index 0000000..d97ac8d
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskInputController.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Controller responsible for task logic that occurs on various input to the recents view.
+ */
+public final class TaskInputController {
+
+    private final TaskListLoader mLoader;
+    private final TaskAdapter mAdapter;
+
+    public TaskInputController(TaskListLoader loader,TaskAdapter adapter) {
+        mLoader = loader;
+        mAdapter = adapter;
+    }
+
+    /**
+     * Logic that occurs when a task view is tapped. Launches the respective task.
+     *
+     * @param viewHolder the task view holder that has been tapped
+     */
+    public void onTaskClicked(TaskHolder viewHolder) {
+        // TODO: Add app launch animation as part of the launch options here.
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(viewHolder.getTask().key,
+                null /* options */, null /* resultCallback */, null /* resultCallbackHandler */);
+    }
+
+    public void onTaskSwiped(TaskHolder viewHolder) {
+        int position = viewHolder.getAdapterPosition();
+        Task task = viewHolder.getTask();
+        ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+        mLoader.removeTask(task);
+        mAdapter.notifyItemRemoved(position);
+    }
+
+    // TODO: Implement "Clear all" and notify adapter that data has updated
+
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index 9f359b4..e6d1a22 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -24,6 +24,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
@@ -45,13 +46,13 @@
 
     /**
      * Returns the current task list as of the last completed load (see
-     * {@link #loadTaskList}). This list of tasks is guaranteed to always have all its task
-     * content loaded.
+     * {@link #loadTaskList}) as a read-only list. This list of tasks is guaranteed to always have
+     * all its task content loaded.
      *
      * @return the current list of tasks w/ all content loaded
      */
-    public ArrayList<Task> getCurrentTaskList() {
-        return mTaskList;
+    public List<Task> getCurrentTaskList() {
+        return Collections.unmodifiableList(mTaskList);
     }
 
     /**
@@ -85,34 +86,47 @@
     }
 
     /**
-     * Loads task content for a list of tasks, including the label and the icon. Uses the list of
-     * tasks since the last load as a cache for loaded content.
+     * Removes the task from the current task list.
+     */
+    void removeTask(Task task) {
+        mTaskList.remove(task);
+    }
+
+    /**
+     * Loads task content for a list of tasks, including the label, icon, and thumbnail. For content
+     * that isn't cached, load the content asynchronously in the background.
      *
      * @param tasksToLoad list of tasks that need to load their content
-     * @param onLoadedCallback runnable to run after all tasks have loaded their content
+     * @param onFullyLoadedCallback runnable to run after all tasks have loaded their content
      */
     private void loadTaskContents(ArrayList<Task> tasksToLoad,
-            @Nullable Runnable onLoadedCallback) {
-        AtomicInteger loadRequestsCount = new AtomicInteger(0);
+            @Nullable Runnable onFullyLoadedCallback) {
+        // Make two load requests per task, one for the icon/title and one for the thumbnail.
+        AtomicInteger loadRequestsCount = new AtomicInteger(tasksToLoad.size() * 2);
+        Runnable itemLoadedRunnable = () -> {
+            if (loadRequestsCount.decrementAndGet() == 0 && onFullyLoadedCallback != null) {
+                onFullyLoadedCallback.run();
+            }
+        };
         for (Task task : tasksToLoad) {
+            // Load icon and title.
             int index = mTaskList.indexOf(task);
             if (index >= 0) {
                 // If we've already loaded the task and have its content then just copy it over.
                 Task loadedTask = mTaskList.get(index);
                 task.titleDescription = loadedTask.titleDescription;
                 task.icon = loadedTask.icon;
+                itemLoadedRunnable.run();
             } else {
                 // Otherwise, load the content in the background.
-                loadRequestsCount.getAndIncrement();
-                mRecentsModel.getIconCache().updateIconInBackground(task, loadedTask -> {
-                    if (loadRequestsCount.decrementAndGet() == 0 && onLoadedCallback != null) {
-                        onLoadedCallback.run();
-                    }
-                });
+                mRecentsModel.getIconCache().updateIconInBackground(task,
+                        loadedTask -> itemLoadedRunnable.run());
             }
-        }
-        if (loadRequestsCount.get() == 0 && onLoadedCallback != null) {
-            onLoadedCallback.run();
+
+            // Load the thumbnail. May return immediately and synchronously if the thumbnail is
+            // cached.
+            mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,
+                    thumbnail -> itemLoadedRunnable.run());
         }
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
new file mode 100644
index 0000000..2a53917
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static androidx.recyclerview.widget.ItemTouchHelper.RIGHT;
+
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
+
+/**
+ * Callback for swipe input on {@link TaskHolder} views in the recents view.
+ */
+public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
+
+    private final TaskInputController mTaskInputController;
+
+    public TaskSwipeCallback(TaskInputController inputController) {
+        super(0 /* dragDirs */, RIGHT);
+        mTaskInputController = inputController;
+    }
+
+    @Override
+    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder,
+            ViewHolder target) {
+        return false;
+    }
+
+    @Override
+    public void onSwiped(ViewHolder viewHolder, int direction) {
+        if (direction == RIGHT) {
+            mTaskInputController.onTaskSwiped((TaskHolder) viewHolder);
+        }
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index afb0540..8c7177b 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -17,18 +17,25 @@
 
 import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.view.View;
 import android.view.ViewDebug;
 import android.widget.FrameLayout;
 
+import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
 
 import com.android.launcher3.R;
 import com.android.quickstep.TaskAdapter;
+import com.android.quickstep.TaskInputController;
 import com.android.quickstep.TaskListLoader;
+import com.android.quickstep.TaskSwipeCallback;
 
 /**
  * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
@@ -67,6 +74,7 @@
                     return ALPHA.get(view);
                 }
             };
+    private static final long CROSSFADE_DURATION = 300;
 
     /**
      * A ratio representing the view's relative placement within its padded space. For example, 0
@@ -75,26 +83,48 @@
     @ViewDebug.ExportedProperty(category = "launcher")
 
     private final Context mContext;
+    private final TaskListLoader mTaskLoader;
+    private final TaskAdapter mTaskAdapter;
+    private final TaskInputController mTaskInputController;
 
     private float mTranslationYFactor;
-    private TaskAdapter mTaskAdapter;
     private RecyclerView mTaskRecyclerView;
-    private TaskListLoader mTaskLoader;
+    private View mEmptyView;
 
     public IconRecentsView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
+        mTaskLoader = new TaskListLoader(mContext);
+        mTaskAdapter = new TaskAdapter(mTaskLoader);
+        mTaskInputController = new TaskInputController(mTaskLoader, mTaskAdapter);
+        mTaskAdapter.setInputController(mTaskInputController);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mTaskLoader = new TaskListLoader(mContext);
-        mTaskAdapter = new TaskAdapter(mTaskLoader);
-        mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
-        mTaskRecyclerView.setAdapter(mTaskAdapter);
-        mTaskRecyclerView.setLayoutManager(
-                new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+        if (mTaskRecyclerView == null) {
+            mTaskRecyclerView = findViewById(R.id.recent_task_recycler_view);
+            mTaskRecyclerView.setAdapter(mTaskAdapter);
+            mTaskRecyclerView.setLayoutManager(
+                    new LinearLayoutManager(mContext, VERTICAL, true /* reverseLayout */));
+            ItemTouchHelper helper = new ItemTouchHelper(
+                    new TaskSwipeCallback(mTaskInputController));
+            helper.attachToRecyclerView(mTaskRecyclerView);
+
+            mEmptyView = findViewById(R.id.recent_task_empty_view);
+            mTaskAdapter.registerAdapterDataObserver(new AdapterDataObserver() {
+                @Override
+                public void onChanged() {
+                    updateContentViewVisibility();
+                }
+
+                @Override
+                public void onItemRangeRemoved(int positionStart, int itemCount) {
+                    updateContentViewVisibility();
+                }
+            });
+        }
     }
 
     /**
@@ -121,4 +151,44 @@
     private float computeTranslationYForFactor(float translationYFactor) {
         return translationYFactor * (getPaddingBottom() - getPaddingTop());
     }
+
+    /**
+     * Update the content view so that the appropriate view is shown based off the current list
+     * of tasks.
+     */
+    private void updateContentViewVisibility() {
+        int taskListSize = mTaskLoader.getCurrentTaskList().size();
+        if (mEmptyView.getVisibility() != VISIBLE && taskListSize == 0) {
+            crossfadeViews(mEmptyView, mTaskRecyclerView);
+            // TODO: Go to home.
+        }
+        if (mTaskRecyclerView.getVisibility() != VISIBLE && taskListSize > 0) {
+            crossfadeViews(mTaskRecyclerView, mEmptyView);
+        }
+    }
+
+    /**
+     * Animate views so that one view fades in while the other fades out.
+     *
+     * @param fadeInView view that should fade in
+     * @param fadeOutView view that should fade out
+     */
+    private void crossfadeViews(View fadeInView, View fadeOutView) {
+        fadeInView.setVisibility(VISIBLE);
+        fadeInView.setAlpha(0f);
+        fadeInView.animate()
+                .alpha(1f)
+                .setDuration(CROSSFADE_DURATION)
+                .setListener(null);
+
+        fadeOutView.animate()
+                .alpha(0f)
+                .setDuration(CROSSFADE_DURATION)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        fadeOutView.setVisibility(GONE);
+                    }
+                });
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
new file mode 100644
index 0000000..3818965
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.R;
+
+/**
+ * View representing an individual task item with the icon + thumbnail adjacent to the task label.
+ */
+public final class TaskItemView extends LinearLayout {
+
+    private TextView mLabelView;
+    private ImageView mIconView;
+    private ImageView mThumbnailView;
+
+    public TaskItemView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mLabelView = findViewById(R.id.task_label);
+        mThumbnailView = findViewById(R.id.task_thumbnail);
+        mIconView = findViewById(R.id.task_icon);
+    }
+
+    /**
+     * Set the label for the task item.
+     *
+     * @param label task label
+     */
+    public void setLabel(String label) {
+        mLabelView.setText(label);
+    }
+
+    /**
+     * Set the icon for the task item.
+     *
+     * @param icon task icon
+     */
+    public void setIcon(Drawable icon) {
+        // TODO: Scale the icon up based off the padding on the side
+        // The icon proper is actually smaller than the drawable and has "padding" on the side for
+        // the purpose of drawing the shadow, allowing the icon to pop up, so we need to scale the
+        // view if we want the icon to be flush with the bottom of the thumbnail.
+        mIconView.setImageDrawable(icon);
+    }
+
+    /**
+     * Set the task thumbnail for the task.
+     *
+     * @param thumbnail task thumbnail for the task
+     */
+    public void setThumbnail(Bitmap thumbnail) {
+        mThumbnailView.setImageBitmap(thumbnail);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
index 963f1fa..fdb80da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/BackgroundAppState.java
@@ -20,6 +20,7 @@
 import android.os.RemoteException;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -72,4 +73,13 @@
         float scale = (float) appWidth / sTempRect.width();
         return new float[] { scale, 0f };
     }
+
+    @Override
+    public int getVisibleElements(Launcher launcher) {
+        if (FeatureFlags.SWIPE_HOME.get()) {
+            return super.getVisibleElements(launcher);
+        }
+        // Hide shelf content (e.g. QSB) because we fade it in when swiping up.
+        return ALL_APPS_HEADER_EXTRA;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
index 3602508..f4ea9f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
 import android.graphics.Rect;
@@ -30,8 +31,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -139,6 +140,8 @@
     public void onBackPressed(Launcher launcher) {
         TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
         if (taskView != null) {
+            launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
+                    newContainerTarget(ContainerType.OVERVIEW));
             taskView.launchTask(true);
         } else {
             super.onBackPressed(launcher);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
index 1ed1353..d61ed72 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -182,11 +182,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(RecentsActivity activity) {
-        return activity.getDragLayer().getAlphaProperty(0);
-    }
-
-    @Override
     public int getContainerType() {
         return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 971987a..e95e2a0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
 import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
@@ -38,13 +39,20 @@
 import android.view.View;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -61,10 +69,6 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
 /**
  * {@link ActivityControlHelper} for the in-launcher recents.
  */
@@ -212,7 +216,7 @@
                         : mShelfState == ShelfAnimState.PEEK
                                 ? shelfPeekingProgress
                                 : shelfOverviewProgress;
-                mShelfAnim = createShelfAnim(activity, toProgress);
+                mShelfAnim = createShelfProgressAnim(activity, toProgress);
                 mShelfAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
@@ -230,10 +234,10 @@
             LauncherState fromState, long transitionLength,
             Consumer<AnimatorPlaybackController> callback) {
         LauncherState endState = OVERVIEW;
+        DeviceProfile dp = activity.getDeviceProfile();
+        long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
         if (wasVisible && fromState != BACKGROUND_APP) {
             // If a translucent app was launched fom launcher, animate launcher states.
-            DeviceProfile dp = activity.getDeviceProfile();
-            long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
             callback.accept(activity.getStateManager()
                     .createAnimationToNewWorkspace(fromState, endState, accuracy));
             return;
@@ -246,10 +250,11 @@
         if (!activity.getDeviceProfile().isVerticalBarLayout()
                 && !FeatureFlags.SWIPE_HOME.get()) {
             // Don't animate the shelf when SWIPE_HOME is true, because we update it atomically.
-            Animator shiftAnim = createShelfAnim(activity,
+            Animator shiftAnim = createShelfProgressAnim(activity,
                     fromState.getVerticalProgress(activity),
                     endState.getVerticalProgress(activity));
             anim.play(shiftAnim);
+            anim.play(createShelfAlphaAnim(activity, endState, accuracy));
         }
         playScaleDownAnim(anim, activity, endState);
 
@@ -266,7 +271,7 @@
         callback.accept(controller);
     }
 
-    private Animator createShelfAnim(Launcher activity, float ... progressValues) {
+    private Animator createShelfProgressAnim(Launcher activity, float ... progressValues) {
         Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(),
                 "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(),
                 SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
@@ -275,6 +280,19 @@
     }
 
     /**
+     * Very quickly fade the alpha of shelf content.
+     */
+    private Animator createShelfAlphaAnim(Launcher activity, LauncherState toState, long accuracy) {
+        AllAppsTransitionController allAppsController = activity.getAllAppsController();
+        AnimatorSetBuilder animBuilder = new AnimatorSetBuilder();
+        animBuilder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, DEACCEL_3);
+        LauncherStateManager.AnimationConfig config = new LauncherStateManager.AnimationConfig();
+        config.duration = accuracy;
+        allAppsController.setAlphas(toState.getVisibleElements(activity), config, animBuilder);
+        return animBuilder.build();
+    }
+
+    /**
      * Scale down recents from the center task being full screen to being in overview.
      */
     private void playScaleDownAnim(AnimatorSet anim, Launcher launcher,
@@ -376,11 +394,6 @@
     }
 
     @Override
-    public AlphaProperty getAlphaProperty(Launcher activity) {
-        return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
index 9fceab4..357c9fc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
@@ -18,6 +18,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.launcher3.config.FeatureFlags;
+
 import java.util.StringJoiner;
 import java.util.function.Consumer;
 
@@ -101,6 +103,9 @@
      * The callback is only run once.
      */
     public void addCallback(int stateMask, Runnable callback) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
+            throw new IllegalStateException("Multiple callbacks on same state");
+        }
         mCallbacks.put(stateMask, callback);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index e3afb92..4e010d2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -230,7 +230,6 @@
                             mTouchSlop) {
                         mPassedTouchSlop = true;
 
-                        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
@@ -272,6 +271,7 @@
     }
 
     private void notifyGestureStarted() {
+        TOUCH_INTERACTION_LOG.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
         }
@@ -310,6 +310,7 @@
         if (listenerSet != null) {
             listenerSet.addListener(handler);
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
+            notifyGestureStarted();
         } else {
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 7dc58a5..00dbdff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -40,7 +40,6 @@
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -78,8 +77,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.FloatingIconView;
@@ -113,7 +110,7 @@
         implements SwipeAnimationListener, OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
@@ -126,47 +123,44 @@
     private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
     private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
     private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
-    private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE =
-            getFlagForIndex(3, "STATE_ACTIVITY_MULTIPLIER_COMPLETE");
 
     // Internal initialization states
     private static final int STATE_APP_CONTROLLER_RECEIVED =
-            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
+            getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
 
     // Interaction finish states
     private static final int STATE_SCALED_CONTROLLER_HOME =
-            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
+            getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
-            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
+            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
     private static final int STATE_SCALED_CONTROLLER_LAST_TASK =
-            getFlagForIndex(7, "STATE_SCALED_CONTROLLER_LAST_TASK");
+            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_LAST_TASK");
 
     private static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(8, "STATE_HANDLER_INVALIDATED");
+            getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
-            getFlagForIndex(9, "STATE_GESTURE_STARTED");
+            getFlagForIndex(8, "STATE_GESTURE_STARTED");
     private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(10, "STATE_GESTURE_CANCELLED");
+            getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
     private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(11, "STATE_GESTURE_COMPLETED");
+            getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
 
     private static final int STATE_CAPTURE_SCREENSHOT =
-            getFlagForIndex(12, "STATE_CAPTURE_SCREENSHOT");
+            getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
     private static final int STATE_SCREENSHOT_CAPTURED =
-            getFlagForIndex(13, "STATE_SCREENSHOT_CAPTURED");
+            getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
-            getFlagForIndex(14, "STATE_SCREENSHOT_VIEW_SHOWN");
+            getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
 
     private static final int STATE_RESUME_LAST_TASK =
-            getFlagForIndex(15, "STATE_RESUME_LAST_TASK");
+            getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
     private static final int STATE_START_NEW_TASK =
-            getFlagForIndex(16, "STATE_START_NEW_TASK");
+            getFlagForIndex(15, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
-            getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED");
+            getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
 
     private static final int LAUNCHER_UI_STATES =
-            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
-            | STATE_LAUNCHER_STARTED;
+            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     // For debugging, keep in sync with above states
 
@@ -226,7 +220,6 @@
     // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
     // visible.
     private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
-    private boolean mDispatchedDownEvent;
     private boolean mContinuingLastGesture;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
@@ -238,7 +231,6 @@
     private final ActivityInitListener mActivityInitListener;
 
     private final int mRunningTaskId;
-    private final RunningTaskInfo mRunningTaskInfo;
     private ThumbnailData mTaskSnapshot;
 
     private MultiStateCallback mStateCallback;
@@ -265,7 +257,6 @@
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
         mContext = context;
-        mRunningTaskInfo = runningTaskInfo;
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
         mActivityControlHelper = controller;
@@ -312,8 +303,7 @@
                 this::startNewTask);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE
-                        | STATE_CAPTURE_SCREENSHOT,
+                        | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
                 this::switchToScreenshot);
 
         mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
@@ -322,13 +312,13 @@
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED
                         | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+                        | STATE_LAUNCHER_DRAWN,
                 this::finishCurrentTransitionToHome);
         mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
                 this::reset);
 
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
+                        | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpAnimation);
@@ -415,6 +405,7 @@
         });
         mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
         mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+        mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
         mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -439,11 +430,10 @@
         AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
 
         if (mWasLauncherAlreadyVisible) {
-            mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
+            mStateCallback.setState(STATE_LAUNCHER_DRAWN);
         } else {
             TraceHelper.beginSection("WTS-init");
             View dragLayer = activity.getDragLayer();
-            mActivityControlHelper.getAlphaProperty(activity).setValue(0);
             dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
 
                 @Override
@@ -476,25 +466,6 @@
     }
 
     private void launcherFrameDrawn() {
-        AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity);
-        if (property.getValue() < 1) {
-            if (mGestureStarted) {
-                final MultiStateCallback callback = mStateCallback;
-                ObjectAnimator animator = ObjectAnimator.ofFloat(
-                        property, MultiValueAlpha.VALUE, 1);
-                animator.setDuration(getFadeInDuration()).addListener(
-                        new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
-                            }
-                        });
-                animator.start();
-            } else {
-                property.setValue(1);
-                mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
-            }
-        }
         mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
     }
 
@@ -822,6 +793,7 @@
             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
+            mLiveTileOverlay.startIconAnimation();
             mRecentsAnimationWrapper.enableInputProxy();
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -1072,7 +1044,6 @@
 
     private void invalidateHandlerWithLauncher() {
         mLauncherTransitionController = null;
-        mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
 
         mRecentsView.setEnableFreeScroll(true);
         mRecentsView.setRunningTaskIconScaledDown(false);
@@ -1172,7 +1143,7 @@
         mActivityControlHelper.onSwipeUpComplete(mActivity);
 
         // Animate the first icon.
-        mRecentsView.animateUpRunningTaskIconScale();
+        mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
         mRecentsView.setSwipeDownShouldLaunchApp(true);
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 0aa1beb..19e9cb4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.AppUsageLimit;
 import android.content.res.Resources;
 import android.icu.text.MeasureFormat;
 import android.icu.text.MeasureFormat.FormatWidth;
@@ -44,13 +45,13 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
-import java.lang.reflect.Method;
 import java.time.Duration;
 import java.util.Locale;
 
 public final class DigitalWellBeingToast extends LinearLayout {
     static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
     static final int MINUTE_MS = 60000;
+    private final LauncherApps mLauncherApps;
 
     public interface InitializeCallback {
         void call(float saturation, String contentDescription);
@@ -67,6 +68,7 @@
         setLayoutDirection(Utilities.isRtl(getResources()) ?
                 View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         setOnClickListener((view) -> openAppUsageSettings());
+        mLauncherApps = context.getSystemService(LauncherApps.class);
     }
 
     @Override
@@ -87,47 +89,29 @@
         }
 
         Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
-            long appUsageLimitTimeMs = -1;
-            long appRemainingTimeMs = -1;
+            final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit(
+                    task.getTopComponent().getPackageName(),
+                    UserHandle.of(task.key.userId));
 
-            try {
-                final Method getAppUsageLimit = LauncherApps.class.getMethod(
-                        "getAppUsageLimit",
-                        String.class,
-                        UserHandle.class);
-                final Object usageLimit = getAppUsageLimit.invoke(
-                        getContext().getSystemService(LauncherApps.class),
-                        task.getTopComponent().getPackageName(),
-                        UserHandle.of(task.key.userId));
-
-                if (usageLimit != null) {
-                    final Class appUsageLimitClass = usageLimit.getClass();
-                    appUsageLimitTimeMs = (long) appUsageLimitClass.getMethod("getTotalUsageLimit").
-                            invoke(usageLimit);
-                    appRemainingTimeMs = (long) appUsageLimitClass.getMethod("getUsageRemaining").
-                            invoke(usageLimit);
-                }
-            } catch (Exception e) {
-                // Do nothing
-            }
-
-            final long appUsageLimitTimeMsFinal = appUsageLimitTimeMs;
-            final long appRemainingTimeMsFinal = appRemainingTimeMs;
+            final long appUsageLimitTimeMs =
+                    usageLimit != null ? usageLimit.getTotalUsageLimit() : -1;
+            final long appRemainingTimeMs =
+                    usageLimit != null ? usageLimit.getUsageRemaining() : -1;
 
             post(() -> {
-                if (appUsageLimitTimeMsFinal < 0) {
+                if (appUsageLimitTimeMs < 0) {
                     setVisibility(GONE);
                 } else {
                     setVisibility(VISIBLE);
-                    mText.setText(getText(appRemainingTimeMsFinal));
-                    mImage.setImageResource(appRemainingTimeMsFinal > 0 ?
+                    mText.setText(getText(appRemainingTimeMs));
+                    mImage.setImageResource(appRemainingTimeMs > 0 ?
                             R.drawable.hourglass_top : R.drawable.hourglass_bottom);
                 }
 
                 callback.call(
-                        appUsageLimitTimeMsFinal >= 0 && appRemainingTimeMsFinal <= 0 ? 0 : 1,
+                        appUsageLimitTimeMs >= 0 && appRemainingTimeMs <= 0 ? 0 : 1,
                         getContentDescriptionForTask(
-                                task, appUsageLimitTimeMsFinal, appRemainingTimeMsFinal));
+                                task, appUsageLimitTimeMs, appRemainingTimeMs));
             });
         });
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index ab2b90f..a838797 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -1,5 +1,11 @@
 package com.android.quickstep.views;
 
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
@@ -9,16 +15,37 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+
+import com.android.launcher3.anim.Interpolators;
 
 public class LiveTileOverlay extends Drawable {
 
+    private static final long ICON_ANIM_DURATION = 120;
+
+    private static final FloatProperty<LiveTileOverlay> PROGRESS =
+            new FloatProperty<LiveTileOverlay>("progress") {
+                @Override
+                public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
+                    liveTileOverlay.setIconAnimationProgress(progress);
+                }
+
+                @Override
+                public Float get(LiveTileOverlay liveTileOverlay) {
+                    return liveTileOverlay.mIconAnimationProgress;
+                }
+            };
+
     private final Paint mPaint = new Paint();
 
     private Rect mBoundsRect = new Rect();
     private RectF mCurrentRect;
     private float mCornerRadius;
+    private Drawable mIcon;
+    private Animator mIconAnimator;
 
     private boolean mDrawEnabled = true;
+    private float mIconAnimationProgress = 0f;
 
     public LiveTileOverlay() {
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
@@ -35,6 +62,33 @@
         invalidateSelf();
     }
 
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    public void startIconAnimation() {
+        if (mIconAnimator != null) {
+            mIconAnimator.cancel();
+        }
+        // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
+        mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
+        mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
+        mIconAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIconAnimator = null;
+            }
+        });
+        mIconAnimator.start();
+    }
+
+    public float cancelIconAnimation() {
+        if (mIconAnimator != null) {
+            mIconAnimator.cancel();
+        }
+        return mIconAnimationProgress;
+    }
+
     public void setDrawEnabled(boolean drawEnabled) {
         if (mDrawEnabled != drawEnabled) {
             mDrawEnabled = drawEnabled;
@@ -46,6 +100,16 @@
     public void draw(Canvas canvas) {
         if (mCurrentRect != null && mDrawEnabled) {
             canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+            if (mIcon != null && mIconAnimationProgress > 0f) {
+                canvas.save();
+                float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+                        1f).getInterpolation(mIconAnimationProgress);
+                canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+                        mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+                canvas.scale(scale, scale);
+                mIcon.draw(canvas);
+                canvas.restore();
+            }
         }
     }
 
@@ -59,4 +123,9 @@
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
+
+    private void setIconAnimationProgress(float progress) {
+        mIconAnimationProgress = progress;
+        invalidateSelf();
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 3e0e8ae..252a957 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -278,6 +278,7 @@
     private final int mEmptyMessagePadding;
     private boolean mShowEmptyMessage;
     private Layout mEmptyTextLayout;
+    private LiveTileOverlay mLiveTileOverlay;
 
     private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             (inMultiWindowMode) -> {
@@ -855,10 +856,15 @@
     }
 
     public void animateUpRunningTaskIconScale() {
+        animateUpRunningTaskIconScale(0);
+    }
+
+    public void animateUpRunningTaskIconScale(float startProgress) {
         mRunningTaskIconScaledDown = false;
         TaskView firstTask = getRunningTaskView();
         if (firstTask != null) {
             firstTask.animateIconScaleAndDimIntoView();
+            firstTask.setIconScaleAnimStartProgress(startProgress);
         }
     }
 
@@ -1567,6 +1573,16 @@
         mClipAnimationHelper = clipAnimationHelper;
     }
 
+    public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
+        mLiveTileOverlay = liveTileOverlay;
+    }
+
+    public void updateLiveTileIcon(Drawable icon) {
+        if (mLiveTileOverlay != null) {
+            mLiveTileOverlay.setIcon(icon);
+        }
+    }
+
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
         if (mRecentsAnimationWrapper == null) {
             if (onFinishComplete != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 419a666..9eec584 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -156,7 +156,8 @@
     private float mZoomScale;
     private float mFullscreenProgress;
 
-    private Animator mIconAndDimAnimator;
+    private ObjectAnimator mIconAndDimAnimator;
+    private float mIconScaleAnimStartProgress = 0;
     private float mFocusTransitionProgress = 1;
 
     private boolean mShowScreenshot;
@@ -317,6 +318,9 @@
             mIconLoadRequest = iconCache.updateIconInBackground(mTask,
                     (task) -> {
                         setIcon(task.icon);
+                        if (isRunningTask()) {
+                            getRecentsView().updateLiveTileIcon(task.icon);
+                        }
                         mDigitalWellBeingToast.initialize(
                                 mTask,
                                 (saturation, contentDescription) -> {
@@ -380,11 +384,16 @@
         mIconView.setScaleY(scale);
     }
 
+    public void setIconScaleAnimStartProgress(float startProgress) {
+        mIconScaleAnimStartProgress = startProgress;
+    }
+
     public void animateIconScaleAndDimIntoView() {
         if (mIconAndDimAnimator != null) {
             mIconAndDimAnimator.cancel();
         }
         mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
+        mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
         mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
         mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index eccef04..75be2e4 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -85,8 +84,6 @@
         return true;
     }
 
-    AlphaProperty getAlphaProperty(T activity);
-
     /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index fe789aa..6031dcd 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -37,6 +37,9 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class StartLauncherViaGestureTests extends AbstractQuickStepTest {
+
+    static final int STRESS_REPEAT_COUNT = 10;
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -79,4 +82,28 @@
         closeLauncherActivity();
         mLauncher.getBackground().switchToOverview();
     }
+
+    @Test
+    @QuickstepOnOff
+    public void testStressPressHome() {
+        for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+            // Destroy Launcher activity.
+            closeLauncherActivity();
+
+            // The test action.
+            mLauncher.pressHome();
+        }
+    }
+
+    @Test
+    @QuickstepOnOff
+    public void testStressSwipeToOverview() {
+        for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
+            // Destroy Launcher activity.
+            closeLauncherActivity();
+
+            // The test action.
+            mLauncher.getBackground().switchToOverview();
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index d96855e..d75006e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -276,14 +276,6 @@
     }
 
     @Override
-    public void setTag(Object tag) {
-        if (tag != null) {
-            LauncherModel.checkItemInfo((ItemInfo) tag);
-        }
-        super.setTag(tag);
-    }
-
-    @Override
     public void refreshDrawableState() {
         if (!mIgnorePressedStateChange) {
             super.refreshDrawableState();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3b054c2..7919d29 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -133,6 +133,7 @@
     private final Rect mInsets = new Rect();
     public final Rect workspacePadding = new Rect();
     private final Rect mHotseatPadding = new Rect();
+    // When true, nav bar is on the left side of the screen.
     private boolean mIsSeascape;
 
     // Notification dots
@@ -587,28 +588,40 @@
     /**
      * Gets an item's location on the home screen. This is useful if the home screen
      * is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
-     *
-     * TODO(b/123900446): Handle landscape mode
      * @param pageDiff The page difference relative to the current page.
      */
     public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
             int pageDiff, Rect outBounds) {
         outBounds.setEmpty();
-        outBounds.left = mInsets.left
-                + workspacePadding.left + cellLayoutPaddingLeftRightPx + (cellX * getCellSize().x);
-        outBounds.top = mInsets.top;
         if (container == CONTAINER_HOTSEAT) {
-            outBounds.top += workspacePadding.top
-                    + (inv.numRows * getCellSize().y)
-                    + verticalDragHandleSizePx
-                    - verticalDragHandleOverlapWorkspace;
-            outBounds.bottom = outBounds.top + hotseatBarSizePx - hotseatBarBottomPaddingPx;
+            final int actualHotseatCellHeight;
+            if (isVerticalBarLayout()) {
+                actualHotseatCellHeight = availableHeightPx / inv.numRows;
+                if (mIsSeascape) {
+                    outBounds.left = mHotseatPadding.left;
+                } else {
+                    outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
+                }
+                outBounds.right = outBounds.left + iconSizePx;
+                outBounds.top = mHotseatPadding.top
+                        + actualHotseatCellHeight * (inv.numRows - cellX - 1);
+                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+            } else {
+                actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
+                        - hotseatBarTopPaddingPx;
+                outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+                        + (cellX * getCellSize().x);
+                outBounds.right = outBounds.left + getCellSize().x;
+                outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
+                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
+            }
         } else {
-            outBounds.top += workspacePadding.top + (cellY * getCellSize().y);
+            outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
+                    + (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
+            outBounds.right = outBounds.left + (getCellSize().x * spanX);
+            outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
             outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
-            outBounds.left += (pageDiff) * availableWidthPx;
         }
-        outBounds.right = outBounds.left + (getCellSize().x * spanX);
     }
 
     public float getAspectRatioWithInsets() {
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index a130604..134e116 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -114,8 +114,6 @@
 
     ItemInfo(ItemInfo info) {
         copyFrom(info);
-        // tempdebug:
-        LauncherModel.checkItemInfo(this);
     }
 
     public void copyFrom(ItemInfo info) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d820448..99343aa 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -378,14 +378,7 @@
         }
 
         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            mUserEventDispatcher = null;
-            initDeviceProfile(mDeviceProfile.inv);
-            dispatchDeviceProfileChanged();
-            reapplyUi();
-            mDragLayer.recreateControllers();
-
-            // TODO: We can probably avoid rebind when only screen size changed.
-            rebindModel();
+            onIdpChanged(mDeviceProfile.inv);
         }
 
         mOldConfig.setTo(newConfig);
@@ -410,8 +403,19 @@
 
     @Override
     public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) {
+        onIdpChanged(idp);
+    }
+
+    private void onIdpChanged(InvariantDeviceProfile idp) {
+        mUserEventDispatcher = null;
+
         initDeviceProfile(idp);
-        getRootView().dispatchInsets();
+        dispatchDeviceProfileChanged();
+        reapplyUi();
+        mDragLayer.recreateControllers();
+
+        // TODO: We can probably avoid rebind when only screen size changed.
+        rebindModel();
     }
 
     private void initDeviceProfile(InvariantDeviceProfile idp) {
@@ -488,11 +492,15 @@
 
     @Override
     public void invalidateParent(ItemInfo info) {
-        FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(getDeviceProfile().inv);
-        if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) {
+        if (info.container >= 0) {
             View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
-            if (folderIcon != null) {
-                folderIcon.invalidate();
+            if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
+                FolderIconPreviewVerifier verifier =
+                        new FolderIconPreviewVerifier(getDeviceProfile().inv);
+                verifier.setFolderInfo((FolderInfo) folderIcon.getTag());
+                if (verifier.isItemInPreview(info.rank)) {
+                    folderIcon.invalidate();
+                }
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index b1664f1..c559f2b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -34,7 +34,6 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
@@ -207,57 +206,6 @@
                 hasVerticalHotseat, verifyChanges);
     }
 
-    static void checkItemInfoLocked(
-            final int itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
-        ItemInfo modelItem = sBgDataModel.itemsIdMap.get(itemId);
-        if (modelItem != null && item != modelItem) {
-            // If it is a release build on a release device, check all the data is consistent as
-            // we don't want to crash non-dev users.
-            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
-                    modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
-                if (modelItem.title.toString().equals(item.title.toString()) &&
-                        modelItem.getIntent().filterEquals(item.getIntent()) &&
-                        modelItem.id == item.id &&
-                        modelItem.itemType == item.itemType &&
-                        modelItem.container == item.container &&
-                        modelItem.screenId == item.screenId &&
-                        modelItem.cellX == item.cellX &&
-                        modelItem.cellY == item.cellY &&
-                        modelItem.spanX == item.spanX &&
-                        modelItem.spanY == item.spanY) {
-                    // For all intents and purposes, this is the same object
-                    return;
-                }
-            }
-
-            // the modelItem needs to match up perfectly with item if our model is
-            // to be consistent with the database-- for now, just require
-            // modelItem == item or the equality check above
-            String msg = "item: " + ((item != null) ? item.toString() : "null") +
-                    "modelItem: " +
-                    ((modelItem != null) ? modelItem.toString() : "null") +
-                    "Error: ItemInfo passed to checkItemInfo doesn't match original";
-            RuntimeException e = new RuntimeException(msg);
-            if (stackTrace != null) {
-                e.setStackTrace(stackTrace);
-            }
-            throw e;
-        }
-    }
-
-    static void checkItemInfo(final ItemInfo item) {
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        final int itemId = item.id;
-        Runnable r = new Runnable() {
-            public void run() {
-                synchronized (sBgDataModel) {
-                    checkItemInfoLocked(itemId, item, stackTrace);
-                }
-            }
-        };
-        runOnWorkerThread(r);
-    }
-
     /**
      * Set this as the current Launcher activity object for the loader.
      */
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index fb33694..ce73229 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 
 import android.annotation.TargetApi;
+import android.app.backup.BackupManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
@@ -150,7 +151,7 @@
             mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);
 
             if (RestoreDbTask.isPending(getContext())) {
-                if (!RestoreDbTask.performRestore(mOpenHelper)) {
+                if (!RestoreDbTask.performRestore(mOpenHelper, new BackupManager(getContext()))) {
                     mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                 }
                 // Set is pending to false irrespective of the result, so that it doesn't get
@@ -542,6 +543,7 @@
      * The class is subclassed in tests to create an in-memory db.
      */
     public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
+        private final BackupManager mBackupManager;
         private final Handler mWidgetHostResetHandler;
         private final Context mContext;
         private int mMaxItemId = -1;
@@ -571,6 +573,7 @@
             super(context, tableName, SCHEMA_VERSION);
             mContext = context;
             mWidgetHostResetHandler = widgetHostResetHandler;
+            mBackupManager = new BackupManager(mContext);
         }
 
         protected void initIds() {
@@ -620,9 +623,12 @@
             Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
         }
 
+        public long getSerialNumberForUser(UserHandle user) {
+            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(user);
+        }
+
         public long getDefaultUserSerial() {
-            return UserManagerCompat.getInstance(mContext).getSerialNumberForUser(
-                    Process.myUserHandle());
+            return getSerialNumberForUser(Process.myUserHandle());
         }
 
         private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e699500..7036639 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -571,9 +571,11 @@
         final boolean isFolderIcon = v instanceof FolderIcon;
         final Rect rect = new Rect();
 
+        // Deep shortcut views have their icon drawn in a separate view.
         final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
-        if (fromDeepShortcutView) {
-            // Deep shortcut views have their icon drawn in a separate view.
+        if (v instanceof DeepShortcutView) {
+            dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
+        } else if (fromDeepShortcutView) {
             DeepShortcutView view = (DeepShortcutView) v.getParent();
             dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
         } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 09eb6d9..9427675 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3084,6 +3084,10 @@
     }
 
     private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) {
+        // TODO(b/128460496) Potential race condition where layout is not yet loaded
+        if (layout == null) {
+            return false;
+        }
         ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
         // map over all the shortcuts on the workspace
         final int itemCount = container.getChildCount();
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 93bf69d..a4ecec7 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -188,9 +188,12 @@
 
     private void setAlphas(LauncherState toState, AnimationConfig config,
             AnimatorSetBuilder builder) {
+        setAlphas(toState.getVisibleElements(mLauncher), config, builder);
+    }
+
+    public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
         PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
                 : config.getPropertySetter(builder);
-        int visibleElements = toState.getVisibleElements(mLauncher);
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index d6e450a..cb239b3 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -21,6 +21,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 
 import androidx.annotation.GuardedBy;
@@ -256,6 +259,16 @@
         @Override
         public void initialize(Context context) {
             contentResolver = context.getContentResolver();
+            contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true,
+                    new ContentObserver(new Handler(Looper.getMainLooper())) {
+                        @Override
+                        public void onChange(boolean selfChange) {
+                            superInitialize(context);
+                    }});
+            superInitialize(context);
+        }
+
+        private void superInitialize(Context context) {
             super.initialize(context);
         }
 
@@ -274,10 +287,5 @@
             }
             return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1;
         }
-
-        @Override
-        public boolean get() {
-            return getFromStorage(null, getDefaultValue());
-        }
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f005ce7..6950a1f 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -63,8 +63,7 @@
     public static final int ALPHA_INDEX_OVERLAY = 0;
     public static final int ALPHA_INDEX_LAUNCHER_LOAD = 1;
     public static final int ALPHA_INDEX_TRANSITIONS = 2;
-    public static final int ALPHA_INDEX_SWIPE_UP = 3;
-    private static final int ALPHA_CHANNEL_COUNT = 4;
+    private static final int ALPHA_CHANNEL_COUNT = 3;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 5e41bb7..0c5a1fc 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -19,11 +19,8 @@
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.ColorFilter;
 import android.graphics.Matrix;
-import android.graphics.Paint;
 import android.graphics.Path;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -49,11 +46,15 @@
 
     private final Drawable mBadge;
     private final Path mMask;
+    private final ConstantState mConstantState;
 
     private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) {
         super(bg, fg);
         mBadge = badge;
         mMask = mask;
+
+        mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(),
+                badge.getConstantState(), mask);
     }
 
     @Override
@@ -134,4 +135,35 @@
 
         return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
     }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mConstantState;
+    }
+
+    private static class MyConstantState extends ConstantState {
+        private final ConstantState mBg;
+        private final ConstantState mFg;
+        private final ConstantState mBadge;
+        private final Path mMask;
+
+        MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) {
+            mBg = bg;
+            mFg = fg;
+            mBadge = badge;
+            mMask = mask;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(),
+                    mBadge.newDrawable(), mMask);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mBg.getChangingConfigurations() & mFg.getChangingConfigurations()
+                    & mBadge.getChangingConfigurations();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 67495ea..71c9148 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -200,6 +200,7 @@
     private void setFolder(Folder folder) {
         mFolder = folder;
         mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
+        mPreviewVerifier.setFolderInfo(mFolder.getInfo());
         updatePreviewItems(false);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
index 5a27cd4..4c84e35 100644
--- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
+++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.folder;
 
+import android.util.Log;
+
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 
@@ -26,14 +28,20 @@
  */
 public class FolderIconPreviewVerifier {
 
+    private static final String TAG = "FolderPreviewVerifier";
+
     private final int mMaxGridCountX;
     private final int mMaxGridCountY;
     private final int mMaxItemsPerPage;
-    private final int[] mGridSize = new int[2];
+    private final int[] mGridSize = new int[] { 1, 1 };
 
+    private int mNumItemsInFolder;
     private int mGridCountX;
     private boolean mDisplayingUpperLeftQuadrant = false;
 
+    /**
+     * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work.
+     */
     public FolderIconPreviewVerifier(InvariantDeviceProfile profile) {
         mMaxGridCountX = profile.numFolderColumns;
         mMaxGridCountY = profile.numFolderRows;
@@ -42,11 +50,14 @@
 
     public void setFolderInfo(FolderInfo info) {
         int numItemsInFolder = info.contents.size();
-        FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
-                mMaxGridCountY, mMaxItemsPerPage, mGridSize);
-        mGridCountX = mGridSize[0];
+        if (numItemsInFolder != mNumItemsInFolder) {
+            FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX,
+                    mMaxGridCountY, mMaxItemsPerPage, mGridSize);
+            mGridCountX = mGridSize[0];
 
-        mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+            mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW;
+            mNumItemsInFolder = numItemsInFolder;
+        }
     }
 
     /**
@@ -62,6 +73,10 @@
      * @return True iff the icon is in the 2x2 upper left quadrant of the Folder.
      */
     public boolean isItemInPreview(int page, int rank) {
+        if (mGridSize[0] == 1) {
+            Log.w(TAG, "setFolderInfo not called before checking if item is in preview.");
+        }
+
         // First page items are laid out such that the first 4 items are always in the upper
         // left quadrant. For all other pages, we need to check the row and col.
         if (page > 0 || mDisplayingUpperLeftQuadrant) {
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 06eaf38..d4ce6dd 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -321,6 +321,7 @@
 
         FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier(
                 Launcher.getLauncher(getContext()).getDeviceProfile().inv);
+        verifier.setFolderInfo(mFolder.getInfo());
         rank = 0;
         for (int i = 0; i < itemCount; i++) {
             View v = list.size() > i ? list.get(i) : null;
diff --git a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
index 52d45bb..f8583b8 100644
--- a/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
+++ b/src/com/android/launcher3/graphics/ShiftedBitmapDrawable.java
@@ -32,10 +32,14 @@
     private float mShiftX;
     private float mShiftY;
 
+    private final ConstantState mConstantState;
+
     public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
         mBitmap = bitmap;
         mShiftX = shiftX;
         mShiftY = shiftY;
+
+        mConstantState = new MyConstantState(mBitmap, mShiftX, mShiftY);
     }
 
     public float getShiftX() {
@@ -71,4 +75,31 @@
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
+
+    @Override
+    public ConstantState getConstantState() {
+        return mConstantState;
+    }
+
+    private static class MyConstantState extends ConstantState {
+        private final Bitmap mBitmap;
+        private float mShiftX;
+        private float mShiftY;
+
+        MyConstantState(Bitmap bitmap, float shiftX, float shiftY) {
+            mBitmap = bitmap;
+            mShiftX = shiftX;
+            mShiftY = shiftY;
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new ShiftedBitmapDrawable(mBitmap, mShiftX, mShiftY);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return 0;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 210f744..97cf267 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -61,6 +61,8 @@
 
     protected final WeakReference<Callbacks> mCallbacks;
 
+    private int mMyBindingId;
+
     public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) {
         mUiExecutor = new MainThreadExecutor();
@@ -94,6 +96,7 @@
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
             mBgDataModel.lastBindId++;
+            mMyBindingId = mBgDataModel.lastBindId;
         }
 
         final int currentScreen;
@@ -285,6 +288,10 @@
 
     protected void executeCallbacksTask(CallbackTask task, Executor executor) {
         executor.execute(() -> {
+            if (mMyBindingId != mBgDataModel.lastBindId) {
+                Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
+                return;
+            }
             Callbacks callbacks = mCallbacks.get();
             if (callbacks != null) {
                 task.execute(callbacks);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 7275576..2b20b08 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -340,8 +340,6 @@
                 Intent intent;
                 String targetPkg;
 
-                FolderIconPreviewVerifier verifier =
-                        new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
                 while (!mStopped && c.moveToNext()) {
                     try {
                         if (c.user == null) {
@@ -366,13 +364,13 @@
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                            if (!Process.myUserHandle().equals(c.user)) {
+                            if (allUsers.indexOfValue(c.user) < 0) {
                                 if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                                    c.markDeleted("Legacy shortcuts are only allowed for default user");
+                                    c.markDeleted("Legacy shortcuts are only allowed for current users");
                                     continue;
                                 } else if (c.restoreFlag != 0) {
                                     // Don't restore items for other profiles.
-                                    c.markDeleted("Restore from managed profile not supported");
+                                    c.markDeleted("Restore from other profiles not supported");
                                     continue;
                                 }
                             }
@@ -461,8 +459,7 @@
                                 c.markRestored();
                             }
 
-                            boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() &&
-                                    !verifier.isItemInPreview(c.getInt(rankIndex));
+                            boolean useLowResIcon = !c.isOnWorkspaceOrHotseat();
 
                             if (c.restoreFlag != 0) {
                                 // Already verified above that user is same as default user
@@ -745,24 +742,25 @@
                 }
             }
 
+            // Sort the folder items, update ranks, and make sure all preview items are high res.
             FolderIconPreviewVerifier verifier =
                     new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile());
-            // Sort the folder items and make sure all items in the preview are high resolution.
             for (FolderInfo folder : mBgDataModel.folders) {
                 Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                 verifier.setFolderInfo(folder);
+                int size = folder.contents.size();
 
-                int numItemsInPreview = 0;
-                for (ShortcutInfo info : folder.contents) {
+                // Update ranks here to ensure there are no gaps caused by removed folder items.
+                // Ranks are the source of truth for folder items, so cellX and cellY can be ignored
+                // for now. Database will be updated once user manually modifies folder.
+                for (int rank = 0; rank < size; ++rank) {
+                    ShortcutInfo info = folder.contents.get(rank);
+                    info.rank = rank;
+
                     if (info.usingLowResIcon()
                             && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                             && verifier.isItemInPreview(info.rank)) {
                         mIconCache.getTitleAndIcon(info, false);
-                        numItemsInPreview++;
-                    }
-
-                    if (numItemsInPreview >= MAX_NUM_ITEMS_IN_PREVIEW) {
-                        break;
                     }
                 }
             }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ac5076c..daf99e9 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -112,19 +113,18 @@
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
-            if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
-                ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
-                ShortcutInfo shortcut = (ShortcutInfo) item;
-                if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
-                        modelShortcut.intent.filterEquals(shortcut.intent) &&
-                        modelShortcut.id == shortcut.id &&
-                        modelShortcut.itemType == shortcut.itemType &&
-                        modelShortcut.container == shortcut.container &&
-                        modelShortcut.screenId == shortcut.screenId &&
-                        modelShortcut.cellX == shortcut.cellX &&
-                        modelShortcut.cellY == shortcut.cellY &&
-                        modelShortcut.spanX == shortcut.spanX &&
-                        modelShortcut.spanY == shortcut.spanY) {
+            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
+                    modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+                if (modelItem.title.toString().equals(item.title.toString()) &&
+                        modelItem.getIntent().filterEquals(item.getIntent()) &&
+                        modelItem.id == item.id &&
+                        modelItem.itemType == item.itemType &&
+                        modelItem.container == item.container &&
+                        modelItem.screenId == item.screenId &&
+                        modelItem.cellX == item.cellX &&
+                        modelItem.cellY == item.cellY &&
+                        modelItem.spanX == item.spanX &&
+                        modelItem.spanY == item.spanY) {
                     // For all intents and purposes, this is the same object
                     return;
                 }
@@ -310,7 +310,7 @@
     /**
      * Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
      * if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
-     * {@link #abortDelete()} MUST be called after this method, or else all delete
+     * {@link #abortDelete} MUST be called after this method, or else all delete
      * operations will remain uncommitted indefinitely.
      */
     public void prepareToUndoDelete() {
@@ -325,7 +325,7 @@
 
     /**
      * If {@link #prepareToUndoDelete} has been called, we store the Runnable to be run when
-     * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
+     * {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
      * Otherwise, we run the Runnable immediately.
      */
     private void enqueueDeleteRunnable(Runnable r) {
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index bcca4d8..040b5e5 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,10 +18,15 @@
 
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
+import android.app.backup.BackupManager;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+import android.util.SparseLongArray;
 
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherProvider.DatabaseHelper;
@@ -48,10 +53,10 @@
     private static final String INFO_COLUMN_NAME = "name";
     private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
 
-    public static boolean performRestore(DatabaseHelper helper) {
+    public static boolean performRestore(DatabaseHelper helper, BackupManager backupManager) {
         SQLiteDatabase db = helper.getWritableDatabase();
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            new RestoreDbTask().sanitizeDB(helper, db);
+            new RestoreDbTask().sanitizeDB(helper, db, backupManager);
             t.commit();
             return true;
         } catch (Exception e) {
@@ -62,20 +67,44 @@
 
     /**
      * Makes the following changes in the provider DB.
-     *   1. Removes all entries belonging to a managed profile as managed profiles
-     *      cannot be restored.
+     *   1. Removes all entries belonging to any profiles that were not restored.
      *   2. Marks all entries as restored. The flags are updated during first load or as
      *      the restored apps get installed.
-     *   3. If the user serial for primary profile is different than that of the previous device,
-     *      update the entries to the new profile id.
+     *   3. If the user serial for any restored profile is different than that of the previous
+     *      device, update the entries to the new profile id.
      */
-    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db) throws Exception {
+    private void sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
+            throws Exception {
+        // Primary user ids
+        long myProfileId = helper.getDefaultUserSerial();
         long oldProfileId = getDefaultProfileId(db);
-        // Delete all entries which do not belong to the main user
-        int itemsDeleted = db.delete(
-                Favorites.TABLE_NAME, "profileId != ?", new String[]{Long.toString(oldProfileId)});
+        LongSparseArray<Long> oldManagedProfileIds = getManagedProfileIds(db, oldProfileId);
+        LongSparseArray<Long> profileMapping = new LongSparseArray<>(oldManagedProfileIds.size()
+                + 1);
+
+        // Build mapping of restored profile ids to their new profile ids.
+        profileMapping.put(oldProfileId, myProfileId);
+        for (int i = oldManagedProfileIds.size() - 1; i >= 0; --i) {
+            long oldManagedProfileId = oldManagedProfileIds.keyAt(i);
+            UserHandle user = getUserForAncestralSerialNumber(backupManager, oldManagedProfileId);
+            if (user != null) {
+                long newManagedProfileId = helper.getSerialNumberForUser(user);
+                profileMapping.put(oldManagedProfileId, newManagedProfileId);
+            }
+        }
+
+        // Delete all entries which do not belong to any restored profile(s).
+        int numProfiles = profileMapping.size();
+        String[] profileIds = new String[numProfiles];
+        profileIds[0] = Long.toString(oldProfileId);
+        StringBuilder whereClause = new StringBuilder("profileId != ?");
+        for (int i = profileMapping.size() - 1; i >= 1; --i) {
+            whereClause.append(" AND profileId != ?");
+            profileIds[i] = Long.toString(profileMapping.keyAt(i));
+        }
+        int itemsDeleted = db.delete(Favorites.TABLE_NAME, whereClause.toString(), profileIds);
         if (itemsDeleted > 0) {
-            FileLog.d(TAG, itemsDeleted + " items belonging to a managed profile, were deleted");
+            FileLog.d(TAG, itemsDeleted + " items from unrestored user(s) were deleted");
         }
 
         // Mark all items as restored.
@@ -85,7 +114,7 @@
                 | (keepAllIcons ? ShortcutInfo.FLAG_RESTORE_STARTED : 0));
         db.update(Favorites.TABLE_NAME, values, null, null);
 
-        // Mark widgets with appropriate restore flag
+        // Mark widgets with appropriate restore flag.
         values.put(Favorites.RESTORED,  LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
                 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
                 LauncherAppWidgetInfo.FLAG_UI_NOT_READY |
@@ -93,21 +122,46 @@
         db.update(Favorites.TABLE_NAME, values, "itemType = ?",
                 new String[]{Integer.toString(Favorites.ITEM_TYPE_APPWIDGET)});
 
-        long myProfileId = helper.getDefaultUserSerial();
+        // Migrate ids. To avoid any overlap, we initially move conflicting ids to a temp location.
+        // Using Long.MIN_VALUE since profile ids can not be negative, so there will be no overlap.
+        final long tempLocationOffset = Long.MIN_VALUE;
+        SparseLongArray tempMigratedIds = new SparseLongArray(profileMapping.size());
+        int numTempMigrations = 0;
+        for (int i = profileMapping.size() - 1; i >= 0; --i) {
+            long oldId = profileMapping.keyAt(i);
+            long newId = profileMapping.valueAt(i);
+
+            if (oldId != newId) {
+                if (profileMapping.indexOfKey(newId) >= 0) {
+                    tempMigratedIds.put(numTempMigrations, newId);
+                    numTempMigrations++;
+                    newId = tempLocationOffset + newId;
+                }
+                migrateProfileId(db, oldId, newId);
+            }
+        }
+
+        // Migrate ids from their temporary id to their actual final id.
+        for (int i = tempMigratedIds.size() - 1; i >= 0; --i) {
+            long newId = tempMigratedIds.valueAt(i);
+            migrateProfileId(db, tempLocationOffset + newId, newId);
+        }
+
         if (myProfileId != oldProfileId) {
-            FileLog.d(TAG, "Changing primary user id from " + oldProfileId + " to " + myProfileId);
-            migrateProfileId(db, myProfileId);
+            changeDefaultColumn(db, myProfileId);
         }
     }
 
     /**
-     * Updates profile id of all entries and changes the default value for the column.
+     * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
      */
-    protected void migrateProfileId(SQLiteDatabase db, long newProfileId) {
+    protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
+        FileLog.d(TAG, "Changing profile user id from " + oldProfileId + " to " + newProfileId);
         // Update existing entries.
         ContentValues values = new ContentValues();
         values.put(Favorites.PROFILE_ID, newProfileId);
-        db.update(Favorites.TABLE_NAME, values, null, null);
+        db.update(Favorites.TABLE_NAME, values, "profileId = ?",
+                new String[]{Long.toString(oldProfileId)});
 
         // Change default value of the column.
         db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
@@ -116,6 +170,43 @@
         dropTable(db, "favorites_old");
     }
 
+
+    /**
+     * Changes the default value for the column.
+     */
+    protected void changeDefaultColumn(SQLiteDatabase db, long newProfileId) {
+        db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;");
+        Favorites.addTableToDb(db, newProfileId, false);
+        db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;");
+        dropTable(db, "favorites_old");
+    }
+
+    /**
+     * Returns a list of the managed profile id(s) used in the favorites table of the provided db.
+     */
+    private LongSparseArray<Long> getManagedProfileIds(SQLiteDatabase db, long defaultProfileId) {
+        LongSparseArray<Long> ids = new LongSparseArray<>();
+        try (Cursor c = db.rawQuery("SELECT profileId from favorites WHERE profileId != ? "
+                + "GROUP BY profileId", new String[] {Long.toString(defaultProfileId)})){
+                while (c.moveToNext()) {
+                    ids.put(c.getLong(c.getColumnIndex(Favorites.PROFILE_ID)), null);
+                }
+        }
+        return ids;
+    }
+
+    /**
+     * Returns a UserHandle of a restored managed profile with the given serial number, or null
+     * if none found.
+     */
+    private UserHandle getUserForAncestralSerialNumber(BackupManager backupManager,
+            long ancestralSerialNumber) {
+        if (Build.VERSION.SDK_INT < 29) {
+            return null;
+        }
+        return backupManager.getUserForAncestralSerialNumber(ancestralSerialNumber);
+    }
+
     /**
      * Returns the profile id used in the favorites table of the provided db.
      */
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index ec0462b..e5c70da 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -34,6 +34,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
+import android.widget.ImageView;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
@@ -48,6 +49,8 @@
 import com.android.launcher3.folder.FolderShape;
 import com.android.launcher3.graphics.ShiftedBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.DeepShortcutView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
@@ -65,7 +68,6 @@
     private Runnable mStartRunnable;
     private Runnable mEndRunnable;
 
-    private Drawable mDrawable;
     private int mOriginalHeight;
     private final int mBlurSizeOutline;
 
@@ -189,70 +191,85 @@
     private void getIcon(Launcher launcher, View v, ItemInfo info, boolean useDrawableAsIs,
             float aspectRatio) {
         final LayoutParams lp = (LayoutParams) getLayoutParams();
-
+        Drawable drawable = null;
         boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() && !useDrawableAsIs
                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
         if (!supportsAdaptiveIcons && v instanceof BubbleTextView) {
             // Similar to DragView, we simply use the BubbleTextView icon here.
-            mDrawable = ((BubbleTextView) v).getIcon();
+            drawable = ((BubbleTextView) v).getIcon();
         }
-        if (mDrawable == null) {
-            mDrawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+        if (info instanceof SystemShortcut) {
+            if (v instanceof ImageView) {
+                drawable = ((ImageView) v).getDrawable();
+            } else if (v instanceof DeepShortcutView) {
+                drawable = ((DeepShortcutView) v).getIconView().getBackground();
+            } else {
+                drawable = v.getBackground();
+            }
+        }
+        if (drawable == null) {
+            drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
                     useDrawableAsIs, new Object[1]);
         }
 
-        if (supportsAdaptiveIcons && mDrawable instanceof AdaptiveIconDrawable) {
-            mIsAdaptiveIcon = true;
-
-            AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) mDrawable;
-            Drawable background = adaptiveIcon.getBackground();
-            if (background == null) {
-                background = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mBackground = background;
-            Drawable foreground = adaptiveIcon.getForeground();
-            if (foreground == null) {
-                foreground = new ColorDrawable(Color.TRANSPARENT);
-            }
-            mForeground = foreground;
-
-            int offset = getOffsetForAdaptiveIconBounds();
-            mFinalDrawableBounds.set(offset, offset, lp.width - offset, mOriginalHeight - offset);
-            if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) {
-                ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
-                ((FolderIcon) v).getPreviewBounds(sTmpRect);
-                sbd.setShiftX(sbd.getShiftX() - sTmpRect.left);
-                sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
-            }
-            mForeground.setBounds(mFinalDrawableBounds);
-            mBackground.setBounds(mFinalDrawableBounds);
-
-            int blurMargin = mBlurSizeOutline / 2;
-            mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin,
-                    mOriginalHeight - blurMargin);
-
-            if (aspectRatio > 0) {
-                lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
-                layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
-                        + lp.height);
-            }
-            mBgDrawableStartScale = (float) lp.height / mOriginalHeight;
-            setBackgroundDrawableBounds(mBgDrawableStartScale);
-
-            // Set up outline
-            mOutline.set(0, 0, lp.width, lp.height);
-            setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRoundRect(mOutline, mTaskCornerRadius);
-                }
-            });
-            setClipToOutline(true);
-        } else {
-            setBackground(mDrawable);
-        }
+        Drawable finalDrawable = drawable == null ? null
+                : drawable.getConstantState().newDrawable();
+        boolean isAdaptiveIcon = supportsAdaptiveIcons
+                && finalDrawable instanceof AdaptiveIconDrawable;
+        int iconOffset = getOffsetForIconBounds(finalDrawable);
 
         new Handler(Looper.getMainLooper()).post(() -> {
+            if (isAdaptiveIcon) {
+                mIsAdaptiveIcon = true;
+
+                AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) finalDrawable;
+                Drawable background = adaptiveIcon.getBackground();
+                if (background == null) {
+                    background = new ColorDrawable(Color.TRANSPARENT);
+                }
+                mBackground = background;
+                Drawable foreground = adaptiveIcon.getForeground();
+                if (foreground == null) {
+                    foreground = new ColorDrawable(Color.TRANSPARENT);
+                }
+                mForeground = foreground;
+
+                mFinalDrawableBounds.set(iconOffset, iconOffset, lp.width -
+                        iconOffset, mOriginalHeight - iconOffset);
+                if (mForeground instanceof ShiftedBitmapDrawable && v instanceof FolderIcon) {
+                    ShiftedBitmapDrawable sbd = (ShiftedBitmapDrawable) mForeground;
+                    ((FolderIcon) v).getPreviewBounds(sTmpRect);
+                    sbd.setShiftX(sbd.getShiftX() - sTmpRect.left);
+                    sbd.setShiftY(sbd.getShiftY() - sTmpRect.top);
+                }
+                mForeground.setBounds(mFinalDrawableBounds);
+                mBackground.setBounds(mFinalDrawableBounds);
+
+                int blurMargin = mBlurSizeOutline / 2;
+                mStartRevealRect.set(blurMargin, blurMargin , lp.width - blurMargin,
+                        mOriginalHeight - blurMargin);
+
+                if (aspectRatio > 0) {
+                    lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
+                    layout(lp.leftMargin, lp.topMargin, lp.leftMargin + lp.width, lp.topMargin
+                            + lp.height);
+                }
+                mBgDrawableStartScale = (float) lp.height / mOriginalHeight;
+                setBackgroundDrawableBounds(mBgDrawableStartScale);
+
+                // Set up outline
+                mOutline.set(0, 0, lp.width, lp.height);
+                setOutlineProvider(new ViewOutlineProvider() {
+                    @Override
+                    public void getOutline(View view, Outline outline) {
+                        outline.setRoundRect(mOutline, mTaskCornerRadius);
+                    }
+                });
+                setClipToOutline(true);
+            } else {
+                setBackground(finalDrawable);
+            }
+
             invalidate();
             invalidateOutline();
         });
@@ -267,9 +284,10 @@
         mBackground.setBounds(mBgDrawableBounds);
     }
 
-    private int getOffsetForAdaptiveIconBounds() {
+    @WorkerThread
+    private int getOffsetForIconBounds(Drawable drawable) {
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O ||
-                !(mDrawable instanceof AdaptiveIconDrawable)) {
+                !(drawable instanceof AdaptiveIconDrawable)) {
             return 0;
         }
 
@@ -278,7 +296,7 @@
         bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
 
         try (LauncherIcons li = LauncherIcons.obtain(Launcher.fromContext(getContext()))) {
-            Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(mDrawable, null));
+            Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null));
         }
 
         bounds.inset(
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index babb731..6fa8d62 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -41,18 +41,34 @@
         // Verify item add
         assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
 
-        new RestoreDbTask().migrateProfileId(db, 33);
+        new RestoreDbTask().migrateProfileId(db, 42, 33);
 
         // verify data migrated
         assertEquals(0, getCount(db, "select * from favorites where profileId = 42"));
         assertEquals(5, getCount(db, "select * from favorites where profileId = 33"));
+    }
+
+    @Test
+    public void testChangeDefaultColumn() throws Exception {
+        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        // Add some dummy data
+        for (int i = 0; i < 5; i++) {
+            ContentValues values = new ContentValues();
+            values.put(Favorites._ID, i);
+            values.put(Favorites.TITLE, "item " + i);
+            db.insert(Favorites.TABLE_NAME, null, values);
+        }
+        // Verify default column is 42
+        assertEquals(5, getCount(db, "select * from favorites where profileId = 42"));
+
+        new RestoreDbTask().changeDefaultColumn(db, 33);
 
         // Verify default value changed
         ContentValues values = new ContentValues();
         values.put(Favorites._ID, 100);
         values.put(Favorites.TITLE, "item 100");
         db.insert(Favorites.TABLE_NAME, null, values);
-        assertEquals(6, getCount(db, "select * from favorites where profileId = 33"));
+        assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
     }
 
     private int getCount(SQLiteDatabase db, String sql) {