Merge "Support shorter height row layout / Disable ICON_SLICE" into sc-dev
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 5f1046d..b124b33 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -24,6 +24,8 @@
         android:id="@+id/taskbar_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:background="@color/taskbar_background"/>
+        android:background="@color/taskbar_background"
+        android:gravity="center"
+        android:animateLayoutChanges="true"/>
 
 </com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_app_icon.xml b/quickstep/res/layout/taskbar_app_icon.xml
new file mode 100644
index 0000000..6fefdb6
--- /dev/null
+++ b/quickstep/res/layout/taskbar_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.views.DoubleShadowBubbleTextView style="@style/BaseIcon.Workspace.Taskbar" />
diff --git a/quickstep/res/layout/taskbar_predicted_app_icon.xml b/quickstep/res/layout/taskbar_predicted_app_icon.xml
new file mode 100644
index 0000000..211ebc8
--- /dev/null
+++ b/quickstep/res/layout/taskbar_predicted_app_icon.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher3.uioverrides.PredictedAppIcon style="@style/BaseIcon.Workspace.Taskbar" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4272f50..39cc0b8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -122,4 +122,9 @@
 
     <!-- Taskbar -->
     <dimen name="taskbar_size">48dp</dimen>
+    <dimen name="taskbar_icon_size">32dp</dimen>
+    <dimen name="taskbar_icon_touch_size">48dp</dimen>
+    <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
+    <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
+    <dimen name="taskbar_icon_spacing">14dp</dimen>
 </resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 8d054b4..5a353f0 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -85,4 +85,10 @@
         <item name="android:drawablePadding">8dp</item>
         <item name="android:textAllCaps">false</item>
     </style>
+
+    <!-- Icon displayed on the taskbar -->
+    <style name="BaseIcon.Workspace.Taskbar" >
+        <item name="iconDisplay">taskbar</item>
+        <item name="iconSizeOverride">@dimen/taskbar_icon_size</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 09a3cfd..cbe0eb0 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -265,6 +265,11 @@
     }
 
     @Override
+    public boolean isViewInTaskbar(View v) {
+        return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
+    }
+
+    @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
@@ -350,6 +355,10 @@
         // populating workspace.
         // TODO: Find a better place for this
         WellbeingModel.INSTANCE.get(this);
+
+        if (mTaskbarController != null) {
+            mTaskbarController.onHotseatUpdated();
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 36c8bb8..876cabc 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -202,9 +202,10 @@
     }
 
     @Override
-    public boolean supportsAdaptiveIconAnimation() {
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return hasControlRemoteAppTransitionPermission()
-                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
+                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+                && !mLauncher.isViewInTaskbar(clickedView);
     }
 
     /**
@@ -972,9 +973,12 @@
                     launcherIsATargetWithMode(appTargets, MODE_CLOSING);
 
             final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
+            final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
             if (launchingFromRecents) {
                 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
                         launcherClosing);
+            } else if (launchingFromTaskbar) {
+                // TODO
             } else {
                 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
                         launcherClosing);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b2de4c9..aa6601b 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -75,7 +74,7 @@
 
     private int mHotSeatItemsCount;
 
-    private Launcher mLauncher;
+    private QuickstepLauncher mLauncher;
     private final Hotseat mHotseat;
 
     private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -108,7 +107,7 @@
         return true;
     };
 
-    public HotseatPredictionController(Launcher launcher) {
+    public HotseatPredictionController(QuickstepLauncher launcher) {
         mLauncher = launcher;
         mHotseat = launcher.getHotseat();
         mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
@@ -229,6 +228,10 @@
         } else {
             if (callback != null) callback.run();
         }
+
+        if (mLauncher.getTaskbarController() != null) {
+            mLauncher.getTaskbarController().onHotseatUpdated();
+        }
     }
 
     /**
@@ -242,6 +245,10 @@
      * start and pauses predicted apps update on the hotseat
      */
     public void setPauseUIUpdate(boolean paused) {
+        if (mLauncher.getTaskbarController() != null) {
+            // Taskbar is present, always allow updates since hotseat is still visible.
+            return;
+        }
         mUIUpdatePaused = paused;
         if (!paused) {
             fillGapsWithPrediction();
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java
index 0ea2f8b..c441e22 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultIconSlice.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.search;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.app.search.SearchTarget;
@@ -104,7 +105,7 @@
                 PackageItemInfo pkgItem = new PackageItemInfo(parentTarget.getPackageName());
                 pkgItem.user = parentTarget.getUserHandle();
                 appState.getIconCache().getTitleAndIconForApp(pkgItem, false);
-                mIcon.applyFromItemInfoWithIcon(pkgItem);
+                MAIN_EXECUTOR.post(() -> mIcon.applyFromItemInfoWithIcon(pkgItem));
             });
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index bdf7f8a..7608645 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -26,6 +26,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
 import android.view.WindowManager;
 
 import androidx.annotation.Nullable;
@@ -36,7 +38,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.quickstep.AnimatedFloat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -55,6 +59,8 @@
     private final Point mTaskbarSize;
     private final TaskbarStateHandler mTaskbarStateHandler;
     private final TaskbarVisibilityController mTaskbarVisibilityController;
+    private final TaskbarHotseatController mHotseatController;
+    private final TaskbarDragController mDragController;
 
     // Initialized in init().
     private WindowManager.LayoutParams mWindowLayoutParams;
@@ -64,12 +70,16 @@
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
         mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+        mTaskbarView.setCallbacks(createTaskbarViewCallbacks());
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT,
                 mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size));
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
         mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
                 createTaskbarVisibilityControllerCallbacks());
+        mHotseatController = new TaskbarHotseatController(mLauncher,
+                createTaskbarHotseatControllerCallbacks());
+        mDragController = new TaskbarDragController(mLauncher);
     }
 
     private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() {
@@ -87,13 +97,38 @@
         };
     }
 
+    private TaskbarViewCallbacks createTaskbarViewCallbacks() {
+        return new TaskbarViewCallbacks() {
+            @Override
+            public View.OnClickListener getItemOnClickListener() {
+                return ItemClickHandler.INSTANCE;
+            }
+
+            @Override
+            public View.OnLongClickListener getItemOnLongClickListener() {
+                return mDragController::startDragOnLongClick;
+            }
+        };
+    }
+
+    private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() {
+        return new TaskbarHotseatControllerCallbacks() {
+            @Override
+            public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+                mTaskbarView.updateHotseatItems(hotseatItemInfos);
+            }
+        };
+    }
+
     /**
      * Initializes the Taskbar, including adding it to the screen.
      */
     public void init() {
+        mTaskbarView.init(mHotseatController.getNumHotseatIcons());
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
         mTaskbarVisibilityController.init();
+        mHotseatController.init();
     }
 
     private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -109,9 +144,11 @@
      * Removes the Taskbar from the screen, and removes any obsolete listeners etc.
      */
     public void cleanup() {
+        mTaskbarView.cleanup();
         removeFromWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(null);
         mTaskbarVisibilityController.cleanup();
+        mHotseatController.cleanup();
     }
 
     private void removeFromWindowManager() {
@@ -191,6 +228,32 @@
     }
 
     /**
+     * Should be called when one or more items in the Hotseat have changed.
+     */
+    public void onHotseatUpdated() {
+        mHotseatController.onHotseatUpdated();
+    }
+
+    /**
+     * @param ev MotionEvent in screen coordinates.
+     * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+     */
+    public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
+        return mTaskbarView.isEventOverAnyItem(ev);
+    }
+
+    public boolean isDraggingItem() {
+        return mTaskbarView.isDraggingItem();
+    }
+
+    /**
+     * @return Whether the given View is in the same window as Taskbar.
+     */
+    public boolean isViewInTaskbar(View v) {
+        return mTaskbarContainerView.getWindowId().equals(v.getWindowId());
+    }
+
+    /**
      * Contains methods that TaskbarStateHandler can call to interface with TaskbarController.
      */
     protected interface TaskbarStateHandlerCallbacks {
@@ -205,4 +268,19 @@
         void updateTaskbarBackgroundAlpha(float alpha);
         void updateTaskbarVisibilityAlpha(float alpha);
     }
+
+    /**
+     * Contains methods that TaskbarView can call to interface with TaskbarController.
+     */
+    protected interface TaskbarViewCallbacks {
+        View.OnClickListener getItemOnClickListener();
+        View.OnLongClickListener getItemOnLongClickListener();
+    }
+
+    /**
+     * Contains methods that TaskbarHotseatController can call to interface with TaskbarController.
+     */
+    protected interface TaskbarHotseatControllerCallbacks {
+        void updateHotseatItems(ItemInfo[] hotseatItemInfos);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
new file mode 100644
index 0000000..2318ff9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.systemui.shared.system.ClipDescriptionCompat;
+import com.android.systemui.shared.system.LauncherAppsCompat;
+
+/**
+ * Handles long click on Taskbar items to start a system drag and drop operation.
+ */
+public class TaskbarDragController {
+
+    private final BaseQuickstepLauncher mLauncher;
+    private final int mDragIconSize;
+
+    public TaskbarDragController(BaseQuickstepLauncher launcher) {
+        mLauncher = launcher;
+        Resources resources = mLauncher.getResources();
+        mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
+    }
+
+    /**
+     * Attempts to start a system drag and drop operation for the given View, using its tag to
+     * generate the ClipDescription and Intent.
+     * @return Whether {@link View#startDragAndDrop} started successfully.
+     */
+    protected boolean startDragOnLongClick(View view) {
+        if (!(view instanceof BubbleTextView)) {
+            return false;
+        }
+
+        BubbleTextView btv = (BubbleTextView) view;
+
+        View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+            @Override
+            public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+                shadowSize.set(mDragIconSize, mDragIconSize);
+                // TODO: should be based on last touch point on the icon.
+                shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+            }
+
+            @Override
+            public void onDrawShadow(Canvas canvas) {
+                canvas.save();
+                float scale = (float) mDragIconSize / btv.getIconSize();
+                canvas.scale(scale, scale);
+                btv.getIcon().draw(canvas);
+                canvas.restore();
+            }
+        };
+
+        Object tag = view.getTag();
+        ClipDescription clipDescription = null;
+        Intent intent = null;
+        if (tag instanceof WorkspaceItemInfo) {
+            WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
+            LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class);
+            clipDescription = new ClipDescription(item.title,
+                    new String[] {
+                            item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                                    ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT
+                                    : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY
+                    });
+            intent = new Intent();
+            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
+                intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId());
+            } else {
+                intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
+                        LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps,
+                                item.getIntent().getComponent(), null, item.user));
+            }
+            intent.putExtra(Intent.EXTRA_USER, item.user);
+        }
+
+        if (clipDescription != null && intent != null) {
+            ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
+            view.setOnDragListener(getDraggedViewDragListener());
+            return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+        }
+        return false;
+    }
+
+    /**
+     * Hide the original Taskbar item while it is being dragged.
+     */
+    private View.OnDragListener getDraggedViewDragListener() {
+        return (view, dragEvent) -> {
+            switch (dragEvent.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    view.setVisibility(INVISIBLE);
+                    return true;
+                case DragEvent.ACTION_DRAG_ENDED:
+                    view.setVisibility(VISIBLE);
+                    view.setOnDragListener(null);
+                    return true;
+            }
+            return false;
+        };
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
new file mode 100644
index 0000000..4dc051a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.view.View;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.data.ItemInfo;
+
+/**
+ * Works with TaskbarController to update the TaskbarView's Hotseat items.
+ */
+public class TaskbarHotseatController {
+
+    private final BaseQuickstepLauncher mLauncher;
+    private final Hotseat mHotseat;
+    private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks;
+    private final int mNumHotseatIcons;
+
+    private final DragController.DragListener mDragListener = new DragController.DragListener() {
+        @Override
+        public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        }
+
+        @Override
+        public void onDragEnd() {
+            onHotseatUpdated();
+        }
+    };
+
+    public TaskbarHotseatController(BaseQuickstepLauncher launcher,
+            TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) {
+        mLauncher = launcher;
+        mHotseat = mLauncher.getHotseat();
+        mTaskbarCallbacks = taskbarCallbacks;
+        mNumHotseatIcons = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+    }
+
+    protected void init() {
+        mLauncher.getDragController().addDragListener(mDragListener);
+    }
+
+    protected void cleanup() {
+        mLauncher.getDragController().removeDragListener(mDragListener);
+    }
+
+    /**
+     * Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
+     */
+    protected void onHotseatUpdated() {
+        ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
+        ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
+        for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
+            View child = shortcutsAndWidgets.getChildAt(i);
+            Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
+            if (tag instanceof ItemInfo) {
+                ItemInfo itemInfo = (ItemInfo) tag;
+                CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+                // Since the hotseat might be laid out vertically or horizontally, use whichever
+                // index is higher.
+                hotseatItemInfos[Math.max(lp.cellX, lp.cellY)] = itemInfo;
+            }
+        }
+
+        mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos);
+    }
+
+    protected int getNumHotseatIcons() {
+        return mNumHotseatIcons;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index bf6e946..c98f09c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,19 +16,50 @@
 package com.android.launcher3.taskbar;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
 import android.widget.LinearLayout;
 
+import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.R;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
 public class TaskbarView extends LinearLayout {
 
     private final ColorDrawable mBackgroundDrawable;
+    private final int mItemMarginLeftRight;
+    private final int mIconTouchSize;
+    private final int mTouchSlop;
+    private final RectF mTempDelegateBounds = new RectF();
+    private final RectF mDelegateSlopBounds = new RectF();
+    private final int[] mTempOutLocation = new int[2];
+
+    // Initialized in init().
+    private int mHotseatStartIndex;
+    private int mHotseatEndIndex;
+
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+
+    // Delegate touches to the closest view if within mIconTouchSize.
+    private boolean mDelegateTargeted;
+    private View mDelegateView;
+
+    private boolean mIsDraggingItem;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -46,7 +77,26 @@
     public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
+        Resources resources = getResources();
         mBackgroundDrawable = (ColorDrawable) getBackground();
+        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+
+    protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
+        mControllerCallbacks = taskbarViewCallbacks;
+    }
+
+    protected void init(int numHotseatIcons) {
+        mHotseatStartIndex = 0;
+        mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
+        updateHotseatItems(new ItemInfo[numHotseatIcons]);
+    }
+
+    protected void cleanup() {
+        removeAllViews();
     }
 
     /**
@@ -56,4 +106,159 @@
     public void setBackgroundAlpha(float alpha) {
         mBackgroundDrawable.setAlpha((int) (alpha * 255));
     }
+
+    /**
+     * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
+     */
+    protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+        for (int i = 0; i < hotseatItemInfos.length; i++) {
+            ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+            int hotseatIndex = mHotseatStartIndex + i;
+            View hotseatView = getChildAt(hotseatIndex);
+
+            // Replace any Hotseat views with the appropriate type if it's not already that type.
+            final int expectedLayoutResId;
+            if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+                expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
+            } else {
+                expectedLayoutResId = R.layout.taskbar_app_icon;
+            }
+            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) {
+                removeView(hotseatView);
+                BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId);
+                LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+                lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+                hotseatView = btv;
+                addView(hotseatView, hotseatIndex, lp);
+            }
+
+            // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
+            if (hotseatView instanceof BubbleTextView
+                    && hotseatItemInfo instanceof WorkspaceItemInfo) {
+                ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
+                        (WorkspaceItemInfo) hotseatItemInfo);
+                hotseatView.setVisibility(VISIBLE);
+                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                hotseatView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
+            } else {
+                hotseatView.setVisibility(GONE);
+                hotseatView.setOnClickListener(null);
+                hotseatView.setOnLongClickListener(null);
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean handled = delegateTouchIfNecessary(event);
+        return super.onTouchEvent(event) || handled;
+    }
+
+    /**
+     * User touched the Taskbar background. Determine whether the touch is close enough to a view
+     * that we should forward the touches to it.
+     * @return Whether a delegate view was chosen and it handled the touch event.
+     */
+    private boolean delegateTouchIfNecessary(MotionEvent event) {
+        final float x = event.getX();
+        final float y = event.getY();
+        if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
+            View delegateView = findDelegateView(x, y);
+            if (delegateView != null) {
+                mDelegateTargeted = true;
+                mDelegateView = delegateView;
+                mDelegateSlopBounds.set(mTempDelegateBounds);
+                mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
+            }
+        }
+
+        boolean sendToDelegate = mDelegateTargeted;
+        boolean inBounds = true;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_MOVE:
+                inBounds = mDelegateSlopBounds.contains(x, y);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mDelegateTargeted = false;
+                break;
+        }
+
+        boolean handled = false;
+        if (sendToDelegate) {
+            if (inBounds) {
+                // Offset event coordinates to be inside the target view
+                event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
+            } else {
+                // Offset event coordinates to be outside the target view (in case it does
+                // something like tracking pressed state)
+                event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
+            }
+            handled = mDelegateView.dispatchTouchEvent(event);
+            // Cleanup if this was the last event to send to the delegate.
+            if (!mDelegateTargeted) {
+                mDelegateView = null;
+            }
+        }
+        return handled;
+    }
+
+    /**
+     * Return an item whose touch bounds contain the given coordinates,
+     * or null if no such item exists.
+     *
+     * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
+     */
+    private @Nullable View findDelegateView(float x, float y) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (!child.isShown() || !child.isClickable()) {
+                continue;
+            }
+            int childCenterX = child.getLeft() + child.getWidth() / 2;
+            int childCenterY = child.getTop() + child.getHeight() / 2;
+            mTempDelegateBounds.set(
+                    childCenterX - mIconTouchSize / 2f,
+                    childCenterY - mIconTouchSize / 2f,
+                    childCenterX + mIconTouchSize / 2f,
+                    childCenterY + mIconTouchSize / 2f);
+            if (mTempDelegateBounds.contains(x, y)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
+     * touch bounds.
+     */
+    public boolean isEventOverAnyItem(MotionEvent ev) {
+        getLocationOnScreen(mTempOutLocation);
+        float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
+        float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
+        return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+    }
+
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                mIsDraggingItem = true;
+                return true;
+            case DragEvent.ACTION_DRAG_ENDED:
+                mIsDraggingItem = false;
+                break;
+        }
+        return super.onDragEvent(event);
+    }
+
+    public boolean isDraggingItem() {
+        return mIsDraggingItem;
+    }
+
+    private View inflate(@LayoutRes int layoutResId) {
+        return LayoutInflater.from(getContext()).inflate(layoutResId, this, false);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 597c17b..22c4a7e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -184,7 +184,10 @@
     }
 
     private int getOutlineOffsetY() {
-        return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+        if (mDisplay != DISPLAY_TASKBAR) {
+            return getPaddingTop() + mDeviceProfile.folderIconOffsetYPx;
+        }
+        return (getMeasuredHeight() / 2) - mNormalizedIconRadius;
     }
 
     private void drawEffect(Canvas canvas, boolean isBadged) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6bcc4bf..36b51cd 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -731,7 +731,6 @@
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
-        mTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     /**
@@ -958,7 +957,6 @@
         if (endTarget == HOME) {
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
-            LiveTileOverlay.INSTANCE.startIconAnimation();
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getPageNearestToCenterOfScreen();
                 if (mRecentsView.getNextPage() != nearestPage) {
@@ -971,9 +969,6 @@
                 }
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                mRecentsView.getRunningTaskView().setIsClickableAsLiveTile(false);
-            }
         }
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
@@ -1067,7 +1062,6 @@
         }
 
         if (mGestureState.getEndTarget() == HOME) {
-            mTaskViewSimulator.setDrawsBelowRecents(false);
             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
             final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
                     ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
@@ -1453,10 +1447,6 @@
     private void finishCurrentTransitionToRecents() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-            final TaskView runningTaskView = mRecentsView.getRunningTaskView();
-            if (runningTaskView != null) {
-                runningTaskView.setIsClickableAsLiveTile(true);
-            }
         } else if (!hasTargets() || mRecentsAnimationController == null) {
             // If there are no targets or the animation not started, then there is nothing to finish
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 5f6e59f..ce14197 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -153,6 +153,13 @@
         return deviceState.isInDeferredGestureRegion(ev);
     }
 
+    /**
+     * @return Whether the gesture in progress should be cancelled.
+     */
+    public boolean shouldCancelCurrentGesture() {
+        return false;
+    }
+
     public abstract void onExitOverview(RotationTouchHelper deviceState,
             Runnable exitRunnable);
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index deb86ec..8b0d782 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -26,6 +26,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.Log;
+import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -311,4 +312,22 @@
         boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
         taskbarController.setIsImeVisible(isImeVisible);
     }
+
+    @Override
+    public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
+        TaskbarController taskbarController = getTaskbarController();
+        if (taskbarController == null) {
+            return super.deferStartingActivity(deviceState, ev);
+        }
+        return taskbarController.isEventOverAnyTaskbarItem(ev);
+    }
+
+    @Override
+    public boolean shouldCancelCurrentGesture() {
+        TaskbarController taskbarController = getTaskbarController();
+        if (taskbarController == null) {
+            return super.shouldCancelCurrentGesture();
+        }
+        return taskbarController.isDraggingItem();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index a98fc1c..8ebea33 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -514,9 +514,14 @@
             }
         }
 
-        boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL)
+        boolean cancelGesture = mGestureState.getActivityInterface() != null
+                && mGestureState.getActivityInterface().shouldCancelCurrentGesture();
+        boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL || cancelGesture)
                 && mConsumer != null
                 && !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture();
+        if (cancelGesture) {
+            event.setAction(ACTION_CANCEL);
+        }
         mUncheckedConsumer.onMotionEvent(event);
 
         if (cleanUpConsumer) {
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index 747c3f2..8210ab0 100644
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -85,6 +85,11 @@
         mIcon = icon;
     }
 
+    // TODO: consider cleaning this up and drawing icon in another way. Previously we place app
+    // below launcher during the initial swipe up and render the icon in this live tile overlay.
+    // However, this resulted in a bunch of touch input issues caused by Launcher getting the input
+    // events during transition (to overview / to another app (quick switch). So now our new
+    // solution places app on top in live tile until it fully settles in Overview.
     public void startIconAnimation() {
         if (mIconAnimator != null) {
             mIconAnimator.cancel();
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index e8f590f..59cf3b2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -356,10 +356,6 @@
         return false;
     }
 
-    public void setIsClickableAsLiveTile(boolean isClickableAsLiveTile) {
-        mIsClickableAsLiveTile = isClickableAsLiveTile;
-    }
-
     private void computeAndSetIconTouchDelegate() {
         float iconHalfSize = mIconView.getWidth() / 2f;
         mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize;
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 96c30b5..e593fb4 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -57,6 +57,7 @@
             <enum name="widget_section" value="3" />
             <enum name="shortcut_popup" value="4" />
             <enum name="hero_app" value="5" />
+            <enum name="taskbar" value="6" />
         </attr>
         <attr name="centerVertically" format="boolean" />
     </declare-styleable>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3425f91..447c9ac 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -232,6 +232,8 @@
     <string name="abandoned_promise_explanation">The app for this icon isn\'t installed.
         You can remove it, or search for the app and install it manually.
     </string>
+    <!-- Title for an app which is being installed. -->
+    <string name="app_installing_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> installing, <xliff:g id="progress" example="30%">%2$s</xliff:g> complete</string>
     <!-- Title for an app which is being downloaded. -->
     <string name="app_downloading_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> downloading, <xliff:g id="progress" example="30%">%2$s</xliff:g> complete</string>
     <!-- Title for an app whose download has been started. -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 575d6cd..5007ffc 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -51,6 +51,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
@@ -92,6 +93,7 @@
     private static final int DISPLAY_ALL_APPS = 1;
     private static final int DISPLAY_FOLDER = 2;
     private static final int DISPLAY_HERO_APP = 5;
+    protected static final int DISPLAY_TASKBAR = 6;
 
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
     private static final float HIGHLIGHT_SCALE = 1.16f;
@@ -140,7 +142,7 @@
     private Drawable mIcon;
     private boolean mCenterVertically;
 
-    private final int mDisplay;
+    protected final int mDisplay;
 
     private final CheckLongPressHelper mLongPressHelper;
 
@@ -206,6 +208,8 @@
             defaultIconSize = grid.folderChildIconSizePx;
         } else if (mDisplay == DISPLAY_HERO_APP) {
             defaultIconSize = grid.allAppsIconSizePx;
+        } else if (mDisplay == DISPLAY_TASKBAR) {
+            defaultIconSize = grid.iconSizePx;
         } else {
             // widget_selection or shortcut_popup
             defaultIconSize = grid.iconSizePx;
@@ -268,6 +272,7 @@
         mDotScaleAnim.start();
     }
 
+    @UiThread
     public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
         applyFromWorkspaceItem(info, false);
     }
@@ -284,13 +289,16 @@
         }
     }
 
+    @UiThread
     public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) {
         applyIconAndLabel(info);
         setTag(info);
         applyLoadingState(promiseStateChanged);
         applyDotState(info, false /* animate */);
+        setDownloadStateContentDescription(info, info.getProgressLevel());
     }
 
+    @UiThread
     public void applyFromApplicationInfo(AppInfo info) {
         applyIconAndLabel(info);
 
@@ -304,11 +312,13 @@
             applyProgressLevel();
         }
         applyDotState(info, false /* animate */);
+        setDownloadStateContentDescription(info, info.getProgressLevel());
     }
 
     /**
      * Apply label and tag using a generic {@link ItemInfoWithIcon}
      */
+    @UiThread
     public void applyFromItemInfoWithIcon(ItemInfoWithIcon info) {
         applyIconAndLabel(info);
         // We don't need to check the info since it's not a WorkspaceItemInfo
@@ -316,16 +326,20 @@
 
         // Verify high res immediately
         verifyHighRes();
+
+        setDownloadStateContentDescription(info, info.getProgressLevel());
     }
 
     /**
      * Apply label and tag using a {@link SearchActionItemInfo}
      */
+    @UiThread
     public void applyFromSearchActionItemInfo(SearchActionItemInfo searchActionItemInfo) {
         applyIconAndLabel(searchActionItemInfo);
         setTag(searchActionItemInfo);
     }
 
+    @UiThread
     protected void applyIconAndLabel(ItemInfoWithIcon info) {
         FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
         mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
@@ -334,6 +348,7 @@
         applyLabel(info);
     }
 
+    @UiThread
     private void applyLabel(ItemInfoWithIcon info) {
         setText(info.title);
         if (info.contentDescription != null) {
@@ -483,6 +498,10 @@
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
+        if (mDisplay == DISPLAY_TASKBAR) {
+            // TODO: support notification dots in Taskbar
+            return;
+        }
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
             Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
@@ -635,9 +654,7 @@
             setContentDescription(info.contentDescription != null
                     ? info.contentDescription : "");
         } else if (progressLevel > 0) {
-            setContentDescription(getContext()
-                    .getString(R.string.app_downloading_title, info.title,
-                            NumberFormat.getPercentInstance().format(progressLevel * 0.01)));
+            setDownloadStateContentDescription(info, progressLevel);
         } else {
             setContentDescription(getContext()
                     .getString(R.string.app_waiting_download_title, info.title));
@@ -713,6 +730,24 @@
         }
     }
 
+    private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) {
+        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+                != 0) {
+            String percentageString = NumberFormat.getPercentInstance()
+                    .format(progressLevel * 0.01);
+            if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+                setContentDescription(getContext()
+                        .getString(
+                            R.string.app_installing_title, info.title, percentageString));
+            } else if ((info.runtimeStatusFlags
+                    & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) {
+                setContentDescription(getContext()
+                        .getString(
+                            R.string.app_downloading_title, info.title, percentageString));
+            }
+        }
+    }
+
     /**
      * Sets the icon for this view based on the layout direction.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0274775..2334267 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1917,6 +1917,13 @@
 
     @Override
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+        if (isViewInTaskbar(v)) {
+            // Start the activity without the hacky workarounds below, which assume the View was
+            // clicked when Launcher was resumed and will be hidden until Launcher is re-resumed
+            // (this isn't the case for Taskbar).
+            return super.startActivitySafely(v, intent, item);
+        }
+
         if (!hasBeenResumed()) {
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
@@ -2818,6 +2825,13 @@
                 .start();
     }
 
+    /**
+     * @return Whether the View is in the same window as the Taskbar window.
+     */
+    public boolean isViewInTaskbar(View v) {
+        return false;
+    }
+
     private static class NonConfigInstance {
         public Configuration config;
         public Bitmap snapshot;
diff --git a/src/com/android/launcher3/LauncherAppTransitionManager.java b/src/com/android/launcher3/LauncherAppTransitionManager.java
index ac3ad9f..0fa441a 100644
--- a/src/com/android/launcher3/LauncherAppTransitionManager.java
+++ b/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -50,7 +50,7 @@
         return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height);
     }
 
-    public boolean supportsAdaptiveIconAnimation() {
+    public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return false;
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
index 1cf98e1..f6e54aa 100644
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -164,8 +164,11 @@
                     @Override
                     public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
                         if (DEBUG) {
-                            Log.d(TAG, "Listener.onCancelled ctrl=" + controller
-                                    + " mAnimationController=" + mAnimationController);
+                            // Keep the verbose logging to chase down IME not showing up issue.
+                            // b/178904132
+                            Log.e(TAG, "Listener.onCancelled ctrl=" + controller
+                                    + " mAnimationController=" + mAnimationController,
+                                    new Exception());
                         }
                         if (mState == State.DRAG_START_BOTTOM) {
                             mState = State.DRAG_START_BOTTOM_IME_CANCELLED;
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 304d496..ce824df 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -24,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
@@ -34,10 +35,12 @@
 import android.util.Pair;
 import android.util.Property;
 import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.util.Themes;
 
 import java.lang.ref.WeakReference;
 
@@ -77,6 +80,9 @@
     private static final SparseArray<WeakReference<Pair<Path, Bitmap>>> sShadowCache =
             new SparseArray<>();
 
+    private static final int PRELOAD_ACCENT_COLOR_INDEX = 0;
+    private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
+
     private final Matrix mTmpMatrix = new Matrix();
     private final PathMeasure mPathMeasure = new PathMeasure();
 
@@ -91,6 +97,9 @@
 
     private Bitmap mShadowBitmap;
     private final int mIndicatorColor;
+    private final int mSystemAccentColor;
+    private final int mSystemBackgroundColor;
+    private final boolean mIsDarkMode;
 
     private int mTrackAlpha;
     private float mTrackLength;
@@ -104,11 +113,23 @@
 
     private ObjectAnimator mCurrentAnim;
 
+    private boolean mIsStartable;
+
     public PreloadIconDrawable(ItemInfoWithIcon info, Context context) {
-        this(info, IconPalette.getPreloadProgressColor(context, info.bitmap.color));
+        this(
+                info,
+                IconPalette.getPreloadProgressColor(context, info.bitmap.color),
+                getPreloadColors(context),
+            (context.getResources().getConfiguration().uiMode
+                    & Configuration.UI_MODE_NIGHT_MASK
+                    & Configuration.UI_MODE_NIGHT_YES) != 0) /* isDarkMode */;
     }
 
-    public PreloadIconDrawable(ItemInfoWithIcon info, int indicatorColor) {
+    public PreloadIconDrawable(
+            ItemInfoWithIcon info,
+            int indicatorColor,
+            int[] preloadColors,
+            boolean isDarkMode) {
         super(info.bitmap);
         mItem = info;
         mShapePath = getShapePath();
@@ -120,9 +141,12 @@
         mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
         mIndicatorColor = indicatorColor;
 
-        setInternalProgress(0);
+        mSystemAccentColor = preloadColors[PRELOAD_ACCENT_COLOR_INDEX];
+        mSystemBackgroundColor = preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX];
+        mIsDarkMode = isDarkMode;
 
-        setIsDisabled(!info.isAppStartable());
+        setInternalProgress(info.getProgressLevel());
+        setIsStartable(info.isAppStartable());
     }
 
     @Override
@@ -148,7 +172,7 @@
     }
 
     private Bitmap getShadowBitmap(int width, int height, float shadowRadius) {
-        int key = (width << 16) | height;
+        int key = ((width << 16) | height) * (mIsDarkMode ? -1 : 1);
         WeakReference<Pair<Path, Bitmap>> shadowRef = sShadowCache.get(key);
         Pair<Path, Bitmap> cache = shadowRef != null ? shadowRef.get() : null;
         Bitmap shadow = cache != null && cache.first.equals(mShapePath) ? cache.second : null;
@@ -157,8 +181,9 @@
         }
         shadow = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         Canvas c = new Canvas(shadow);
-        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, COLOR_SHADOW);
-        mProgressPaint.setColor(COLOR_TRACK);
+        mProgressPaint.setShadowLayer(shadowRadius, 0, 0, mIsStartable
+                ? COLOR_SHADOW : mSystemAccentColor);
+        mProgressPaint.setColor(mIsStartable ? COLOR_TRACK : mSystemBackgroundColor);
         mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
         c.drawPath(mScaledTrackPath, mProgressPaint);
         mProgressPaint.clearShadowLayer();
@@ -176,7 +201,7 @@
         }
 
         // Draw track.
-        mProgressPaint.setColor(mIndicatorColor);
+        mProgressPaint.setColor(mIsStartable ? mIndicatorColor : mSystemAccentColor);
         mProgressPaint.setAlpha(mTrackAlpha);
         if (mShadowBitmap != null) {
             canvas.drawBitmap(mShadowBitmap, bounds.left, bounds.top, mProgressPaint);
@@ -215,6 +240,14 @@
         return !mRanFinishAnimation;
     }
 
+    /** Sets whether this icon should display the startable app UI. */
+    public void setIsStartable(boolean isStartable) {
+        if (mIsStartable != isStartable) {
+            mIsStartable = isStartable;
+            setIsDisabled(!isStartable);
+        }
+    }
+
     private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
         if (mCurrentAnim != null) {
             mCurrentAnim.cancel();
@@ -295,6 +328,18 @@
         invalidateSelf();
     }
 
+    private static int[] getPreloadColors(Context context) {
+        Context dayNightThemeContext = new ContextThemeWrapper(
+                context, android.R.style.Theme_DeviceDefault_DayNight);
+        int[] preloadColors = new int[2];
+
+        preloadColors[PRELOAD_ACCENT_COLOR_INDEX] = Themes.getColorAccent(dayNightThemeContext);
+        preloadColors[PRELOAD_BACKGROUND_COLOR_INDEX] = Themes.getColorBackgroundFloating(
+                dayNightThemeContext);
+
+        return preloadColors;
+    }
+
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
@@ -305,13 +350,21 @@
     @Override
     public ConstantState getConstantState() {
         return new PreloadIconConstantState(
-                mBitmap, mIconColor, !mItem.isAppStartable(), mItem, mIndicatorColor);
+                mBitmap,
+                mIconColor,
+                !mItem.isAppStartable(),
+                mItem,
+                mIndicatorColor,
+                new int[] {mSystemAccentColor, mSystemBackgroundColor},
+                mIsDarkMode);
     }
 
     protected static class PreloadIconConstantState extends FastBitmapConstantState {
 
         protected final ItemInfoWithIcon mInfo;
         protected final int mIndicatorColor;
+        protected final int[] mPreloadColors;
+        protected final boolean mIsDarkMode;
         protected final int mLevel;
 
         public PreloadIconConstantState(
@@ -319,19 +372,24 @@
                 int iconColor,
                 boolean isDisabled,
                 ItemInfoWithIcon info,
-                int indicatorcolor) {
+                int indicatorColor,
+                int[] preloadColors,
+                boolean isDarkMode) {
             super(bitmap, iconColor, isDisabled);
             mInfo = info;
-            mIndicatorColor = indicatorcolor;
+            mIndicatorColor = indicatorColor;
+            mPreloadColors = preloadColors;
+            mIsDarkMode = isDarkMode;
             mLevel = info.getProgressLevel();
         }
 
         @Override
         public PreloadIconDrawable newDrawable() {
-            PreloadIconDrawable drawable = new PreloadIconDrawable(mInfo, mIndicatorColor);
-            drawable.setLevel(mLevel);
-            drawable.setIsDisabled(mIsDisabled);
-            return drawable;
+            return new PreloadIconDrawable(
+                    mInfo,
+                    mIndicatorColor,
+                    mPreloadColors,
+                    mIsDarkMode);
         }
 
         @Override
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index 47d214d..c9fb956 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -15,7 +15,10 @@
  */
 package com.android.launcher3.settings;
 
+import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -26,10 +29,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -130,7 +131,7 @@
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         EditText filterBox = view.findViewById(R.id.filter_box);
-        filterBox.setVisibility(View.VISIBLE);
+        filterBox.setVisibility(VISIBLE);
         filterBox.addTextChangedListener(new TextWatcher() {
             @Override
             public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
@@ -212,7 +213,7 @@
 
         Set<String> pluginActions = manager.getPluginActions();
 
-        ArrayMap<Pair<String, String>, ArrayList<Pair<String, ServiceInfo>>> plugins =
+        ArrayMap<Pair<String, String>, ArrayList<Pair<String, ResolveInfo>>> plugins =
                 new ArrayMap<>();
 
         Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
@@ -224,7 +225,7 @@
         for (String action : pluginActions) {
             String name = toName(action);
             List<ResolveInfo> result = pm.queryIntentServices(
-                    new Intent(action), MATCH_DISABLED_COMPONENTS);
+                    new Intent(action), MATCH_DISABLED_COMPONENTS | GET_RESOLVED_FILTER);
             for (ResolveInfo info : result) {
                 String packageName = info.serviceInfo.packageName;
                 if (!pluginPermissionApps.contains(packageName)) {
@@ -235,7 +236,7 @@
                 if (!plugins.containsKey(key)) {
                     plugins.put(key, new ArrayList<>());
                 }
-                plugins.get(key).add(Pair.create(name, info.serviceInfo));
+                plugins.get(key).add(Pair.create(name, info));
             }
         }
 
@@ -243,11 +244,11 @@
         plugins.forEach((key, si) -> {
             String packageName = key.first;
             List<ComponentName> componentNames = si.stream()
-                    .map(p -> new ComponentName(packageName, p.second.name))
+                    .map(p -> new ComponentName(packageName, p.second.serviceInfo.name))
                     .collect(Collectors.toList());
             if (!componentNames.isEmpty()) {
                 SwitchPreference pref = new PluginPreference(
-                        prefContext, si.get(0).second.applicationInfo, enabler, componentNames);
+                        prefContext, si.get(0).second, enabler, componentNames);
                 pref.setSummary("Plugins: "
                         + si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
                 mPluginsCategory.addPreference(pref);
@@ -341,21 +342,33 @@
     }
 
     private static class PluginPreference extends SwitchPreference {
-        private final boolean mHasSettings;
-        private final PreferenceDataStore mPluginEnabler;
         private final String mPackageName;
+        private final ResolveInfo mSettingsInfo;
+        private final PreferenceDataStore mPluginEnabler;
         private final List<ComponentName> mComponentNames;
 
-        PluginPreference(Context prefContext, ApplicationInfo info,
+        PluginPreference(Context prefContext, ResolveInfo pluginInfo,
                 PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
             super(prefContext);
             PackageManager pm = prefContext.getPackageManager();
-            mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS)
-                    .setPackage(info.packageName), 0) != null;
-            mPackageName = info.packageName;
-            mComponentNames = componentNames;
+            mPackageName = pluginInfo.serviceInfo.applicationInfo.packageName;
+            Intent settingsIntent = new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName);
+            // If any Settings activity in app has category filters, set plugin action as category.
+            List<ResolveInfo> settingsInfos =
+                    pm.queryIntentActivities(settingsIntent, GET_RESOLVED_FILTER);
+            if (pluginInfo.filter != null) {
+                for (ResolveInfo settingsInfo : settingsInfos) {
+                    if (settingsInfo.filter != null && settingsInfo.filter.countCategories() > 0) {
+                        settingsIntent.addCategory(pluginInfo.filter.getAction(0));
+                        break;
+                    }
+                }
+            }
+
+            mSettingsInfo = pm.resolveActivity(settingsIntent, 0);
             mPluginEnabler = pluginEnabler;
-            setTitle(info.loadLabel(pm));
+            mComponentNames = componentNames;
+            setTitle(pluginInfo.loadLabel(pm));
             setChecked(isPluginEnabled());
             setWidgetLayoutResource(R.layout.switch_preference_with_settings);
         }
@@ -396,17 +409,14 @@
         @Override
         public void onBindViewHolder(PreferenceViewHolder holder) {
             super.onBindViewHolder(holder);
-            holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE
-                    : View.GONE);
-            holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE
-                    : View.GONE);
+            boolean hasSettings = mSettingsInfo != null;
+            holder.findViewById(R.id.settings).setVisibility(hasSettings ? VISIBLE : GONE);
+            holder.findViewById(R.id.divider).setVisibility(hasSettings ? VISIBLE : GONE);
             holder.findViewById(R.id.settings).setOnClickListener(v -> {
-                ResolveInfo result = v.getContext().getPackageManager().resolveActivity(
-                        new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0);
-                if (result != null) {
+                if (hasSettings) {
                     v.getContext().startActivity(new Intent().setComponent(
-                            new ComponentName(result.activityInfo.packageName,
-                                    result.activityInfo.name)));
+                            new ComponentName(mSettingsInfo.activityInfo.packageName,
+                                    mSettingsInfo.activityInfo.name)));
                 }
             });
             holder.itemView.setOnLongClickListener(v -> {
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 3122c68..2647d6f 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -302,7 +302,7 @@
                 intent.setPackage(null);
             }
         }
-        if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) {
+        if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation(v)) {
             // Preload the icon to reduce latency b/w swapping the floating view with the original.
             FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
         }
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index b74686f..55d17fc 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -81,6 +81,11 @@
         return getAttrColor(context, android.R.attr.colorAccent);
     }
 
+    /** Returns the floating background color attribute. */
+    public static int getColorBackgroundFloating(Context context) {
+        return getAttrColor(context, android.R.attr.colorBackgroundFloating);
+    }
+
     public static int getAttrColor(Context context, int attr) {
         TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
         int colorAccent = ta.getColor(0, 0);