Add TaskbarView as a child of Hotseat

Instead of keeping the Taskbar window showing above the home screen,
we now integrate a copy of TaskbarView directly into Launcher's view
hierarchy. Most TaskbarController calls apply to both TaskbarViews
(mTaskbarViewInApp and mTaskbarViewOnHome), except for calls related
to Hotseat, which only apply to mTasbkarViewInApp given the real
Hotseat will be showing with mTaskbarViewOnHome.

More cleanup will follow this change.

Test: All taskbar interactions (e.g. drag and drop) continue to work
Bug: 182512211
Bug: 171917176
Change-Id: I0f0b124f652daa85f866e1df8f9e2981540331a0
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 6e1aa1e..87649f7 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -18,6 +18,4 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/taskbar_divider_thickness"
     android:layout_height="@dimen/taskbar_divider_height"
-    android:layout_marginStart="@dimen/taskbar_icon_spacing"
-    android:layout_marginEnd="@dimen/taskbar_icon_spacing"
     android:background="@color/taskbar_divider" />
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_view.xml b/quickstep/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..34a88ea
--- /dev/null
+++ b/quickstep/res/layout/taskbar_view.xml
@@ -0,0 +1,26 @@
+<?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.taskbar.TaskbarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/taskbar_view"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/taskbar_size"
+    android:background="@android:color/transparent"
+    android:layout_gravity="bottom"
+    android:gravity="center"
+    android:visibility="gone" />
+
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index c4becf1..9ab49ce 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.taskbar.TaskbarStateHandler;
+import com.android.launcher3.taskbar.TaskbarView;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
@@ -243,9 +244,10 @@
             mTaskbarController = null;
         }
         if (mDeviceProfile.isTaskbarPresent) {
+            TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView();
             TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
             mTaskbarController = new TaskbarController(this,
-                    taskbarActivityContext.getTaskbarContainerView());
+                    taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
             mTaskbarController.init();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8312b82..fc5e2c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -54,10 +54,7 @@
         return mTaskbarContainerView;
     }
 
-    /**
-     * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
-     * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
-     */
+    @Override
     public LayoutInflater getLayoutInflater() {
         return mLayoutInflater;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 528f43e..5202d91 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -84,7 +84,7 @@
     private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
         return insetsInfo -> {
             if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
-                    || mTaskbarView.isDraggingItem()) {
+                    || mTaskbarView.getVisibility() != VISIBLE || mTaskbarView.isDraggingItem()) {
                 // We're invisible or dragging out of taskbar, let touches pass through us.
                 insetsInfo.touchableRegion.setEmpty();
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index f652961..abf6d54 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,8 +19,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_TASKBAR;
-import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -43,7 +41,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
@@ -54,6 +51,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.AnimatedFloat;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,7 +68,8 @@
     private static final String WINDOW_TITLE = "Taskbar";
 
     private final TaskbarContainerView mTaskbarContainerView;
-    private final TaskbarView mTaskbarView;
+    private final TaskbarView mTaskbarViewInApp;
+    private final TaskbarView mTaskbarViewOnHome;
     private final BaseQuickstepLauncher mLauncher;
     private final WindowManager mWindowManager;
     // Layout width and height of the Taskbar in the default state.
@@ -91,14 +90,17 @@
 
     private @Nullable Animator mAnimator;
     private boolean mIsAnimatingToLauncher;
+    private boolean mIsAnimatingToApp;
 
     public TaskbarController(BaseQuickstepLauncher launcher,
-            TaskbarContainerView taskbarContainerView) {
+            TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) {
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
         mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
-        mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-        mTaskbarView.construct(createTaskbarViewCallbacks());
+        mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+        mTaskbarViewInApp.construct(createTaskbarViewCallbacks());
+        mTaskbarViewOnHome = taskbarViewOnHome;
+        mTaskbarViewOnHome.construct(createTaskbarViewCallbacks());
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
@@ -115,12 +117,13 @@
         return new TaskbarVisibilityControllerCallbacks() {
             @Override
             public void updateTaskbarBackgroundAlpha(float alpha) {
-                mTaskbarView.setBackgroundAlpha(alpha);
+                mTaskbarViewInApp.setBackgroundAlpha(alpha);
             }
 
             @Override
             public void updateTaskbarVisibilityAlpha(float alpha) {
                 mTaskbarContainerView.setAlpha(alpha);
+                mTaskbarViewOnHome.setAlpha(alpha);
             }
         };
     }
@@ -196,7 +199,7 @@
             public View.OnLongClickListener getItemOnLongClickListener() {
                 return view -> {
                     if (mLauncher.hasBeenResumed() && view.getTag() instanceof ItemInfo) {
-                        alignRealHotseatWithTaskbar();
+                        // TODO: remove this path
                         return mDragController.startWorkspaceDragOnLongClick(view);
                     } else {
                         return mDragController.startSystemDragOnLongClick(view);
@@ -205,11 +208,24 @@
             }
 
             @Override
-            public int getEmptyHotseatViewVisibility() {
+            public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) {
                 // When on the home screen, we want the empty hotseat views to take up their full
                 // space so that the others line up with the home screen hotseat.
-                return mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
-                        ? View.INVISIBLE : View.GONE;
+                boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
+                        || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
+                return isOnHomeScreen ? View.INVISIBLE : View.GONE;
+            }
+
+            @Override
+            public float getNonIconScale(TaskbarView taskbarView) {
+                return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f;
+            }
+
+            @Override
+            public void onItemPositionsChanged(TaskbarView taskbarView) {
+                if (taskbarView == mTaskbarViewOnHome) {
+                    alignRealHotseatWithTaskbar();
+                }
             }
         };
     }
@@ -218,7 +234,7 @@
         return new TaskbarHotseatControllerCallbacks() {
             @Override
             public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
-                mTaskbarView.updateHotseatItems(hotseatItemInfos);
+                mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos);
                 mLatestLoadedHotseatItems = hotseatItemInfos;
                 dedupeAndUpdateRecentItems();
             }
@@ -235,7 +251,8 @@
 
             @Override
             public void updateRecentTaskAtIndex(int taskIndex, Task task) {
-                mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
+                mTaskbarViewInApp.updateRecentTaskAtIndex(taskIndex, task);
+                mTaskbarViewOnHome.updateRecentTaskAtIndex(taskIndex, task);
             }
         };
     }
@@ -244,16 +261,20 @@
      * Initializes the Taskbar, including adding it to the screen.
      */
     public void init() {
-        mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
+        mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(),
                 mRecentsController.getNumRecentIcons());
-        mTaskbarContainerView.init(mTaskbarView);
+        mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(),
+                mRecentsController.getNumRecentIcons());
+        mTaskbarContainerView.init(mTaskbarViewInApp);
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
         mTaskbarVisibilityController.init();
         mHotseatController.init();
         mRecentsController.init();
 
-        SCALE_PROPERTY.set(mTaskbarView, mLauncher.hasBeenResumed() ? getTaskbarScaleOnHome() : 1f);
+        SCALE_PROPERTY.set(mTaskbarViewInApp, mLauncher.hasBeenResumed()
+                ? getTaskbarScaleOnHome() : 1f);
+        updateWhichTaskbarViewIsVisible();
     }
 
     private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -274,7 +295,8 @@
             mAnimator.end();
         }
 
-        mTaskbarView.cleanup();
+        mTaskbarViewInApp.cleanup();
+        mTaskbarViewOnHome.cleanup();
         mTaskbarContainerView.cleanup();
         removeFromWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(null);
@@ -313,7 +335,7 @@
         TaskbarContainerView.LayoutParams taskbarLayoutParams =
                 new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
         taskbarLayoutParams.gravity = gravity;
-        mTaskbarView.setLayoutParams(taskbarLayoutParams);
+        mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams);
 
         mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
     }
@@ -330,7 +352,6 @@
             mAnimator = createAnimToLauncher(null, duration);
         } else {
             mAnimator = createAnimToApp(duration);
-            replaceTaskbarWithHotseatOrViceVersa();
         }
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -351,36 +372,41 @@
         if (toState != null) {
             mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
         }
-        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(),
+        anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(),
                 getTaskbarScaleOnHome(), LINEAR);
 
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mIsAnimatingToLauncher = true;
-                mTaskbarView.updateHotseatItemsVisibility();
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsAnimatingToLauncher = false;
+                updateWhichTaskbarViewIsVisible();
             }
         });
 
-        anim.addOnFrameCallback(this::alignRealHotseatWithTaskbar);
-
         return anim.buildAnim();
     }
 
     private Animator createAnimToApp(long duration) {
         PendingAnimation anim = new PendingAnimation(duration);
         anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
-        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(), 1f, LINEAR);
+        anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(), 1f, LINEAR);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mTaskbarView.updateHotseatItemsVisibility();
-                setReplaceTaskbarWithHotseat(false);
+                mIsAnimatingToApp = true;
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
+                updateWhichTaskbarViewIsVisible();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIsAnimatingToApp = false;
             }
         });
         return anim.buildAnim();
@@ -405,11 +431,11 @@
      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
      */
     public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
-        return mTaskbarView.isEventOverAnyItem(ev);
+        return mTaskbarViewInApp.isEventOverAnyItem(ev);
     }
 
     public boolean isDraggingItem() {
-        return mTaskbarView.isDraggingItem();
+        return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem();
     }
 
     private void dedupeAndUpdateRecentItems() {
@@ -454,7 +480,8 @@
             tasksArray[tasksArray.length - 1 - i] = task;
         }
 
-        mTaskbarView.updateRecentTasks(tasksArray);
+        mTaskbarViewInApp.updateRecentTasks(tasksArray);
+        mTaskbarViewOnHome.updateRecentTasks(tasksArray);
         mRecentsController.loadIconsForTasks(tasksArray);
     }
 
@@ -475,10 +502,11 @@
         int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
         int hotseatTopDiff = hotseatHeight - grid.taskbarSize;
 
-        mTaskbarView.getHotseatBoundsAtScale(getTaskbarScaleOnHome()).roundOut(hotseatBounds);
-        mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top + hotseatTopDiff,
-                mTaskbarView.getWidth() - hotseatBounds.right,
-                mTaskbarView.getHeight() - hotseatBounds.bottom);
+        mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
+        mLauncher.getHotseat().setPadding(hotseatBounds.left,
+                hotseatBounds.top + hotseatTopDiff,
+                mTaskbarViewOnHome.getWidth() - hotseatBounds.right,
+                mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom);
     }
 
     /**
@@ -486,36 +514,32 @@
      * show the real one instead.
      */
     public void onLauncherDragLayerHierarchyChanged() {
-        replaceTaskbarWithHotseatOrViceVersa();
+        // TODO: remove, as this is a no-op now
     }
 
-    private void replaceTaskbarWithHotseatOrViceVersa() {
-        boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
-                TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
-        if (!mLauncher.hasBeenResumed()) {
-            replaceTaskbarWithHotseat = false;
-        }
-        setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
-
-        boolean hideTaskbar = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
-                TYPE_HIDE_TASKBAR) != null;
-        mTaskbarVisibilityController.animateToVisibilityForFloatingView(hideTaskbar ? 0f : 1f);
-    }
-
-    private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
-        Hotseat hotseat = mLauncher.getHotseat();
-        if (replaceTaskbarWithHotseat) {
-            alignRealHotseatWithTaskbar();
-            hotseat.getReplaceTaskbarAlpha().setValue(1f);
-            mTaskbarView.setHotseatViewsHidden(true);
+    private void updateWhichTaskbarViewIsVisible() {
+        boolean isInApp = !mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
+                || mIsAnimatingToApp;
+        if (isInApp) {
+            mTaskbarViewInApp.setVisibility(View.VISIBLE);
+            mTaskbarViewOnHome.setVisibility(View.INVISIBLE);
+            mLauncher.getHotseat().setIconsAlpha(0);
         } else {
-            hotseat.getReplaceTaskbarAlpha().setValue(0f);
-            mTaskbarView.setHotseatViewsHidden(false);
+            mTaskbarViewInApp.setVisibility(View.INVISIBLE);
+            mTaskbarViewOnHome.setVisibility(View.VISIBLE);
+            mLauncher.getHotseat().setIconsAlpha(1);
         }
     }
 
-    private float getTaskbarScaleOnHome() {
-        return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
+    /**
+     * Returns the ratio of the taskbar icon size on home vs in an app.
+     */
+    public float getTaskbarScaleOnHome() {
+        DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext()
+                .getDeviceProfile();
+        DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
+                .getDeviceProfile();
+        return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
     }
 
     /**
@@ -561,7 +585,10 @@
     protected interface TaskbarViewCallbacks {
         View.OnClickListener getItemOnClickListener();
         View.OnLongClickListener getItemOnLongClickListener();
-        int getEmptyHotseatViewVisibility();
+        int getEmptyHotseatViewVisibility(TaskbarView taskbarView);
+        /** Returns how much to scale non-icon elements such as spacing and dividers. */
+        float getNonIconScale(TaskbarView taskbarView);
+        void onItemPositionsChanged(TaskbarView taskbarView);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 1d762e9..3567c17 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,11 +15,14 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -36,6 +39,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.FolderIcon;
@@ -48,20 +52,23 @@
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
 
     private final ColorDrawable mBackgroundDrawable;
-    private final int mItemMarginLeftRight;
+    private final int mDividerWidth;
+    private final int mDividerHeight;
     private final int mIconTouchSize;
     private final boolean mIsRtl;
     private final int mTouchSlop;
     private final RectF mTempDelegateBounds = new RectF();
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
-    private final Matrix mTempMatrix = new Matrix();
 
     // Initialized in TaskbarController constructor.
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+    // Scale on elements that aren't icons.
+    private float mNonIconScale;
+    private int mItemMarginLeftRight;
 
     // Initialized in init().
     private LayoutTransition mLayoutTransition;
@@ -78,7 +85,6 @@
     private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
-    private boolean mIsHotseatHidden;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -99,7 +105,8 @@
 
         Resources resources = getResources();
         mBackgroundDrawable = (ColorDrawable) getBackground();
-        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mDividerWidth = resources.getDimensionPixelSize(R.dimen.taskbar_divider_thickness);
+        mDividerHeight = resources.getDimensionPixelSize(R.dimen.taskbar_divider_height);
         mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
         mIsRtl = Utilities.isRtl(resources);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -107,11 +114,16 @@
 
     protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
         mControllerCallbacks = taskbarViewCallbacks;
+        mNonIconScale = mControllerCallbacks.getNonIconScale(this);
+        mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
     }
 
     protected void init(int numHotseatIcons, int numRecentIcons) {
         mLayoutTransition = new LayoutTransition();
-        setLayoutTransitionsEnabled(true);
+        addUpdateListenerForAllLayoutTransitions(
+                () -> mControllerCallbacks.onItemPositionsChanged(this));
+        setLayoutTransition(mLayoutTransition);
 
         mHotseatStartIndex = 0;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
@@ -125,12 +137,30 @@
         updateRecentTasks(new Task[numRecentIcons]);
     }
 
-    private void setLayoutTransitionsEnabled(boolean enabled) {
-        setLayoutTransition(enabled ? mLayoutTransition : null);
+    private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) {
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate);
+    }
+
+    private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) {
+        Animator anim = mLayoutTransition.getAnimator(transitionType);
+        if (anim instanceof ValueAnimator) {
+            ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run());
+        } else {
+            AnimatorSet animSet = new AnimatorSet();
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> onUpdate.run());
+            animSet.playTogether(anim, updateAnim);
+            mLayoutTransition.setAnimator(transitionType, animSet);
+        }
     }
 
     protected void cleanup() {
         removeAllViews();
+        mHotseatRecentsDivider = null;
     }
 
     /**
@@ -170,12 +200,11 @@
             if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
                     || needsReinflate) {
                 removeView(hotseatView);
-                TaskbarActivityContext activityContext =
-                        ActivityContext.lookupContext(getContext());
+                ActivityContext activityContext = ActivityContext.lookupContext(getContext());
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
-                            activityContext, this, folderInfo);
+                            ActivityContext.lookupContext(getContext()), this, folderInfo);
                     folderIcon.setTextVisible(false);
                     hotseatView = folderIcon;
                 } else {
@@ -216,22 +245,12 @@
         }
     }
 
-    /**
-     * Hides or shows the hotseat items immediately (without layout transitions).
-     */
-    protected void setHotseatViewsHidden(boolean hidden) {
-        mIsHotseatHidden = hidden;
-        setLayoutTransitionsEnabled(false);
-        updateHotseatItemsVisibility();
-        setLayoutTransitionsEnabled(true);
-    }
-
     private void updateHotseatItemVisibility(View hotseatView) {
         if (hotseatView.getTag() != null) {
-            hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE);
+            hotseatView.setVisibility(VISIBLE);
         } else {
             int oldVisibility = hotseatView.getVisibility();
-            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
+            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this);
             hotseatView.setVisibility(newVisibility);
             if (oldVisibility == GONE && newVisibility != GONE) {
                 // By default, the layout transition only runs when going to VISIBLE,
@@ -243,7 +262,11 @@
 
     private View addDivider(int dividerIndex) {
         View divider = inflate(R.layout.taskbar_divider);
-        addView(divider, dividerIndex);
+        LayoutParams lp = new LayoutParams(mDividerWidth, mDividerHeight);
+        lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+        divider.setScaleX(mNonIconScale);
+        divider.setScaleY(mNonIconScale);
+        addView(divider, dividerIndex, lp);
         return divider;
     }
 
@@ -437,9 +460,9 @@
     }
 
     /**
-     * @return The bounding box of where the hotseat elements will be when we reach the given scale.
+     * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
      */
-    protected RectF getHotseatBoundsAtScale(float taskbarViewScale) {
+    protected RectF getHotseatBounds() {
         View firstHotseatView = null, lastHotseatView = null;
         for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
             View child = getChildAt(i);
@@ -455,14 +478,11 @@
         }
         View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
         View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
-        RectF hotseatBounds = new RectF(
+        return new RectF(
                 leftmostHotseatView.getLeft() - mItemMarginLeftRight,
                 leftmostHotseatView.getTop(),
                 rightmostHotseatView.getRight() + mItemMarginLeftRight,
                 rightmostHotseatView.getBottom());
-        mTempMatrix.setScale(taskbarViewScale, taskbarViewScale, getPivotX(), getPivotY());
-        mTempMatrix.mapRect(hotseatBounds);
-        return hotseatBounds;
     }
 
     // FolderIconParent implemented methods.
@@ -493,7 +513,12 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
-        return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+        return ActivityContext.lookupContext(getContext()).getLayoutInflater()
+                .inflate(layoutResId, this, false);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Ignore, we just implement Insettable to draw behind system insets.
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ae644cd..3faf72a 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -112,20 +112,32 @@
         }
 
         // Set up springs for the hotseat and qsb.
-        ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
+        ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
         if (grid.isVerticalBarLayout()) {
-            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
-                View child = hotseatChild.getChildAt(i);
+            for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatIcons.getChildAt(i);
                 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
                 addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
             }
         } else {
-            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
-                View child = hotseatChild.getChildAt(i);
-                addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+            final int hotseatRow, qsbRow, taskbarRow;
+            if (grid.isTaskbarPresent) {
+                qsbRow = grid.inv.numRows + 1;
+                hotseatRow = grid.inv.numRows + 2;
+            } else {
+                hotseatRow = grid.inv.numRows + 1;
+                qsbRow = grid.inv.numRows + 2;
+            }
+            // Taskbar and hotseat overlap.
+            taskbarRow = hotseatRow;
+
+            for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatIcons.getChildAt(i);
+                addStaggeredAnimationForView(child, hotseatRow, totalRows);
             }
 
-            addStaggeredAnimationForView(hotseat.getQsb(), grid.inv.numRows + 2, totalRows);
+            addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+            addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
         }
 
         if (animateOverviewScrim) {
diff --git a/res/layout/taskbar_view.xml b/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..96ae43d
--- /dev/null
+++ b/res/layout/taskbar_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<Space
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="0dp"
+    android:visibility="gone" />
\ No newline at end of file
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index af4a843..4049ed6 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -29,8 +29,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.MultiValueAlpha;
-
 import java.util.function.Consumer;
 
 /**
@@ -38,10 +36,6 @@
  */
 public class Hotseat extends CellLayout implements Insettable {
 
-    private static final int ALPHA_INDEX_STATE = 0;
-    private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
-
     // Ratio of empty space, qsb should take up to appear visually centered.
     public static final float QSB_CENTER_FACTOR = .325f;
 
@@ -52,10 +46,12 @@
     @Nullable
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
-    private final MultiValueAlpha mMultiValueAlpha;
     private final View mQsb;
     private final int mQsbHeight;
 
+    private final View mTaskbarView;
+    private final int mTaskbarViewHeight;
+
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -66,12 +62,15 @@
 
     public Hotseat(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX);
-        mMultiValueAlpha.setUpdateVisibility(true);
 
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
         mQsbHeight = mQsb.getLayoutParams().height;
         addView(mQsb);
+
+        mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false);
+        mTaskbarViewHeight = mTaskbarView.getLayoutParams().height;
+        // We want taskbar in the back so its background applies to Hotseat as well.
+        addView(mTaskbarView, 0);
     }
 
     /**
@@ -193,6 +192,8 @@
         int width = getShortcutsAndWidgets().getMeasuredWidth();
         mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+        mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY));
     }
 
     @Override
@@ -212,6 +213,13 @@
                 - (dp.isTaskbarPresent ? dp.taskbarSize : dp.getInsets().bottom);
         int top = bottom - mQsbHeight;
         mQsb.layout(left, top, right, bottom);
+
+        int taskbarWidth = mTaskbarView.getMeasuredWidth();
+        left = (r - l - taskbarWidth) / 2;
+        right = left + taskbarWidth;
+        bottom = b - t;
+        top = bottom - mTaskbarViewHeight;
+        mTaskbarView.layout(left, top, right, bottom);
     }
 
     /**
@@ -221,12 +229,11 @@
         return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
     }
 
-    public MultiValueAlpha.AlphaProperty getStateAlpha() {
-        return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE);
-    }
-
-    public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() {
-        return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR);
+    /**
+     * Sets the alpha value of just our ShortcutAndWidgetContainer.
+     */
+    public void setIconsAlpha(float alpha) {
+        getShortcutsAndWidgets().setAlpha(alpha);
     }
 
     /**
@@ -235,4 +242,11 @@
     public View getQsb() {
         return mQsb;
     }
+
+    /**
+     * Returns the Taskbar inside hotseat
+     */
+    public View getTaskbarView() {
+        return mTaskbarView;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index e9a3495..0f8c6b9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -185,12 +185,7 @@
     }
 
     public int getVisibleElements(Launcher launcher) {
-        DeviceProfile deviceProfile = launcher.getDeviceProfile();
-        int flags = WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
-        if (!deviceProfile.isTaskbarPresent) {
-            flags |= HOTSEAT_ICONS;
-        }
-        return flags;
+        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
     }
 
     /**
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 412754e..cfcf0a0 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
-import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.plugins.ResourceProvider;
 
 /**
@@ -135,8 +134,8 @@
             }
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE,
-                    hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
+            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
+                    config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
             float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     workspacePageIndicatorAlpha, fadeInterpolator);
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 505c6ce..71aa4ac 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Rect;
+import android.view.LayoutInflater;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile;
@@ -67,13 +68,28 @@
     }
 
     /**
+     * Returns a LayoutInflater that is cloned in this Context, so that Views inflated by it will
+     * have the same Context. (i.e. {@link #lookupContext(Context)} will find this ActivityContext.)
+     */
+    default LayoutInflater getLayoutInflater() {
+        if (this instanceof Context) {
+            Context context = (Context) this;
+            return LayoutInflater.from(context).cloneInContext(context);
+        }
+        return null;
+    }
+
+    /**
      * The root view to support drag-and-drop and popup support.
      */
     BaseDragLayer getDragLayer();
 
     DeviceProfile getDeviceProfile();
 
-    static <T extends ActivityContext> T lookupContext(Context context) {
+    /**
+     * Returns the ActivityContext associated with the given Context.
+     */
+    static <T extends Context & ActivityContext> T lookupContext(Context context) {
         if (context instanceof ActivityContext) {
             return (T) context;
         } else if (context instanceof ContextWrapper) {