Revert "Moving taskbar lifecycle to TouchInteractionService"

This reverts commit e215fb730bb3d4a357a2c4bf0c082d3c0ad69495.

Reason for revert: DroidMonitor-triggered revert due to breakage https://android-build.googleplex.com/builds/tests/view?invocationId=I13700009003387451&testResultId=TR89423459137251402, bug https://buganizer.corp.google.com/issues/188755902

Bug: 188755902
Change-Id: I4650136975b60f311499ee6ff5b27ab9a32d23d6
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index c436221..240fe55 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -22,29 +22,9 @@
 
     <com.android.launcher3.taskbar.TaskbarView
         android:id="@+id/taskbar_view"
-        android:layout_width="match_parent"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:gravity="center"
-        android:forceHasOverlappingRendering="false"
-        android:layout_gravity="bottom" >
-
-        <LinearLayout
-            android:id="@+id/system_button_layout"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
-            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
-            android:forceHasOverlappingRendering="false"
-            android:gravity="center" />
-
-        <LinearLayout
-            android:id="@+id/hotseat_icons_layout"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:forceHasOverlappingRendering="false"
-            android:gravity="center" />
-
-    </com.android.launcher3.taskbar.TaskbarView>
+        android:gravity="center"/>
 
     <com.android.launcher3.taskbar.ImeBarView
         android:id="@+id/ime_bar_view"
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/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 50453ac..735cb24 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -149,5 +149,4 @@
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
     <dimen name="taskbar_icon_spacing">8dp</dimen>
     <dimen name="taskbar_folder_margin">16dp</dimen>
-    <dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index bc2c125..e777ee7 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.NO_OFFSET;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
@@ -29,6 +30,7 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.ServiceConnection;
@@ -49,11 +51,13 @@
 import com.android.launcher3.statehandlers.BackButtonAlphaHandler;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarController;
-import com.android.launcher3.taskbar.TaskbarManager;
 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;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
@@ -63,7 +67,6 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -85,6 +88,8 @@
 
     private DepthController mDepthController = new DepthController(this);
     private QuickstepTransitionManager mAppTransitionManager;
+    private ServiceConnection mTisBinderConnection;
+    protected TouchInteractionService.TISBinder mTisBinder;
 
     /**
      * Reusable command for applying the back button alpha on the background thread.
@@ -95,20 +100,8 @@
 
     private OverviewActionsView mActionsView;
 
-    private @Nullable TaskbarManager mTaskbarManager;
     private @Nullable TaskbarController mTaskbarController;
-    private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
-            mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
-            mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName componentName) { }
-    };
     private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
-
     // Will be updated when dragging from taskbar.
     private @Nullable DragOptions mNextWorkspaceDragOptions = null;
     private SplitPlaceholderView mSplitPlaceholderView;
@@ -118,6 +111,24 @@
         super.onCreate(savedInstanceState);
         SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this);
         addMultiWindowModeChangedListener(mDepthController);
+        setupTouchInteractionServiceBinder();
+    }
+
+    private void setupTouchInteractionServiceBinder() {
+        Intent intent = new Intent(this, TouchInteractionService.class);
+        mTisBinderConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName componentName, IBinder binder) {
+                mTisBinder = ((TouchInteractionService.TISBinder) binder);
+                mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName componentName) {
+                mTisBinder = null;
+            }
+        };
+        bindService(intent, mTisBinderConnection, 0);
     }
 
     @Override
@@ -125,12 +136,15 @@
         mAppTransitionManager.onActivityDestroyed();
 
         SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
-
-
-        unbindService(mTisBinderConnection);
-        if (mTaskbarManager != null) {
-            mTaskbarManager.setLauncher(null);
+        if (mTaskbarController != null) {
+            mTaskbarController.cleanup();
+            mTaskbarController = null;
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(null);
+                unbindService(mTisBinderConnection);
+            }
         }
+
         super.onDestroy();
     }
 
@@ -257,12 +271,37 @@
         mAppTransitionManager = new QuickstepTransitionManager(this);
         mAppTransitionManager.registerRemoteAnimations();
 
-        bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
-
+        addTaskbarIfNecessary();
+        addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
     }
 
-    public void setTaskbarController(TaskbarController taskbarController) {
-        mTaskbarController = taskbarController;
+    @Override
+    public void onDisplayInfoChanged(Context context, DisplayController.Info info,
+            int flags) {
+        super.onDisplayInfoChanged(context, info, flags);
+        if ((flags & CHANGE_ACTIVE_SCREEN) != 0) {
+            addTaskbarIfNecessary();
+        }
+    }
+
+    private void addTaskbarIfNecessary() {
+        if (mTaskbarController != null) {
+            mTaskbarController.cleanup();
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(null);
+            }
+            mTaskbarController = null;
+        }
+        if (mDeviceProfile.isTaskbarPresent) {
+            TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView();
+            TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
+            mTaskbarController = new TaskbarController(this,
+                    taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
+            mTaskbarController.init();
+            if (mTisBinder != null) {
+                mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController);
+            }
+        }
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
@@ -301,9 +340,14 @@
     }
 
     @Override
+    public boolean isViewInTaskbar(View v) {
+        return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v);
+    }
+
     public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return mAppTransitionManager.hasControlRemoteAppTransitionPermission()
-                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
+                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get()
+                && !isViewInTaskbar(clickedView);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 80754a0..1b8fcb3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1252,6 +1252,7 @@
 
             final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView;
             final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
+            final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV);
             if (launchingFromWidget) {
                 composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,
                         wallpaperTargets, nonAppTargets);
@@ -1262,6 +1263,8 @@
                         launcherClosing);
                 addCujInstrumentation(
                         anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS);
+            } else if (launchingFromTaskbar) {
+                // TODO
             } else {
                 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
                         launcherClosing);
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
index 540f748..0d4130d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
@@ -16,17 +16,12 @@
 
 package com.android.launcher3.taskbar;
 
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-
 import android.annotation.DrawableRes;
+import android.content.Context;
 import android.view.View;
 import android.widget.ImageView;
 
 import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
 
 /**
  * Creates Buttons for Taskbar for 3 button nav.
@@ -34,46 +29,47 @@
  */
 public class ButtonProvider {
 
-    private final int mMarginLeftRight;
-    private final TaskbarActivityContext mContext;
+    private int mMarginLeftRight;
+    private final Context mContext;
 
-    public ButtonProvider(TaskbarActivityContext context) {
+    public ButtonProvider(Context context) {
         mContext = context;
-        mMarginLeftRight = context.getResources()
-                .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+    }
+
+    public void setMarginLeftRight(int margin) {
+        mMarginLeftRight = margin;
     }
 
     public View getBack() {
         // Back button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
+        return getButtonForDrawable(R.drawable.ic_sysbar_back);
     }
 
     public View getDown() {
         // Ime down button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
+        return getButtonForDrawable(R.drawable.ic_sysbar_back);
     }
 
     public View getHome() {
         // Home button
-        return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
+        return getButtonForDrawable(R.drawable.ic_sysbar_home);
     }
 
     public View getRecents() {
         // Recents button
-        return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
+        return getButtonForDrawable(R.drawable.ic_sysbar_recent);
     }
 
     public View getImeSwitcher() {
         // IME Switcher Button
-        return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
+        return getButtonForDrawable(R.drawable.ic_ime_switcher);
     }
 
-    private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
+    private View getButtonForDrawable(@DrawableRes int drawableId) {
         ImageView buttonView = new ImageView(mContext);
         buttonView.setImageResource(drawableId);
         buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
         buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
-        buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
         return buttonView;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
index 287caab..bb3669b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
@@ -26,6 +29,7 @@
 public class ImeBarView extends RelativeLayout {
 
     private ButtonProvider mButtonProvider;
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
     private View mImeView;
 
     public ImeBarView(Context context) {
@@ -40,9 +44,12 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public void init(ButtonProvider buttonProvider) {
+    public void construct(ButtonProvider buttonProvider) {
         mButtonProvider = buttonProvider;
+    }
 
+    public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) {
+        mControllerCallbacks = taskbarCallbacks;
         ActivityContext context = getActivityContext();
         RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
                 context.getDeviceProfile().iconSizePx,
@@ -57,16 +64,24 @@
 
         // Down Arrow
         View downView = mButtonProvider.getDown();
+        downView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_BACK));
         downView.setLayoutParams(downParams);
         downView.setRotation(-90);
         addView(downView);
 
         // IME switcher button
         mImeView = mButtonProvider.getImeSwitcher();
+        mImeView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_IME_SWITCH));
         mImeView.setLayoutParams(imeParams);
         addView(mImeView);
     }
 
+    public void cleanup() {
+        removeAllViews();
+    }
+
     public void setImeSwitcherVisibility(boolean show) {
         mImeView.setVisibility(show ? VISIBLE : GONE);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 70f2788..3af51d5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -15,164 +15,56 @@
  */
 package com.android.launcher3.taskbar;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-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.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
-
-import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.graphics.PixelFormat;
+import android.content.ContextWrapper;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.Display;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.WindowManager;
-import android.widget.Toast;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
-import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ActivityContext;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.launcher3.views.BaseDragLayer;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
  * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
  * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
  */
-public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext {
-
-    private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
-            SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
-    private static final String TAG = "TaskbarActivityContext";
-
-    private static final String WINDOW_TITLE = "Taskbar";
+public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
 
     private final DeviceProfile mDeviceProfile;
     private final LayoutInflater mLayoutInflater;
     private final TaskbarContainerView mTaskbarContainerView;
-    private final TaskbarIconController mIconController;
     private final MyDragController mDragController;
 
-    private final WindowManager mWindowManager;
-    private WindowManager.LayoutParams mWindowLayoutParams;
-
-    private final SysUINavigationMode.Mode mNavMode;
-    private final TaskbarNavButtonController mNavButtonController;
-
-    private final boolean mIsSafeModeEnabled;
-
-    @NonNull
-    private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
-    private final View.OnClickListener mOnTaskbarIconClickListener;
-    private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
-
-    public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
-            TaskbarNavButtonController buttonController) {
-        super(windowContext, Themes.getActivityThemeRes(windowContext));
-        mDeviceProfile = dp;
-        mNavButtonController = buttonController;
-        mNavMode = SysUINavigationMode.getMode(windowContext);
-        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
-                () -> getPackageManager().isSafeMode());
-
-        mOnTaskbarIconLongClickListener =
-                new TaskbarDragController(this)::startSystemDragOnLongClick;
-        mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
-
+    public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
+        super(launcher);
+        mDeviceProfile = launcher.getDeviceProfile().copy(this);
         float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
         float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
         mDeviceProfile.updateIconSize(iconScale, getResources());
 
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+
         mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater
                 .inflate(R.layout.taskbar, null, false);
-        mIconController = new TaskbarIconController(this, mTaskbarContainerView);
         mDragController = new MyDragController(this);
-
-        Display display = windowContext.getDisplay();
-        Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
-                ? windowContext.getApplicationContext()
-                : windowContext.getApplicationContext().createDisplayContext(display);
-        mWindowManager = c.getSystemService(WindowManager.class);
     }
 
-    public void init() {
-        mWindowLayoutParams = new WindowManager.LayoutParams(
-                MATCH_PARENT,
-                mDeviceProfile.taskbarSize,
-                TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSLUCENT);
-        mWindowLayoutParams.setTitle(WINDOW_TITLE);
-        mWindowLayoutParams.packageName = getPackageName();
-        mWindowLayoutParams.gravity = Gravity.BOTTOM;
-        mWindowLayoutParams.setFitInsetsTypes(0);
-        mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
-        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.setSystemApplicationOverlay(true);
-
-        WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
-        wmWrapper.setProvidesInsetsTypes(
-                mWindowLayoutParams,
-                new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
-        );
-
-        mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
-        mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
-    }
-
-    /**
-     * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
-     */
-    public void setTaskbarWindowHeight(int height) {
-        if (mWindowLayoutParams.height == height) {
-            return;
-        }
-        mWindowLayoutParams.height = height;
-        mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
-    }
-
-    public boolean canShowNavButtons() {
-        return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
+    public TaskbarContainerView getTaskbarContainerView() {
+        return mTaskbarContainerView;
     }
 
     @Override
@@ -181,7 +73,7 @@
     }
 
     @Override
-    public TaskbarContainerView getDragLayer() {
+    public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
         return mTaskbarContainerView;
     }
 
@@ -200,103 +92,6 @@
         return mDragController;
     }
 
-    /**
-     * Sets a new data-source for this taskbar instance
-     */
-    public void setUIController(@NonNull TaskbarUIController uiController) {
-        mUIController.onDestroy();
-        mUIController = uiController;
-        mIconController.setUIController(mUIController);
-        mUIController.onCreate();
-    }
-
-    /**
-     * Called when this instance of taskbar is no longer needed
-     */
-    public void onDestroy() {
-        setUIController(TaskbarUIController.DEFAULT);
-        mIconController.onDestroy();
-        mWindowManager.removeViewImmediate(mTaskbarContainerView);
-    }
-
-    void onNavigationButtonClick(@TaskbarButton int buttonType) {
-        mNavButtonController.onButtonClick(buttonType);
-    }
-
-    public TaskbarIconController getIconController() {
-        return mIconController;
-    }
-
-    /**
-     * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
-     */
-    protected void setTaskbarWindowFullscreen(boolean fullscreen) {
-        setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
-    }
-
-    protected void onTaskbarIconClicked(View view) {
-        Object tag = view.getTag();
-        if (tag instanceof Task) {
-            Task task = (Task) tag;
-            ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
-                    ActivityOptions.makeBasic());
-        } else if (tag instanceof FolderInfo) {
-            FolderIcon folderIcon = (FolderIcon) view;
-            Folder folder = folderIcon.getFolder();
-            setTaskbarWindowFullscreen(true);
-
-            getDragLayer().post(() -> {
-                folder.animateOpen();
-
-                folder.iterateOverItems((itemInfo, itemView) -> {
-                    itemView.setOnClickListener(mOnTaskbarIconClickListener);
-                    itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
-                    // To play haptic when dragging, like other Taskbar items do.
-                    itemView.setHapticFeedbackEnabled(true);
-                    return false;
-                });
-            });
-        } else if (tag instanceof WorkspaceItemInfo) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
-            if (info.isDisabled()) {
-                ItemClickHandler.handleDisabledItemClicked(info, this);
-            } else {
-                Intent intent = new Intent(info.getIntent())
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                try {
-                    if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) {
-                        Toast.makeText(this, R.string.safemode_shortcut_error,
-                                Toast.LENGTH_SHORT).show();
-                    } else  if (info.isPromise()) {
-                        intent = new PackageManagerHelper(this)
-                                .getMarketIntent(info.getTargetPackage())
-                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                        startActivity(intent);
-
-                    } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-                        String id = info.getDeepShortcutId();
-                        String packageName = intent.getPackage();
-                        getSystemService(LauncherApps.class)
-                                .startShortcut(packageName, id, null, null, info.user);
-                    } else if (info.user.equals(Process.myUserHandle())) {
-                        startActivity(intent);
-                    } else {
-                        getSystemService(LauncherApps.class).startMainActivity(
-                                intent.getComponent(), info.user, intent.getSourceBounds(), null);
-                    }
-                } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
-                    Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
-                            .show();
-                    Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e);
-                }
-            }
-        } else {
-            Log.e(TAG, "Unknown type clicked: " + tag);
-        }
-
-        AbstractFloatingView.closeAllOpenViews(this);
-    }
-
     private static class MyDragController extends DragController<TaskbarActivityContext> {
         MyDragController(TaskbarActivityContext activity) {
             super(activity);
@@ -311,8 +106,7 @@
         }
 
         @Override
-        protected void exitDrag() {
-        }
+        protected void exitDrag() { }
 
         @Override
         protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
index 815efb9..29f6935 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -21,7 +21,6 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.taskbar.TaskbarController.TaskbarAnimationControllerCallbacks;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -35,7 +34,7 @@
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
-    private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
+    private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
 
     // Background alpha.
     private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
@@ -56,7 +55,7 @@
             this::updateTranslationY);
 
     public TaskbarAnimationController(BaseQuickstepLauncher launcher,
-            TaskbarAnimationControllerCallbacks taskbarCallbacks) {
+            TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) {
         mLauncher = launcher;
         mTaskbarCallbacks = taskbarCallbacks;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 5034791..621bba7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -29,21 +32,22 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener;
 
 /**
  * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
  */
 public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
 
+    private final int[] mTempLoc = new int[2];
     private final int mFolderMargin;
     private final Paint mTaskbarBackgroundPaint;
 
-    private TaskbarIconController.Callbacks mControllerCallbacks;
-    private TaskbarView mTaskbarView;
+    // Initialized in TaskbarController constructor.
+    private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
 
-    private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+    // Initialized in init.
+    private TaskbarView mTaskbarView;
+    private ViewTreeObserverWrapper.OnComputeInsetsListener mTaskbarInsetsComputer;
 
     public TaskbarContainerView(@NonNull Context context) {
         this(context, null);
@@ -64,6 +68,15 @@
         mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
         mTaskbarBackgroundPaint = new Paint();
         mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+    }
+
+    protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) {
+        mControllerCallbacks = callbacks;
+    }
+
+    protected void init(TaskbarView taskbarView) {
+        mTaskbarView = taskbarView;
+        mTaskbarInsetsComputer = createTaskbarInsetsComputer();
         recreateControllers();
     }
 
@@ -72,24 +85,46 @@
         mControllers = new TouchController[0];
     }
 
-    public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
-        mControllerCallbacks = callbacks;
-        mTaskbarView = taskbarView;
+    private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
+        return insetsInfo -> {
+            if (mControllerCallbacks.isTaskbarTouchable()) {
+                 // Accept touches anywhere in our bounds.
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+            } else {
+                // Let touches pass through us.
+                insetsInfo.touchableRegion.setEmpty();
+                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+            }
+
+            // TaskbarContainerView provides insets to other apps based on contentInsets. These
+            // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
+            // to show a floating view like Folder. Thus, we set the contentInsets to be where
+            // mTaskbarView is, since its position never changes and insets rather than overlays.
+            int[] loc = mTempLoc;
+            float scale = mTaskbarView.getScaleX();
+            float translationY = mTaskbarView.getTranslationY();
+            mTaskbarView.setScaleX(1);
+            mTaskbarView.setScaleY(1);
+            mTaskbarView.setTranslationY(0);
+            mTaskbarView.getLocationInWindow(loc);
+            mTaskbarView.setScaleX(scale);
+            mTaskbarView.setScaleY(scale);
+            mTaskbarView.setTranslationY(translationY);
+            insetsInfo.contentInsets.left = loc[0];
+            insetsInfo.contentInsets.top = loc[1];
+            insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth());
+            insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight());
+        };
     }
 
-    private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
-        if (mControllerCallbacks != null) {
-            mControllerCallbacks.updateInsetsTouchability(insetsInfo);
-        }
-    }
-
-    protected void onDestroy() {
+    protected void cleanup() {
         ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
+
         ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
                 mTaskbarInsetsComputer);
     }
@@ -98,7 +133,7 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        onDestroy();
+        cleanup();
     }
 
     @Override
@@ -108,25 +143,10 @@
         return true;
     }
 
-    public void updateImeBarVisibilityAlpha(float alpha) {
-        if (mControllerCallbacks != null) {
-            mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
-        }
-    }
-
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
-        if (mControllerCallbacks != null) {
-            mControllerCallbacks.onContainerViewRemoved();
-        }
-    }
-
-    @Override
-    protected void dispatchDraw(Canvas canvas) {
-        canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
-                canvas.getHeight(), mTaskbarBackgroundPaint);
-        super.dispatchDraw(canvas);
+        mControllerCallbacks.onViewRemoved();
     }
 
     /**
@@ -138,6 +158,16 @@
         return boundingBox;
     }
 
+    protected TaskbarActivityContext getTaskbarActivityContext() {
+        return mActivity;
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
+                canvas.getHeight(), mTaskbarBackgroundPaint);
+        super.dispatchDraw(canvas);
+    }
 
     /**
      * Sets the alpha of the background color behind all the Taskbar contents.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index cdae5be..6084e10 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -15,83 +15,105 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+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.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
+import android.inputmethodservice.InputMethodService;
+import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
-
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.TouchInteractionService.TaskbarOverviewProxyDelegate;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.WindowManagerWrapper;
 
 /**
- * A data source which integrates with a Launcher instance
- * TODO: Rename to have Launcher prefix
+ * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
  */
+public class TaskbarController implements TaskbarOverviewProxyDelegate {
 
-public class TaskbarController extends TaskbarUIController {
+    private static final String WINDOW_TITLE = "Taskbar";
+
+    private final TaskbarContainerView mTaskbarContainerView;
+    private final TaskbarView mTaskbarViewInApp;
+    private final TaskbarView mTaskbarViewOnHome;
+    private final ImeBarView mImeBarView;
 
     private final BaseQuickstepLauncher mLauncher;
+    private final WindowManager mWindowManager;
+    // Layout width and height of the Taskbar in the default state.
+    private final Point mTaskbarSize;
     private final TaskbarStateHandler mTaskbarStateHandler;
     private final TaskbarAnimationController mTaskbarAnimationController;
     private final TaskbarHotseatController mHotseatController;
+    private final TaskbarDragController mDragController;
+    private final TaskbarNavButtonController mNavButtonController;
 
-    private final TaskbarActivityContext mContext;
-    final TaskbarContainerView mTaskbarContainerView;
-    final TaskbarView mTaskbarView;
+    // Initialized in init().
+    private WindowManager.LayoutParams mWindowLayoutParams;
+    private SysUINavigationMode.Mode mNavMode = SysUINavigationMode.Mode.NO_BUTTON;
+    private final SysUINavigationMode.NavigationModeChangeListener mNavigationModeChangeListener =
+            this::onNavModeChanged;
 
     private @Nullable Animator mAnimator;
     private boolean mIsAnimatingToLauncher;
 
-    public TaskbarController(BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
-        mContext = context;
-        mTaskbarContainerView = context.getDragLayer();
-        mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-
+    public TaskbarController(BaseQuickstepLauncher launcher,
+            TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) {
         mLauncher = launcher;
+        mTaskbarContainerView = taskbarContainerView;
+        mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
+        ButtonProvider buttonProvider = new ButtonProvider(launcher);
+        mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+        mTaskbarViewInApp.construct(createTaskbarViewCallbacks(), buttonProvider);
+        mTaskbarViewOnHome = taskbarViewOnHome;
+        mTaskbarViewOnHome.construct(createTaskbarViewCallbacks(), buttonProvider);
+        mImeBarView = mTaskbarContainerView.findViewById(R.id.ime_bar_view);
+        mImeBarView.construct(buttonProvider);
+        mNavButtonController = new TaskbarNavButtonController(launcher);
+        mWindowManager = mLauncher.getWindowManager();
+        mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
         mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
                 createTaskbarAnimationControllerCallbacks());
-        mHotseatController = new TaskbarHotseatController(
-                mLauncher, mTaskbarView::updateHotseatItems);
-    }
-
-    @Override
-    protected void onCreate() {
-        mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
-        mTaskbarAnimationController.init();
-        mHotseatController.init();
-        setTaskbarViewVisible(!mLauncher.hasBeenResumed());
-        alignRealHotseatWithTaskbar();
-        mLauncher.setTaskbarController(this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (mAnimator != null) {
-            // End this first, in case it relies on properties that are about to be cleaned up.
-            mAnimator.end();
-        }
-        mTaskbarStateHandler.setAnimationController(null);
-        mTaskbarAnimationController.cleanup();
-        mHotseatController.cleanup();
-        setTaskbarViewVisible(true);
-        mLauncher.getHotseat().setIconsAlpha(1f);
-        mLauncher.setTaskbarController(null);
-    }
-
-    @Override
-    protected boolean isTaskbarTouchable() {
-        return !mIsAnimatingToLauncher;
+        mHotseatController = new TaskbarHotseatController(mLauncher,
+                createTaskbarHotseatControllerCallbacks());
+        mDragController = new TaskbarDragController(mLauncher);
     }
 
     private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
@@ -103,34 +125,245 @@
 
             @Override
             public void updateTaskbarVisibilityAlpha(float alpha) {
-                mTaskbarView.setAlpha(alpha);
+                mTaskbarViewInApp.setAlpha(alpha);
+                mTaskbarViewOnHome.setAlpha(alpha);
             }
 
             @Override
             public void updateImeBarVisibilityAlpha(float alpha) {
-                mTaskbarContainerView.updateImeBarVisibilityAlpha(alpha);
+                if (mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) {
+                    // TODO Remove sysui IME bar for gesture nav as well
+                    return;
+                }
+                mImeBarView.setAlpha(alpha);
+                mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
             }
 
             @Override
             public void updateTaskbarScale(float scale) {
-                mTaskbarView.setScaleX(scale);
-                mTaskbarView.setScaleY(scale);
+                mTaskbarViewInApp.setScaleX(scale);
+                mTaskbarViewInApp.setScaleY(scale);
             }
 
             @Override
             public void updateTaskbarTranslationY(float translationY) {
                 if (translationY < 0) {
                     // Resize to accommodate the max translation we'll reach.
-                    mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
+                    setTaskbarWindowHeight(mTaskbarSize.y
                             + mLauncher.getHotseat().getTaskbarOffsetY());
                 } else {
-                    mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
+                    setTaskbarWindowHeight(mTaskbarSize.y);
                 }
-                mTaskbarView.setTranslationY(translationY);
+                mTaskbarViewInApp.setTranslationY(translationY);
             }
         };
     }
 
+    private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() {
+        return new TaskbarContainerViewCallbacks() {
+            @Override
+            public void onViewRemoved() {
+                // Ensure no other children present (like Folders, etc)
+                for (int i = 0; i < mTaskbarContainerView.getChildCount(); i++) {
+                    View v = mTaskbarContainerView.getChildAt(i);
+                    if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))){
+                        return;
+                    }
+                }
+                setTaskbarWindowFullscreen(false);
+            }
+
+            @Override
+            public boolean isTaskbarTouchable() {
+                return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
+                        && (mTaskbarViewInApp.getVisibility() == VISIBLE
+                            || mImeBarView.getVisibility() == VISIBLE)
+                        && !mIsAnimatingToLauncher;
+            }
+        };
+    }
+
+    private TaskbarViewCallbacks createTaskbarViewCallbacks() {
+        return new TaskbarViewCallbacks() {
+            @Override
+            public View.OnClickListener getItemOnClickListener() {
+                return view -> {
+                    Object tag = view.getTag();
+                    if (tag instanceof Task) {
+                        Task task = (Task) tag;
+                        ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
+                                ActivityOptions.makeBasic());
+                    } else if (tag instanceof FolderInfo) {
+                        FolderIcon folderIcon = (FolderIcon) view;
+                        Folder folder = folderIcon.getFolder();
+
+                        setTaskbarWindowFullscreen(true);
+
+                        mTaskbarContainerView.post(() -> {
+                            folder.animateOpen();
+
+                            folder.iterateOverItems((itemInfo, itemView) -> {
+                                itemView.setOnClickListener(getItemOnClickListener());
+                                itemView.setOnLongClickListener(getItemOnLongClickListener());
+                                // To play haptic when dragging, like other Taskbar items do.
+                                itemView.setHapticFeedbackEnabled(true);
+                                return false;
+                            });
+                        });
+                    } else {
+                        ItemClickHandler.INSTANCE.onClick(view);
+                    }
+
+                    AbstractFloatingView.closeAllOpenViews(
+                            mTaskbarContainerView.getTaskbarActivityContext());
+                };
+            }
+
+            @Override
+            public View.OnLongClickListener getItemOnLongClickListener() {
+                return mDragController::startSystemDragOnLongClick;
+            }
+
+            @Override
+            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.
+                boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
+                        || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
+                return isOnHomeScreen ? INVISIBLE : GONE;
+            }
+
+            @Override
+            public float getNonIconScale(TaskbarView taskbarView) {
+                return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f;
+            }
+
+            @Override
+            public void onItemPositionsChanged(TaskbarView taskbarView) {
+                if (taskbarView == mTaskbarViewOnHome) {
+                    alignRealHotseatWithTaskbar();
+                }
+            }
+
+            @Override
+            public void onNavigationButtonClick(@TaskbarButton int buttonType) {
+                mNavButtonController.onButtonClick(buttonType);
+            }
+        };
+    }
+
+    private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() {
+        return new TaskbarHotseatControllerCallbacks() {
+            @Override
+            public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+                mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos);
+            }
+        };
+    }
+
+    /**
+     * Initializes the Taskbar, including adding it to the screen.
+     */
+    public void init() {
+        mNavMode = SysUINavigationMode.INSTANCE.get(mLauncher)
+                .addModeChangeListener(mNavigationModeChangeListener);
+        mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(), mNavMode);
+        mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(), mNavMode);
+        mTaskbarContainerView.init(mTaskbarViewInApp);
+        mImeBarView.init(createTaskbarViewCallbacks());
+        addToWindowManager();
+        mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
+        mTaskbarAnimationController.init();
+        mHotseatController.init();
+
+        setWhichTaskbarViewIsVisible(mLauncher.hasBeenResumed()
+                ? mTaskbarViewOnHome
+                : mTaskbarViewInApp);
+    }
+
+    private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
+        return new TaskbarStateHandlerCallbacks() {
+            @Override
+            public AnimatedFloat getAlphaTarget() {
+                return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState();
+            }
+
+            @Override
+            public AnimatedFloat getScaleTarget() {
+                return mTaskbarAnimationController.getTaskbarScaleForLauncherState();
+            }
+
+            @Override
+            public AnimatedFloat getTranslationYTarget() {
+                return mTaskbarAnimationController.getTaskbarTranslationYForLauncherState();
+            }
+        };
+    }
+
+    /**
+     * Removes the Taskbar from the screen, and removes any obsolete listeners etc.
+     */
+    public void cleanup() {
+        if (mAnimator != null) {
+            // End this first, in case it relies on properties that are about to be cleaned up.
+            mAnimator.end();
+        }
+
+        mTaskbarViewInApp.cleanup();
+        mTaskbarViewOnHome.cleanup();
+        mTaskbarContainerView.cleanup();
+        mImeBarView.cleanup();
+        removeFromWindowManager();
+        mTaskbarStateHandler.setTaskbarCallbacks(null);
+        mTaskbarAnimationController.cleanup();
+        mHotseatController.cleanup();
+
+        setWhichTaskbarViewIsVisible(null);
+        SysUINavigationMode.INSTANCE.get(mLauncher)
+                .removeModeChangeListener(mNavigationModeChangeListener);
+    }
+
+    private void removeFromWindowManager() {
+        mWindowManager.removeViewImmediate(mTaskbarContainerView);
+    }
+
+    private void addToWindowManager() {
+        final int gravity = Gravity.BOTTOM;
+
+        mWindowLayoutParams = new WindowManager.LayoutParams(
+                mTaskbarSize.x,
+                mTaskbarSize.y,
+                TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSLUCENT);
+        mWindowLayoutParams.setTitle(WINDOW_TITLE);
+        mWindowLayoutParams.packageName = mLauncher.getPackageName();
+        mWindowLayoutParams.gravity = gravity;
+        mWindowLayoutParams.setFitInsetsTypes(0);
+        mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowLayoutParams.setSystemApplicationOverlay(true);
+
+        WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
+        wmWrapper.setProvidesInsetsTypes(
+                mWindowLayoutParams,
+                new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
+        );
+
+        TaskbarContainerView.LayoutParams taskbarLayoutParams =
+                new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
+        taskbarLayoutParams.gravity = gravity;
+        mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams);
+
+        mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
+    }
+
+    private void onNavModeChanged(SysUINavigationMode.Mode newMode) {
+        mNavMode = newMode;
+        cleanup();
+        init();
+    }
+
     /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
@@ -168,14 +401,13 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mIsAnimatingToLauncher = true;
-                mTaskbarView.setHolesAllowedInLayout(true);
-                mTaskbarView.updateHotseatItemsVisibility();
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsAnimatingToLauncher = false;
-                setTaskbarViewVisible(false);
+                setWhichTaskbarViewIsVisible(mTaskbarViewOnHome);
             }
         });
 
@@ -188,21 +420,44 @@
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mTaskbarView.updateHotseatItemsVisibility();
-                setTaskbarViewVisible(true);
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
+                setWhichTaskbarViewIsVisible(mTaskbarViewInApp);
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
-                mTaskbarView.setHolesAllowedInLayout(false);
             }
         });
         return anim.buildAnim();
     }
 
-    @Override
-    protected void onImeVisible(TaskbarContainerView containerView, boolean isVisible) {
-        mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
+    /**
+     * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
+     */
+    public void setIsImeVisible(boolean isImeVisible) {
+        mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
+        blockTaskbarTouchesForIme(isImeVisible);
+    }
+
+    /**
+     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
+     * instantiating at all, which is what's responsible for sending sysui state flags over.
+     *
+     * @param vis IME visibility flag
+     * @param backDisposition Used to determine back button behavior for software keyboard
+     *                        See BACK_DISPOSITION_* constants in {@link InputMethodService}
+     */
+    public void updateImeStatus(int displayId, int vis, int backDisposition,
+            boolean showImeSwitcher) {
+        if (displayId != mTaskbarContainerView.getContext().getDisplayId() ||
+                mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) {
+            return;
+        }
+
+        boolean imeVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
+        mTaskbarAnimationController.animateToVisibilityForIme(imeVisible ? 0 : 1);
+        mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
+        blockTaskbarTouchesForIme(imeVisible);
     }
 
     /**
@@ -217,17 +472,24 @@
      * @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();
+    }
+
+    /**
+     * @return Whether the given View is in the same window as Taskbar.
+     */
+    public boolean isViewInTaskbar(View v) {
+        return mTaskbarContainerView.isAttachedToWindow()
+                && mTaskbarContainerView.getWindowId().equals(v.getWindowId());
     }
 
     /**
      * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
      */
-    @Override
     public void alignRealHotseatWithTaskbar() {
         Rect hotseatBounds = new Rect();
         DeviceProfile grid = mLauncher.getDeviceProfile();
@@ -236,28 +498,60 @@
         int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
         int hotseatBottomDiff = taskbarOffset;
 
-        RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
-        Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
-                mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
-        hotseatBoundsF.round(hotseatBounds);
+        mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
         mLauncher.getHotseat().setPadding(hotseatBounds.left,
                 hotseatBounds.top + hotseatTopDiff,
-                mTaskbarView.getWidth() - hotseatBounds.right,
-                mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
+                mTaskbarViewOnHome.getWidth() - hotseatBounds.right,
+                mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
+    }
+
+    private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) {
+        mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp
+                ? VISIBLE : INVISIBLE);
+        mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome
+                ? VISIBLE : INVISIBLE);
+        mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f);
+    }
+
+    private void blockTaskbarTouchesForIme(boolean block) {
+        mTaskbarViewOnHome.setTouchesEnabled(!block);
+        mTaskbarViewInApp.setTouchesEnabled(!block);
     }
 
     /**
      * Returns the ratio of the taskbar icon size on home vs in an app.
      */
     public float getTaskbarScaleOnHome() {
-        DeviceProfile inAppDp = mContext.getDeviceProfile();
-        DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
+        DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext()
+                .getDeviceProfile();
+        DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
+                .getDeviceProfile();
         return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
     }
 
-    void setTaskbarViewVisible(boolean isVisible) {
-        mTaskbarView.setIconsVisibility(isVisible);
-        mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
+    /**
+     * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
+     */
+    private void setTaskbarWindowFullscreen(boolean fullscreen) {
+        setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mTaskbarSize.y);
+    }
+
+    /**
+     * Updates the TaskbarContainer height (pass mTaskbarSize.y to reset).
+     */
+    private void setTaskbarWindowHeight(int height) {
+        mWindowLayoutParams.width = mTaskbarSize.x;
+        mWindowLayoutParams.height = height;
+        mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams);
+    }
+
+    /**
+     * Contains methods that TaskbarStateHandler can call to interface with TaskbarController.
+     */
+    protected interface TaskbarStateHandlerCallbacks {
+        AnimatedFloat getAlphaTarget();
+        AnimatedFloat getScaleTarget();
+        AnimatedFloat getTranslationYTarget();
     }
 
     /**
@@ -271,4 +565,32 @@
         void updateTaskbarScale(float scale);
         void updateTaskbarTranslationY(float translationY);
     }
+
+    /**
+     * Contains methods that TaskbarContainerView can call to interface with TaskbarController.
+     */
+    protected interface TaskbarContainerViewCallbacks {
+        void onViewRemoved();
+        boolean isTaskbarTouchable();
+    }
+
+    /**
+     * Contains methods that TaskbarView can call to interface with TaskbarController.
+     */
+    protected interface TaskbarViewCallbacks {
+        View.OnClickListener getItemOnClickListener();
+        View.OnLongClickListener getItemOnLongClickListener();
+        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);
+        void onNavigationButtonClick(@TaskbarButton int buttonType);
+    }
+
+    /**
+     * 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
index ee44927..5eb34cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -20,7 +20,6 @@
 
 import android.content.ClipData;
 import android.content.ClipDescription;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.res.Resources;
@@ -30,6 +29,7 @@
 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;
@@ -43,12 +43,12 @@
  */
 public class TaskbarDragController {
 
-    private final Context mContext;
+    private final BaseQuickstepLauncher mLauncher;
     private final int mDragIconSize;
 
-    public TaskbarDragController(Context context) {
-        mContext = context;
-        Resources resources = mContext.getResources();
+    public TaskbarDragController(BaseQuickstepLauncher launcher) {
+        mLauncher = launcher;
+        Resources resources = mLauncher.getResources();
         mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
     }
 
@@ -63,6 +63,7 @@
         }
 
         BubbleTextView btv = (BubbleTextView) view;
+
         View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
             @Override
             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
@@ -86,7 +87,7 @@
         Intent intent = null;
         if (tag instanceof WorkspaceItemInfo) {
             WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
-            LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+            LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class);
             clipDescription = new ClipDescription(item.title,
                     new String[] {
                             item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 91cf7ef..68829cd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -26,8 +26,6 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.model.data.ItemInfo;
 
-import java.util.function.Consumer;
-
 /**
  * Works with TaskbarController to update the TaskbarView's Hotseat items.
  */
@@ -35,12 +33,13 @@
 
     private final BaseQuickstepLauncher mLauncher;
     private final Hotseat mHotseat;
-    private final Consumer<ItemInfo[]> mTaskbarCallbacks;
+    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) { }
+        public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        }
 
         @Override
         public void onDragEnd() {
@@ -48,8 +47,8 @@
         }
     };
 
-    public TaskbarHotseatController(
-            BaseQuickstepLauncher launcher, Consumer<ItemInfo[]> taskbarCallbacks) {
+    public TaskbarHotseatController(BaseQuickstepLauncher launcher,
+            TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) {
         mLauncher = launcher;
         mHotseat = mLauncher.getHotseat();
         mTaskbarCallbacks = taskbarCallbacks;
@@ -86,6 +85,10 @@
             }
         }
 
-        mTaskbarCallbacks.accept(hotseatItemInfos);
+        mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos);
+    }
+
+    protected int getNumHotseatIcons() {
+        return mNumHotseatIcons;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
deleted file mode 100644
index 2a37915..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
-import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-
-/**
- * Controller for taskbar icon UI
- */
-public class TaskbarIconController {
-
-    private final Rect mTempRect = new Rect();
-
-    private final TaskbarActivityContext mActivity;
-    private final TaskbarContainerView mContainerView;
-
-    private final TaskbarView mTaskbarView;
-    private final ImeBarView mImeBarView;
-
-    @NonNull
-    private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
-    TaskbarIconController(TaskbarActivityContext activity, TaskbarContainerView containerView) {
-        mActivity = activity;
-        mContainerView = containerView;
-        mTaskbarView = mContainerView.findViewById(R.id.taskbar_view);
-        mImeBarView = mContainerView.findViewById(R.id.ime_bar_view);
-    }
-
-    public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
-        mContainerView.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
-                mUIController.alignRealHotseatWithTaskbar());
-
-        ButtonProvider buttonProvider = new ButtonProvider(mActivity);
-        mImeBarView.init(buttonProvider);
-        mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
-        mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
-
-        mContainerView.init(new Callbacks(), mTaskbarView);
-    }
-
-    public void onDestroy() {
-        mContainerView.onDestroy();
-    }
-
-    public void setUIController(@NonNull TaskbarUIController uiController) {
-        mUIController = uiController;
-    }
-
-    /**
-     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
-     * instantiating at all, which is what's responsible for sending sysui state flags over.
-     *
-     * @param vis IME visibility flag
-     */
-    public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
-        if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
-            return;
-        }
-
-        mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
-        setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
-    }
-
-    /**
-     * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
-     */
-    public void setImeIsVisible(boolean isImeVisible) {
-        mTaskbarView.setTouchesEnabled(!isImeVisible);
-        mUIController.onImeVisible(mContainerView, isImeVisible);
-    }
-
-    /**
-     * Callbacks for {@link TaskbarContainerView} to interact with the icon controller
-     */
-    public class Callbacks {
-
-        /**
-         * Called to update the touchable insets
-         */
-        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
-            insetsInfo.touchableRegion.setEmpty();
-            if (mContainerView.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mImeBarView.getVisibility() == VISIBLE) {
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
-            } else if (!mUIController.isTaskbarTouchable()) {
-                // Let touches pass through us.
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            } else if (mTaskbarView.areIconsVisible()) {
-                // Buttons are visible, take over the full taskbar area
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
-            } else {
-                if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
-                    mContainerView.getDescendantRectRelativeToSelf(
-                            mTaskbarView.mSystemButtonContainer, mTempRect);
-                    insetsInfo.touchableRegion.set(mTempRect);
-                }
-                insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
-            }
-
-            // TaskbarContainerView provides insets to other apps based on contentInsets. These
-            // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
-            // to show a floating view like Folder. Thus, we set the contentInsets to be where
-            // mTaskbarView is, since its position never changes and insets rather than overlays.
-            insetsInfo.contentInsets.left = mTaskbarView.getLeft();
-            insetsInfo.contentInsets.top = mTaskbarView.getTop();
-            insetsInfo.contentInsets.right = mContainerView.getWidth() - mTaskbarView.getRight();
-            insetsInfo.contentInsets.bottom = mContainerView.getHeight() - mTaskbarView.getBottom();
-        }
-
-        public void onContainerViewRemoved() {
-            int count = mContainerView.getChildCount();
-            // Ensure no other children present (like Folders, etc)
-            for (int i = 0; i < count; i++) {
-                View v = mContainerView.getChildAt(i);
-                if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
-                    return;
-                }
-            }
-            mActivity.setTaskbarWindowFullscreen(false);
-        }
-
-        public void updateImeBarVisibilityAlpha(float alpha) {
-            if (!mActivity.canShowNavButtons()) {
-                // TODO Remove sysui IME bar for gesture nav as well
-                return;
-            }
-            mImeBarView.setAlpha(alpha);
-            mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
deleted file mode 100644
index b9eec93..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
-import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
-import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
-import android.view.Display;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.TouchInteractionService;
-
-/**
- * Class to manager taskbar lifecycle
- */
-public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
-        SysUINavigationMode.NavigationModeChangeListener {
-
-    private final Context mContext;
-    private final DisplayController mDisplayController;
-    private final SysUINavigationMode mSysUINavigationMode;
-    private final TaskbarNavButtonController mNavButtonController;
-
-    private TaskbarActivityContext mTaskbarActivityContext;
-    private BaseQuickstepLauncher mLauncher;
-
-    private static final int CHANGE_FLAGS =
-            CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
-
-    public TaskbarManager(TouchInteractionService service) {
-        mDisplayController = DisplayController.INSTANCE.get(service);
-        mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
-        Display display =
-                service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
-        mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
-        mNavButtonController = new TaskbarNavButtonController(service);
-
-        mDisplayController.addChangeListener(this);
-        mSysUINavigationMode.addModeChangeListener(this);
-        recreateTaskbar();
-    }
-
-    @Override
-    public void onNavigationModeChanged(Mode newMode) {
-        recreateTaskbar();
-    }
-
-    @Override
-    public void onDisplayInfoChanged(Context context, Info info, int flags) {
-        if ((flags & CHANGE_FLAGS) != 0) {
-            recreateTaskbar();
-        }
-    }
-
-    private void destroyExistingTaskbar() {
-        if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.onDestroy();
-            mTaskbarActivityContext = null;
-        }
-    }
-
-    /**
-     * Sets or clears a launcher to act as taskbar callback
-     */
-    public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
-        mLauncher = launcher;
-        if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.setUIController(mLauncher == null
-                    ? TaskbarUIController.DEFAULT
-                    : new TaskbarController(launcher, mTaskbarActivityContext));
-        }
-    }
-
-    private void recreateTaskbar() {
-        destroyExistingTaskbar();
-        if (!FeatureFlags.ENABLE_TASKBAR.get()) {
-            return;
-        }
-        DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
-        if (!dp.isTaskbarPresent) {
-            return;
-        }
-        mTaskbarActivityContext = new TaskbarActivityContext(
-                mContext, dp.copy(mContext), mNavButtonController);
-        mTaskbarActivityContext.init();
-        if (mLauncher != null) {
-            mTaskbarActivityContext.setUIController(
-                    new TaskbarController(mLauncher, mTaskbarActivityContext));
-        }
-    }
-
-    /**
-     * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
-     * @param systemUiStateFlags The latest SystemUiStateFlags
-     */
-    public void onSystemUiFlagsChanged(int systemUiStateFlags) {
-        boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
-        if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.getIconController().setImeIsVisible(isImeVisible);
-        }
-    }
-
-    /**
-     * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
-     * instantiating at all, which is what's responsible for sending sysui state flags over.
-     *
-     * @param vis IME visibility flag
-     * @param backDisposition Used to determine back button behavior for software keyboard
-     *                        See BACK_DISPOSITION_* constants in {@link InputMethodService}
-     */
-    public void updateImeStatus(int displayId, int vis, int backDisposition,
-            boolean showImeSwitcher) {
-        if (mTaskbarActivityContext != null) {
-            mTaskbarActivityContext.getIconController()
-                    .updateImeStatus(displayId, vis, showImeSwitcher);
-        }
-    }
-
-    /**
-     * Called when the manager is no longer needed
-     */
-    public void destroy() {
-        destroyExistingTaskbar();
-        mDisplayController.removeChangeListener(this);
-        mSysUINavigationMode.removeModeChangeListener(this);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 3b5afad..54e1610 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,8 +16,7 @@
 
 package com.android.launcher3.taskbar;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
+import android.content.Context;
 import android.content.Intent;
 import android.view.inputmethod.InputMethodManager;
 
@@ -54,10 +53,11 @@
     static final int BUTTON_RECENTS = BUTTON_HOME << 1;
     static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
 
-    private final TouchInteractionService mService;
 
-    public TaskbarNavButtonController(TouchInteractionService service) {
-        mService = service;
+    private final Context mContext;
+
+    public TaskbarNavButtonController(Context context) {
+        mContext = context;
     }
 
     public void onButtonClick(@TaskbarButton int buttonType) {
@@ -78,13 +78,13 @@
     }
 
     private void navigateHome() {
-        mService.startActivity(new Intent(Intent.ACTION_MAIN)
+        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
     }
 
     private void navigateToOverview() {
-        mService.getOverviewCommandHelper()
+        TouchInteractionService.getInstance().getOverviewCommandHelper()
                 .addCommand(OverviewCommandHelper.TYPE_SHOW);
     }
 
@@ -93,8 +93,8 @@
     }
 
     private void showIMESwitcher() {
-        mService.getSystemService(InputMethodManager.class)
-                .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
-                        DEFAULT_DISPLAY);
+        mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+                true /* showAuxiliarySubtypes */, mContext.getDisplayId());
     }
+
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index a701aae..6ea51fa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -24,52 +24,59 @@
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.quickstep.AnimatedFloat;
 
 /**
  * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
- * isn't present (i.e. {@link #setAnimationController} is never called).
+ * isn't present (i.e. {@link #setTaskbarCallbacks} is never called).
  */
 public class TaskbarStateHandler implements StateManager.StateHandler<LauncherState> {
 
     private final BaseQuickstepLauncher mLauncher;
 
     // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
-    private @Nullable TaskbarAnimationController mAnimationController = null;
+    private @Nullable TaskbarController.TaskbarStateHandlerCallbacks mTaskbarCallbacks = null;
 
     public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
-    public void setAnimationController(TaskbarAnimationController callbacks) {
-        mAnimationController = callbacks;
+    public void setTaskbarCallbacks(TaskbarController.TaskbarStateHandlerCallbacks callbacks) {
+        mTaskbarCallbacks = callbacks;
     }
 
     @Override
     public void setState(LauncherState state) {
-        setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+        if (mTaskbarCallbacks == null) {
+            return;
+        }
+
+        AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
+        AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget();
+        boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0;
+        alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f);
+        scaleTarget.updateValue(state.getTaskbarScale(mLauncher));
+        translationYTarget.updateValue(state.getTaskbarTranslationY(mLauncher));
     }
 
     @Override
     public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
             PendingAnimation animation) {
-        setState(toState, animation);
-    }
-
-    private void setState(LauncherState toState, PropertySetter setter) {
-        if (mAnimationController == null) {
+        if (mTaskbarCallbacks == null) {
             return;
         }
 
+        AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
+        AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget();
         boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
-        setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
-                AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
-        setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
-                AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
-        setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
-                AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
+        animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
+        animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher),
+                LINEAR);
+        animation.setFloat(translationYTarget, AnimatedFloat.VALUE,
+                toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
deleted file mode 100644
index e16f5e6..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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;
-
-/**
- * Base class for providing different taskbar UI
- */
-public class TaskbarUIController {
-
-    public static final TaskbarUIController DEFAULT = new TaskbarUIController();
-
-    /**
-     * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
-     */
-    public void alignRealHotseatWithTaskbar() { }
-
-    protected void onCreate() { }
-
-    protected void onDestroy() { }
-
-    protected boolean isTaskbarTouchable() {
-        return true;
-    }
-
-    protected void onImeVisible(TaskbarContainerView container, boolean isVisible) {
-        container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c6573a6..9e8013e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,14 +15,20 @@
  */
 package com.android.launcher3.taskbar;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
 
+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.Rect;
 import android.graphics.RectF;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.view.DragEvent;
 import android.view.Gravity;
@@ -45,12 +51,17 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.SysUINavigationMode;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
 public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
 
+
+    private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
+            SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
+
     private final int mIconTouchSize;
     private final boolean mIsRtl;
     private final int mTouchSlop;
@@ -58,16 +69,17 @@
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
 
-    private final int mItemMarginLeftRight;
-
-    private final TaskbarActivityContext mActivityContext;
-
     // Initialized in TaskbarController constructor.
-    private View.OnClickListener mIconClickListener;
-    private View.OnLongClickListener mIconLongClickListener;
+    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+    // Scale on elements that aren't icons.
+    private float mNonIconScale;
+    private int mItemMarginLeftRight;
 
-    LinearLayout mSystemButtonContainer;
-    LinearLayout mHotseatIconsContainer;
+    // Initialized in init().
+    private LayoutTransition mLayoutTransition;
+    private int mHotseatStartIndex;
+    private int mHotseatEndIndex;
+    private LinearLayout mButtonRegion;
 
     // Delegate touches to the closest view if within mIconTouchSize.
     private boolean mDelegateTargeted;
@@ -79,12 +91,10 @@
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
 
+    private int mNavButtonStartIndex;
     /** Provider of buttons added to taskbar in 3 button nav */
     private ButtonProvider mButtonProvider;
 
-    private boolean mDisableRelayout;
-    private boolean mAreHolesAllowed;
-
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -101,58 +111,80 @@
     public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mActivityContext = ActivityContext.lookupContext(context);
 
         Resources resources = getResources();
         mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
-        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
-
         mIsRtl = Utilities.isRtl(resources);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     }
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mSystemButtonContainer = findViewById(R.id.system_button_layout);
-        mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+    protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks,
+            ButtonProvider buttonProvider) {
+        mControllerCallbacks = taskbarViewCallbacks;
+        mNonIconScale = mControllerCallbacks.getNonIconScale(this);
+        mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
+        mButtonProvider = buttonProvider;
+        mButtonProvider.setMarginLeftRight(mItemMarginLeftRight);
     }
 
-    protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
-                ButtonProvider buttonProvider) {
-        mIconClickListener = clickListener;
-        mIconLongClickListener = longClickListener;
-        mButtonProvider = buttonProvider;
-
-        if (mActivityContext.canShowNavButtons()) {
+    protected void init(int numHotseatIcons, SysUINavigationMode.Mode newMode) {
+        // TODO: check if buttons on left
+        if (newMode == SysUINavigationMode.Mode.THREE_BUTTONS && ENABLE_THREE_BUTTON_TASKBAR) {
+            // 3 button
+            mNavButtonStartIndex = 0;
             createNavButtons();
         } else {
-            mSystemButtonContainer.setVisibility(GONE);
+            mNavButtonStartIndex = -1;
+            removeNavButtons();
         }
 
-        int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
+        mHotseatStartIndex = mNavButtonStartIndex + 1;
+        mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
+
+        mLayoutTransition = new LayoutTransition();
+        addUpdateListenerForAllLayoutTransitions(() -> {
+            if (getLayoutTransition() == mLayoutTransition) {
+                mControllerCallbacks.onItemPositionsChanged(this);
+            }
+        });
+        setLayoutTransition(mLayoutTransition);
     }
 
-    /**
-     * Enables/disables empty icons in taskbar so that the layout matches with Launcher
-     */
-    public void setHolesAllowedInLayout(boolean areHolesAllowed) {
-        if (mAreHolesAllowed != areHolesAllowed) {
-            mAreHolesAllowed = areHolesAllowed;
-            updateHotseatItemsVisibility();
-            // TODO: Add animation
+    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);
         }
     }
 
-    private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
-        if (mAreHolesAllowed != areHolesAllowed) {
-            mAreHolesAllowed = areHolesAllowed;
-            updateHotseatItemsVisibility();
-            onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
-                    makeMeasureSpec(getMeasuredHeight(), EXACTLY));
-            onLayout(false, getLeft(), getTop(), getRight(), getBottom());
-        }
+    protected void cleanup() {
+        endAllLayoutTransitionAnimators();
+        setLayoutTransition(null);
+        removeAllViews();
+    }
+
+    private void endAllLayoutTransitionAnimators() {
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.APPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING).end();
     }
 
     /**
@@ -160,9 +192,10 @@
      */
     protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
         for (int i = 0; i < hotseatItemInfos.length; i++) {
-            ItemInfo hotseatItemInfo = hotseatItemInfos[
-                    !mIsRtl ? i : hotseatItemInfos.length - i - 1];
-            View hotseatView = mHotseatIconsContainer.getChildAt(i);
+            ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i
+                    : hotseatItemInfos.length - i - 1];
+            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;
@@ -180,23 +213,23 @@
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null
-                    || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
                     || needsReinflate) {
-                mHotseatIconsContainer.removeView(hotseatView);
+                removeView(hotseatView);
+                ActivityContext activityContext = getActivityContext();
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
-                            mActivityContext, this, folderInfo);
+                            getActivityContext(), this, folderInfo);
                     folderIcon.setTextVisible(false);
                     hotseatView = folderIcon;
                 } else {
                     hotseatView = inflate(expectedLayoutResId);
                 }
-                int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
+                int iconSize = activityContext.getDeviceProfile().iconSizePx;
                 LayoutParams lp = new LayoutParams(iconSize, iconSize);
                 lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
-                mHotseatIconsContainer.addView(hotseatView, i, lp);
+                addView(hotseatView, hotseatIndex, lp);
             }
 
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -204,11 +237,13 @@
                     && hotseatItemInfo instanceof WorkspaceItemInfo) {
                 ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
                         (WorkspaceItemInfo) hotseatItemInfo);
-                hotseatView.setOnClickListener(mIconClickListener);
-                hotseatView.setOnLongClickListener(mIconLongClickListener);
+                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                hotseatView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
             } else if (isFolder) {
-                hotseatView.setOnClickListener(mIconClickListener);
-                hotseatView.setOnLongClickListener(mIconLongClickListener);
+                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                hotseatView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
             } else {
                 hotseatView.setOnClickListener(null);
                 hotseatView.setOnLongClickListener(null);
@@ -219,14 +254,24 @@
     }
 
     protected void updateHotseatItemsVisibility() {
-        for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
-            updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
+        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+            updateHotseatItemVisibility(getChildAt(i));
         }
     }
 
     private void updateHotseatItemVisibility(View hotseatView) {
-        hotseatView.setVisibility(
-                hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
+        if (hotseatView.getTag() != null) {
+            hotseatView.setVisibility(VISIBLE);
+        } else {
+            int oldVisibility = hotseatView.getVisibility();
+            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this);
+            hotseatView.setVisibility(newVisibility);
+            if (oldVisibility == GONE && newVisibility != GONE) {
+                // By default, the layout transition only runs when going to VISIBLE,
+                // but we want it to run when going to GONE to INVISIBLE as well.
+                getLayoutTransition().showChild(this, hotseatView, oldVisibility);
+            }
+        }
     }
 
     @Override
@@ -333,20 +378,49 @@
         return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
     }
 
+    private void removeNavButtons() {
+        if (mButtonRegion != null) {
+            mButtonRegion.removeAllViews();
+            removeView(mButtonRegion);
+        } // else We've never been in 3 button. Woah Scoob!
+    }
+
     /**
      * Add back/home/recents buttons into a single ViewGroup that will be inserted at
      * {@param navButtonStartIndex}
      */
     private void createNavButtons() {
+        ActivityContext context = getActivityContext();
+        if (mButtonRegion == null) {
+            mButtonRegion = new LinearLayout(getContext());
+        } else {
+            mButtonRegion.removeAllViews();
+        }
+        mButtonRegion.setVisibility(VISIBLE);
+
         LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
-                mActivityContext.getDeviceProfile().iconSizePx,
-                mActivityContext.getDeviceProfile().iconSizePx
+                context.getDeviceProfile().iconSizePx,
+                context.getDeviceProfile().iconSizePx
         );
         buttonParams.gravity = Gravity.CENTER;
 
-        mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
-        mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
-        mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
+        View backButton = mButtonProvider.getBack();
+        backButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_BACK));
+        mButtonRegion.addView(backButton, buttonParams);
+
+        // Home button
+        View homeButton = mButtonProvider.getHome();
+        homeButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_HOME));
+        mButtonRegion.addView(homeButton, buttonParams);
+
+        View recentsButton = mButtonProvider.getRecents();
+        recentsButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick(
+                BUTTON_RECENTS));
+        mButtonRegion.addView(recentsButton, buttonParams);
+
+        addView(mButtonRegion, mNavButtonStartIndex);
     }
 
     @Override
@@ -354,7 +428,7 @@
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_STARTED:
                 mIsDraggingItem = true;
-                AbstractFloatingView.closeAllOpenViews(mActivityContext);
+                AbstractFloatingView.closeAllOpenViews(getActivityContext());
                 return true;
             case DragEvent.ACTION_DRAG_ENDED:
                 mIsDraggingItem = false;
@@ -371,26 +445,26 @@
      * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
      */
     protected RectF getHotseatBounds() {
-        RectF result;
-        mDisableRelayout = true;
-        boolean wereHolesAllowed = mAreHolesAllowed;
-        setHolesAllowedInLayoutNoAnimation(true);
-        result = new RectF(
-                mHotseatIconsContainer.getLeft(),
-                mHotseatIconsContainer.getTop(),
-                mHotseatIconsContainer.getRight(),
-                mHotseatIconsContainer.getBottom());
-        setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
-        mDisableRelayout = false;
-
-        return result;
-    }
-
-    @Override
-    public void requestLayout() {
-        if (!mDisableRelayout) {
-            super.requestLayout();
+        View firstHotseatView = null, lastHotseatView = null;
+        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                if (firstHotseatView == null) {
+                    firstHotseatView = child;
+                }
+                lastHotseatView = child;
+            }
         }
+        if (firstHotseatView == null || lastHotseatView == null) {
+            return new RectF();
+        }
+        View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
+        View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
+        return new RectF(
+                leftmostHotseatView.getLeft() - mItemMarginLeftRight,
+                leftmostHotseatView.getTop(),
+                rightmostHotseatView.getRight() + mItemMarginLeftRight,
+                rightmostHotseatView.getBottom());
     }
 
     // FolderIconParent implemented methods.
@@ -421,7 +495,7 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+        return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false);
     }
 
     @Override
@@ -429,11 +503,7 @@
         // Ignore, we just implement Insettable to draw behind system insets.
     }
 
-    public void setIconsVisibility(boolean isVisible) {
-        mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
-    }
-
-    public boolean areIconsVisible() {
-        return mHotseatIconsContainer.getVisibility() == VISIBLE;
+    private <T extends Context & ActivityContext> T getActivityContext() {
+        return ActivityContext.lookupContext(getContext());
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 86bf119..f5ddd0e 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -52,6 +52,7 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.views.ScrimView;
@@ -122,6 +123,11 @@
         return null;
     }
 
+    @Nullable
+    public TaskbarController getTaskbarController() {
+        return null;
+    }
+
     public final boolean isResumed() {
         ACTIVITY_TYPE activity = getCreatedActivity();
         return activity != null && activity.hasBeenResumed();
@@ -362,6 +368,13 @@
     protected abstract int getOverviewScrimColorForState(ACTIVITY_TYPE activity, STATE_TYPE state);
 
     /**
+     * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
+     * @param systemUiStateFlags The latest SystemUiStateFlags
+     */
+    public void onSystemUiFlagsChanged(int systemUiStateFlags) {
+    }
+
+    /**
      * Returns the expected STATE_TYPE from the provided GestureEndTarget.
      */
     public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index ff69180..5217c3b 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -163,7 +164,8 @@
     }
 
     @Nullable
-    private TaskbarController getTaskbarController() {
+    @Override
+    public TaskbarController getTaskbarController() {
         BaseQuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
@@ -299,6 +301,16 @@
     }
 
     @Override
+    public void onSystemUiFlagsChanged(int systemUiStateFlags) {
+        TaskbarController taskbarController = getTaskbarController();
+        if (taskbarController == null) {
+            return;
+        }
+        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) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d95f7b7..edc7a3c 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -52,6 +52,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -74,7 +75,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.LauncherTraceProto;
@@ -147,9 +147,21 @@
     private OverscrollPlugin mOverscrollPlugin;
 
     /**
-     * Local IOverviewProxy implementation with some methods for local components
+     * Extension of OverviewProxy aidl interface without needing to modify the actual interface.
+     * This is for methods that need only need local access and not intended to make IPC calls.
      */
-    public class TISBinder extends IOverviewProxy.Stub {
+    public abstract static class TISBinder extends IOverviewProxy.Stub {
+        public abstract void setTaskbarOverviewProxyDelegate(
+                @Nullable TaskbarOverviewProxyDelegate i);
+    }
+
+
+    private final TISBinder mMyBinder = new TISBinder() {
+
+        public void setTaskbarOverviewProxyDelegate(
+                @Nullable TaskbarOverviewProxyDelegate delegate) {
+            mTaskbarOverviewProxyDelegate = delegate;
+        }
 
         @BinderThread
         public void onInitialize(Bundle bundle) {
@@ -262,24 +274,40 @@
 
         @Override
         public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
-                int backDisposition, boolean showImeSwitcher) {
-            MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
-                    displayId, vis, backDisposition, showImeSwitcher));
+                int backDisposition, boolean showImeSwitcher) throws RemoteException {
+            if (mTaskbarOverviewProxyDelegate == null) {
+                return;
+            }
+            MAIN_EXECUTOR.execute(() -> {
+                if (mTaskbarOverviewProxyDelegate == null) {
+                    return;
+                }
+                mTaskbarOverviewProxyDelegate
+                        .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher);
+            });
         }
+    };
 
-        public TaskbarManager getTaskbarManager() {
-            return mTaskbarManager;
-        }
+    public interface TaskbarOverviewProxyDelegate {
+        void updateImeStatus(int displayId, int vis, int backDisposition,
+                boolean showImeSwitcher);
     }
 
     private static boolean sConnected = false;
+    private static TouchInteractionService sInstance;
     private static boolean sIsInitialized = false;
     private RotationTouchHelper mRotationTouchHelper;
+    @Nullable
+    private TaskbarOverviewProxyDelegate mTaskbarOverviewProxyDelegate;
 
     public static boolean isConnected() {
         return sConnected;
     }
 
+    @Nullable
+    public static TouchInteractionService getInstance() {
+        return sInstance;
+    }
 
     public static boolean isInitialized() {
         return sIsInitialized;
@@ -308,7 +336,9 @@
 
     private DisplayManager mDisplayManager;
 
-    private TaskbarManager mTaskbarManager;
+    public TouchInteractionService() {
+        sInstance = this;
+    }
 
     @Override
     public void onCreate() {
@@ -318,14 +348,13 @@
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this, true);
-        mDisplayManager = getSystemService(DisplayManager.class);
-        mTaskbarManager = new TaskbarManager(this);
-
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
         ProtoTracer.INSTANCE.get(this).add(this);
+        mDisplayManager = getSystemService(DisplayManager.class);
+
         sConnected = true;
     }
 
@@ -439,7 +468,8 @@
             int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
             SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
             mOverviewComponentObserver.onSystemUiStateChanged();
-            mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
+            mOverviewComponentObserver.getActivityInterface().onSystemUiFlagsChanged(
+                    systemUiStateFlags);
 
             if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
                     (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
@@ -482,7 +512,6 @@
         getSystemService(AccessibilityManager.class)
                 .unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
 
-        mTaskbarManager.destroy();
         sConnected = false;
         super.onDestroy();
     }
@@ -490,7 +519,7 @@
     @Override
     public IBinder onBind(Intent intent) {
         Log.d(TAG, "Touch service connected: user=" + getUserId());
-        return new TISBinder();
+        return mMyBinder;
     }
 
     private void onInputEvent(InputEvent ev) {
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 49aec93..d430028 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -116,7 +116,7 @@
                     addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
                 }
             } else {
-                final int hotseatRow, qsbRow;
+                final int hotseatRow, qsbRow, taskbarRow;
                 if (grid.isTaskbarPresent) {
                     qsbRow = grid.inv.numRows + 1;
                     hotseatRow = grid.inv.numRows + 2;
@@ -124,12 +124,16 @@
                     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(), qsbRow, totalRows);
+                addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
             }
 
             mAnimators.addListener(new AnimatorListenerAdapter() {
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/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 322c6ee..3d044d6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -447,6 +447,10 @@
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
+        if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) {
+            // TODO: support notification dots in Taskbar
+            return;
+        }
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
             Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index ff380ce..b2a9e75 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,6 +49,7 @@
     private final View mQsb;
     private final int mQsbHeight;
 
+    private final View mTaskbarView;
     private final int mTaskbarViewHeight;
 
     public Hotseat(Context context) {
@@ -66,7 +67,10 @@
         mQsbHeight = mQsb.getLayoutParams().height;
         addView(mQsb);
 
-        mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
+        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);
     }
 
     /**
@@ -183,6 +187,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
@@ -196,6 +202,13 @@
         int bottom = b - t - getQsbOffsetY();
         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 - getTaskbarOffsetY();
+        top = bottom - mTaskbarViewHeight;
+        mTaskbarView.layout(left, top, right, bottom);
     }
 
     /**
@@ -231,4 +244,10 @@
         return mQsb;
     }
 
+    /**
+     * Returns the Taskbar inside hotseat
+     */
+    public View getTaskbarView() {
+        return mTaskbarView;
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8889e60..cf90216 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1942,6 +1942,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
@@ -2853,6 +2860,13 @@
                 .start();
     }
 
+    /**
+     * @return Whether the View is in the same window as the Taskbar window.
+     */
+    public boolean isViewInTaskbar(View v) {
+        return false;
+    }
+
     public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return false;
     }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2884fba..7ae729a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -324,17 +324,15 @@
     }
 
     public static void scaleRectFAboutCenter(RectF r, float scale) {
-        scaleRectFAboutPivot(r, scale, r.centerX(), r.centerY());
-    }
-
-    public static void scaleRectFAboutPivot(RectF r, float scale, float px, float py) {
         if (scale != 1.0f) {
-            r.offset(-px, -py);
+            float cx = r.centerX();
+            float cy = r.centerY();
+            r.offset(-cx, -cy);
             r.left = r.left * scale;
             r.top = r.top * scale ;
             r.right = r.right * scale;
             r.bottom = r.bottom * scale;
-            r.offset(px, py);
+            r.offset(cx, cy);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index b5dcd3a..ce7dc07 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -27,7 +27,6 @@
 
 import android.app.AlertDialog;
 import android.app.PendingIntent;
-import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.LauncherApps;
@@ -195,43 +194,36 @@
     }
 
     /**
-     * Handles clicking on a disabled shortcut
-     */
-    public static void handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) {
-        final int disabledFlags = shortcut.runtimeStatusFlags
-                & WorkspaceItemInfo.FLAG_DISABLED_MASK;
-        if ((disabledFlags
-                & ~FLAG_DISABLED_SUSPENDED
-                & ~FLAG_DISABLED_QUIET_USER) == 0) {
-            // If the app is only disabled because of the above flags, launch activity anyway.
-            // Framework will tell the user why the app is suspended.
-        } else {
-            if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
-                // Use a message specific to this shortcut, if it has one.
-                Toast.makeText(context, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
-                return;
-            }
-            // Otherwise just use a generic error message.
-            int error = R.string.activity_not_available;
-            if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
-                error = R.string.safemode_shortcut_error;
-            } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0
-                    || (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
-                error = R.string.shortcut_not_available;
-            }
-            Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    /**
      * Event handler for an app shortcut click.
      *
      * @param v The view that was clicked. Must be a tagged with a {@link WorkspaceItemInfo}.
      */
     public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {
         if (shortcut.isDisabled()) {
-            handleDisabledItemClicked(shortcut, launcher);
-            return;
+            final int disabledFlags = shortcut.runtimeStatusFlags
+                    & WorkspaceItemInfo.FLAG_DISABLED_MASK;
+            if ((disabledFlags &
+                    ~FLAG_DISABLED_SUSPENDED &
+                    ~FLAG_DISABLED_QUIET_USER) == 0) {
+                // If the app is only disabled because of the above flags, launch activity anyway.
+                // Framework will tell the user why the app is suspended.
+            } else {
+                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
+                    // Use a message specific to this shortcut, if it has one.
+                    Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                // Otherwise just use a generic error message.
+                int error = R.string.activity_not_available;
+                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
+                    error = R.string.safemode_shortcut_error;
+                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
+                    error = R.string.shortcut_not_available;
+                }
+                Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
+                return;
+            }
         }
 
         // Check for abandoned promise