Merge "Fix PathParser error when parsing app close interpolator in RTL languages." into sc-dev
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index 240fe55..e680233 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.taskbar.TaskbarContainerView
+<com.android.launcher3.taskbar.TaskbarDragLayer
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/taskbar_container"
     android:layout_width="wrap_content"
@@ -22,9 +22,29 @@
 
     <com.android.launcher3.taskbar.TaskbarView
         android:id="@+id/taskbar_view"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:gravity="center"/>
+        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>
 
     <com.android.launcher3.taskbar.ImeBarView
         android:id="@+id/ime_bar_view"
@@ -32,4 +52,4 @@
         android:layout_height="wrap_content"
         android:visibility="gone"/>
 
-</com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
+</com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_view.xml b/quickstep/res/layout/taskbar_view.xml
deleted file mode 100644
index 34a88ea..0000000
--- a/quickstep/res/layout/taskbar_view.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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 735cb24..50453ac 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -149,4 +149,5 @@
     <!-- 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 25e2359..0b41f15 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.NO_OFFSET;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
-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;
@@ -31,7 +30,6 @@
 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;
@@ -52,13 +50,11 @@
 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.LauncherTaskbarUIController;
+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;
@@ -68,6 +64,7 @@
 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;
@@ -89,8 +86,6 @@
 
     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.
@@ -101,8 +96,20 @@
 
     private OverviewActionsView mActionsView;
 
-    private @Nullable TaskbarController mTaskbarController;
+    private @Nullable TaskbarManager mTaskbarManager;
+    private @Nullable LauncherTaskbarUIController mTaskbarUIController;
+    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;
@@ -112,24 +119,6 @@
         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
@@ -137,15 +126,12 @@
         mAppTransitionManager.onActivityDestroyed();
 
         SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
-        if (mTaskbarController != null) {
-            mTaskbarController.cleanup();
-            mTaskbarController = null;
-            if (mTisBinder != null) {
-                mTisBinder.setTaskbarOverviewProxyDelegate(null);
-                unbindService(mTisBinderConnection);
-            }
-        }
 
+
+        unbindService(mTisBinderConnection);
+        if (mTaskbarManager != null) {
+            mTaskbarManager.setLauncher(null);
+        }
         super.onDestroy();
     }
 
@@ -272,37 +258,12 @@
         mAppTransitionManager = new QuickstepTransitionManager(this);
         mAppTransitionManager.registerRemoteAnimations();
 
-        addTaskbarIfNecessary();
-        addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary());
+        bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
+
     }
 
-    @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 void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
+        mTaskbarUIController = taskbarUIController;
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
@@ -332,8 +293,8 @@
         return mDepthController;
     }
 
-    public @Nullable TaskbarController getTaskbarController() {
-        return mTaskbarController;
+    public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
+        return mTaskbarUIController;
     }
 
     public TaskbarStateHandler getTaskbarStateHandler() {
@@ -341,14 +302,9 @@
     }
 
     @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()
-                && !isViewInTaskbar(clickedView);
+                && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get();
     }
 
     @Override
@@ -394,8 +350,8 @@
 
     @Override
     public float getNormalTaskbarScale() {
-        if (mTaskbarController != null) {
-            return mTaskbarController.getTaskbarScaleOnHome();
+        if (mTaskbarUIController != null) {
+            return mTaskbarUIController.getTaskbarScaleOnHome();
         }
         return super.getNormalTaskbarScale();
     }
@@ -417,8 +373,8 @@
         }
 
         if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
-            if (mTaskbarController != null) {
-                mTaskbarController.onLauncherResumedOrPaused(hasBeenResumed());
+            if (mTaskbarUIController != null) {
+                mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed());
             }
         }
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 1b8fcb3..80754a0 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1252,7 +1252,6 @@
 
             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);
@@ -1263,8 +1262,6 @@
                         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/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 5dcf84c..85e5ab0 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -263,8 +263,8 @@
             removeOutlineDrawings();
         }
 
-        if (mLauncher.getTaskbarController() != null) {
-            mLauncher.getTaskbarController().onHotseatUpdated();
+        if (mLauncher.getTaskbarUIController() != null) {
+            mLauncher.getTaskbarUIController().onHotseatUpdated();
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
index 0d4130d..540f748 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
@@ -16,12 +16,17 @@
 
 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.
@@ -29,47 +34,46 @@
  */
 public class ButtonProvider {
 
-    private int mMarginLeftRight;
-    private final Context mContext;
+    private final int mMarginLeftRight;
+    private final TaskbarActivityContext mContext;
 
-    public ButtonProvider(Context context) {
+    public ButtonProvider(TaskbarActivityContext context) {
         mContext = context;
-    }
-
-    public void setMarginLeftRight(int margin) {
-        mMarginLeftRight = margin;
+        mMarginLeftRight = context.getResources()
+                .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
     }
 
     public View getBack() {
         // Back button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back);
+        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
     }
 
     public View getDown() {
         // Ime down button
-        return getButtonForDrawable(R.drawable.ic_sysbar_back);
+        return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
     }
 
     public View getHome() {
         // Home button
-        return getButtonForDrawable(R.drawable.ic_sysbar_home);
+        return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
     }
 
     public View getRecents() {
         // Recents button
-        return getButtonForDrawable(R.drawable.ic_sysbar_recent);
+        return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
     }
 
     public View getImeSwitcher() {
         // IME Switcher Button
-        return getButtonForDrawable(R.drawable.ic_ime_switcher);
+        return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
     }
 
-    private View getButtonForDrawable(@DrawableRes int drawableId) {
+    private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
         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 bb3669b..287caab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
@@ -16,9 +16,6 @@
 
 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;
@@ -29,7 +26,6 @@
 public class ImeBarView extends RelativeLayout {
 
     private ButtonProvider mButtonProvider;
-    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
     private View mImeView;
 
     public ImeBarView(Context context) {
@@ -44,12 +40,9 @@
         super(context, attrs, defStyleAttr);
     }
 
-    public void construct(ButtonProvider buttonProvider) {
+    public void init(ButtonProvider buttonProvider) {
         mButtonProvider = buttonProvider;
-    }
 
-    public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) {
-        mControllerCallbacks = taskbarCallbacks;
         ActivityContext context = getActivityContext();
         RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
                 context.getDeviceProfile().iconSizePx,
@@ -64,24 +57,16 @@
 
         // 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/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
new file mode 100644
index 0000000..c2d107c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+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.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
+
+
+/**
+ * A data source which integrates with a Launcher instance
+ * TODO: Rename to have Launcher prefix
+ */
+
+public class LauncherTaskbarUIController extends TaskbarUIController {
+
+    private final BaseQuickstepLauncher mLauncher;
+    private final TaskbarStateHandler mTaskbarStateHandler;
+    private final TaskbarAnimationController mTaskbarAnimationController;
+    private final TaskbarHotseatController mHotseatController;
+
+    private final TaskbarActivityContext mContext;
+    final TaskbarDragLayer mTaskbarDragLayer;
+    final TaskbarView mTaskbarView;
+
+    private @Nullable Animator mAnimator;
+    private boolean mIsAnimatingToLauncher;
+
+    public LauncherTaskbarUIController(
+            BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
+        mContext = context;
+        mTaskbarDragLayer = context.getDragLayer();
+        mTaskbarView = mTaskbarDragLayer.findViewById(R.id.taskbar_view);
+
+        mLauncher = launcher;
+        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.setTaskbarUIController(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.setTaskbarUIController(null);
+    }
+
+    @Override
+    protected boolean isTaskbarTouchable() {
+        return !mIsAnimatingToLauncher;
+    }
+
+    private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
+        return new TaskbarAnimationControllerCallbacks() {
+            @Override
+            public void updateTaskbarBackgroundAlpha(float alpha) {
+                mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
+            }
+
+            @Override
+            public void updateTaskbarVisibilityAlpha(float alpha) {
+                mTaskbarView.setAlpha(alpha);
+            }
+
+            @Override
+            public void updateImeBarVisibilityAlpha(float alpha) {
+                mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
+            }
+
+            @Override
+            public void updateTaskbarScale(float scale) {
+                mTaskbarView.setScaleX(scale);
+                mTaskbarView.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
+                            + mLauncher.getHotseat().getTaskbarOffsetY());
+                } else {
+                    mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
+                }
+                mTaskbarView.setTranslationY(translationY);
+            }
+        };
+    }
+
+    /**
+     * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
+     */
+    public void onLauncherResumedOrPaused(boolean isResumed) {
+        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+        if (isResumed) {
+            mAnimator = createAnimToLauncher(null, duration);
+        } else {
+            mAnimator = createAnimToApp(duration);
+        }
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimator = null;
+            }
+        });
+        mAnimator.start();
+    }
+
+    /**
+     * Create Taskbar animation when going from an app to Launcher.
+     * @param toState If known, the state we will end up in when reaching Launcher.
+     */
+    public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
+        PendingAnimation anim = new PendingAnimation(duration);
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
+        if (toState != null) {
+            mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
+        }
+
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mIsAnimatingToLauncher = true;
+                mTaskbarView.setHolesAllowedInLayout(true);
+                mTaskbarView.updateHotseatItemsVisibility();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIsAnimatingToLauncher = false;
+                setTaskbarViewVisible(false);
+            }
+        });
+
+        return anim.buildAnim();
+    }
+
+    private Animator createAnimToApp(long duration) {
+        PendingAnimation anim = new PendingAnimation(duration);
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mTaskbarView.updateHotseatItemsVisibility();
+                setTaskbarViewVisible(true);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mTaskbarView.setHolesAllowedInLayout(false);
+            }
+        });
+        return anim.buildAnim();
+    }
+
+    @Override
+    protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
+        mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
+    }
+
+    /**
+     * Should be called when one or more items in the Hotseat have changed.
+     */
+    public void onHotseatUpdated() {
+        mHotseatController.onHotseatUpdated();
+    }
+
+    /**
+     * @param ev MotionEvent in screen coordinates.
+     * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
+     */
+    public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
+        return mTaskbarView.isEventOverAnyItem(ev);
+    }
+
+    public boolean isDraggingItem() {
+        return mTaskbarView.isDraggingItem();
+    }
+
+    /**
+     * 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();
+        int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
+        int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
+        int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
+        int hotseatBottomDiff = taskbarOffset;
+
+        RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
+        Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
+                mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
+        hotseatBoundsF.round(hotseatBounds);
+        mLauncher.getHotseat().setPadding(hotseatBounds.left,
+                hotseatBounds.top + hotseatTopDiff,
+                mTaskbarView.getWidth() - hotseatBounds.right,
+                mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
+    }
+
+    /**
+     * 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();
+        return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
+    }
+
+    void setTaskbarViewVisible(boolean isVisible) {
+        mTaskbarView.setIconsVisibility(isVisible);
+        mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
+    }
+
+    /**
+     * Contains methods that TaskbarAnimationController can call to interface with
+     * TaskbarController.
+     */
+    protected interface TaskbarAnimationControllerCallbacks {
+        void updateTaskbarBackgroundAlpha(float alpha);
+        void updateTaskbarVisibilityAlpha(float alpha);
+        void updateImeBarVisibilityAlpha(float alpha);
+        void updateTaskbarScale(float scale);
+        void updateTaskbarTranslationY(float translationY);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 3af51d5..8c3d453 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -15,56 +15,164 @@
  */
 package com.android.launcher3.taskbar;
 
-import android.content.ContextWrapper;
+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.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.BaseQuickstepLauncher;
+import com.android.launcher3.AbstractFloatingView;
 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.launcher3.views.BaseDragLayer;
+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;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
  * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
  * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
  */
-public class TaskbarActivityContext extends ContextWrapper implements ActivityContext {
+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";
 
     private final DeviceProfile mDeviceProfile;
     private final LayoutInflater mLayoutInflater;
-    private final TaskbarContainerView mTaskbarContainerView;
+    private final TaskbarDragLayer mDragLayer;
+    private final TaskbarIconController mIconController;
     private final MyDragController mDragController;
 
-    public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
-        super(launcher);
-        mDeviceProfile = launcher.getDeviceProfile().copy(this);
+    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;
+
         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
+        mDragLayer = (TaskbarDragLayer) mLayoutInflater
                 .inflate(R.layout.taskbar, null, false);
+        mIconController = new TaskbarIconController(this, mDragLayer);
         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 TaskbarContainerView getTaskbarContainerView() {
-        return mTaskbarContainerView;
+    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(mDragLayer, 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(mDragLayer, mWindowLayoutParams);
+    }
+
+    public boolean canShowNavButtons() {
+        return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
     }
 
     @Override
@@ -73,8 +181,8 @@
     }
 
     @Override
-    public BaseDragLayer<TaskbarActivityContext> getDragLayer() {
-        return mTaskbarContainerView;
+    public TaskbarDragLayer getDragLayer() {
+        return mDragLayer;
     }
 
     @Override
@@ -84,7 +192,7 @@
 
     @Override
     public Rect getFolderBoundingBox() {
-        return mTaskbarContainerView.getFolderBoundingBox();
+        return mDragLayer.getFolderBoundingBox();
     }
 
     @Override
@@ -92,6 +200,116 @@
         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(mDragLayer);
+    }
+
+    void onNavigationButtonClick(@TaskbarButton int buttonType) {
+        mNavButtonController.onButtonClick(buttonType);
+    }
+
+    /**
+     * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
+     */
+    public void setImeIsVisible(boolean isImeVisible) {
+        mIconController.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
+     */
+    public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
+        mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
+    }
+
+    /**
+     * 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);
@@ -106,7 +324,8 @@
         }
 
         @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 29f6935..e20ddf8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -21,6 +21,7 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -34,7 +35,7 @@
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
-    private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
+    private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
 
     // Background alpha.
     private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
@@ -55,7 +56,7 @@
             this::updateTranslationY);
 
     public TaskbarAnimationController(BaseQuickstepLauncher launcher,
-            TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) {
+            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
deleted file mode 100644
index 621bba7..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ /dev/null
@@ -1,180 +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 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;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.TouchController;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper;
-
-/**
- * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder.
- */
-public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext> {
-
-    private final int[] mTempLoc = new int[2];
-    private final int mFolderMargin;
-    private final Paint mTaskbarBackgroundPaint;
-
-    // Initialized in TaskbarController constructor.
-    private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks;
-
-    // Initialized in init.
-    private TaskbarView mTaskbarView;
-    private ViewTreeObserverWrapper.OnComputeInsetsListener mTaskbarInsetsComputer;
-
-    public TaskbarContainerView(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, 1 /* alphaChannelCount */);
-        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();
-    }
-
-    @Override
-    public void recreateControllers() {
-        mControllers = new TouchController[0];
-    }
-
-    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());
-        };
-    }
-
-    protected void cleanup() {
-        ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
-                mTaskbarInsetsComputer);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        cleanup();
-    }
-
-    @Override
-    protected boolean canFindActiveController() {
-        // Unlike super class, we want to be able to find controllers when touches occur in the
-        // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
-        return true;
-    }
-
-    @Override
-    public void onViewRemoved(View child) {
-        super.onViewRemoved(child);
-        mControllerCallbacks.onViewRemoved();
-    }
-
-    /**
-     * @return Bounds (in our coordinates) where an opened Folder can display.
-     */
-    protected Rect getFolderBoundingBox() {
-        Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
-        boundingBox.inset(mFolderMargin, mFolderMargin);
-        return boundingBox;
-    }
-
-    protected TaskbarActivityContext getTaskbarActivityContext() {
-        return mActivity;
-    }
-
-    @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.
-     * @param alpha 0 is fully transparent, 1 is fully opaque.
-     */
-    protected void setTaskbarBackgroundAlpha(float alpha) {
-        mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
-        invalidate();
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
deleted file mode 100644
index 6084e10..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ /dev/null
@@ -1,596 +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.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.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.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;
-
-/**
- * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
- */
-public class TaskbarController implements TaskbarOverviewProxyDelegate {
-
-    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;
-
-    // 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,
-            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,
-                createTaskbarHotseatControllerCallbacks());
-        mDragController = new TaskbarDragController(mLauncher);
-    }
-
-    private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
-        return new TaskbarAnimationControllerCallbacks() {
-            @Override
-            public void updateTaskbarBackgroundAlpha(float alpha) {
-                mTaskbarContainerView.setTaskbarBackgroundAlpha(alpha);
-            }
-
-            @Override
-            public void updateTaskbarVisibilityAlpha(float alpha) {
-                mTaskbarViewInApp.setAlpha(alpha);
-                mTaskbarViewOnHome.setAlpha(alpha);
-            }
-
-            @Override
-            public void updateImeBarVisibilityAlpha(float 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) {
-                mTaskbarViewInApp.setScaleX(scale);
-                mTaskbarViewInApp.setScaleY(scale);
-            }
-
-            @Override
-            public void updateTaskbarTranslationY(float translationY) {
-                if (translationY < 0) {
-                    // Resize to accommodate the max translation we'll reach.
-                    setTaskbarWindowHeight(mTaskbarSize.y
-                            + mLauncher.getHotseat().getTaskbarOffsetY());
-                } else {
-                    setTaskbarWindowHeight(mTaskbarSize.y);
-                }
-                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.
-     */
-    public void onLauncherResumedOrPaused(boolean isResumed) {
-        long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
-        if (mAnimator != null) {
-            mAnimator.cancel();
-        }
-        if (isResumed) {
-            mAnimator = createAnimToLauncher(null, duration);
-        } else {
-            mAnimator = createAnimToApp(duration);
-        }
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimator = null;
-            }
-        });
-        mAnimator.start();
-    }
-
-    /**
-     * Create Taskbar animation when going from an app to Launcher.
-     * @param toState If known, the state we will end up in when reaching Launcher.
-     */
-    public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
-        PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
-        if (toState != null) {
-            mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
-        }
-
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mIsAnimatingToLauncher = true;
-                mTaskbarViewInApp.updateHotseatItemsVisibility();
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mIsAnimatingToLauncher = false;
-                setWhichTaskbarViewIsVisible(mTaskbarViewOnHome);
-            }
-        });
-
-        return anim.buildAnim();
-    }
-
-    private Animator createAnimToApp(long duration) {
-        PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mTaskbarViewInApp.updateHotseatItemsVisibility();
-                setWhichTaskbarViewIsVisible(mTaskbarViewInApp);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-            }
-        });
-        return anim.buildAnim();
-    }
-
-    /**
-     * 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);
-    }
-
-    /**
-     * Should be called when one or more items in the Hotseat have changed.
-     */
-    public void onHotseatUpdated() {
-        mHotseatController.onHotseatUpdated();
-    }
-
-    /**
-     * @param ev MotionEvent in screen coordinates.
-     * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
-     */
-    public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
-        return mTaskbarViewInApp.isEventOverAnyItem(ev);
-    }
-
-    public boolean 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.
-     */
-    public void alignRealHotseatWithTaskbar() {
-        Rect hotseatBounds = new Rect();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
-        int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
-        int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
-        int hotseatBottomDiff = taskbarOffset;
-
-        mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
-        mLauncher.getHotseat().setPadding(hotseatBounds.left,
-                hotseatBounds.top + hotseatTopDiff,
-                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 = mTaskbarContainerView.getTaskbarActivityContext()
-                .getDeviceProfile();
-        DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
-                .getDeviceProfile();
-        return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
-    }
-
-    /**
-     * 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();
-    }
-
-    /**
-     * Contains methods that TaskbarAnimationController can call to interface with
-     * TaskbarController.
-     */
-    protected interface TaskbarAnimationControllerCallbacks {
-        void updateTaskbarBackgroundAlpha(float alpha);
-        void updateTaskbarVisibilityAlpha(float alpha);
-        void updateImeBarVisibilityAlpha(float alpha);
-        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 5eb34cb..ee44927 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -20,6 +20,7 @@
 
 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;
@@ -29,7 +30,6 @@
 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 BaseQuickstepLauncher mLauncher;
+    private final Context mContext;
     private final int mDragIconSize;
 
-    public TaskbarDragController(BaseQuickstepLauncher launcher) {
-        mLauncher = launcher;
-        Resources resources = mLauncher.getResources();
+    public TaskbarDragController(Context context) {
+        mContext = context;
+        Resources resources = mContext.getResources();
         mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
     }
 
@@ -63,7 +63,6 @@
         }
 
         BubbleTextView btv = (BubbleTextView) view;
-
         View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
             @Override
             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
@@ -87,7 +86,7 @@
         Intent intent = null;
         if (tag instanceof WorkspaceItemInfo) {
             WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
-            LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class);
+            LauncherApps launcherApps = mContext.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/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
new file mode 100644
index 0000000..45ec911
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+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 TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
+
+    private final int mFolderMargin;
+    private final Paint mTaskbarBackgroundPaint;
+
+    private TaskbarIconController.Callbacks mControllerCallbacks;
+    private TaskbarView mTaskbarView;
+
+    private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+
+    public TaskbarDragLayer(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr, int defStyleRes) {
+        super(context, attrs, 1 /* alphaChannelCount */);
+        mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+        mTaskbarBackgroundPaint = new Paint();
+        mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+        recreateControllers();
+    }
+
+    @Override
+    public void recreateControllers() {
+        mControllers = new TouchController[0];
+    }
+
+    public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
+        mControllerCallbacks = callbacks;
+        mTaskbarView = taskbarView;
+    }
+
+    private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
+        if (mControllerCallbacks != null) {
+            mControllerCallbacks.updateInsetsTouchability(insetsInfo);
+        }
+    }
+
+    protected void onDestroy() {
+        ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(),
+                mTaskbarInsetsComputer);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        onDestroy();
+    }
+
+    @Override
+    protected boolean canFindActiveController() {
+        // Unlike super class, we want to be able to find controllers when touches occur in the
+        // gesture area. For example, this allows Folder to close itself when touching the Taskbar.
+        return true;
+    }
+
+    public void updateImeBarVisibilityAlpha(float alpha) {
+        if (mControllerCallbacks != null) {
+            mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
+        }
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        if (mControllerCallbacks != null) {
+            mControllerCallbacks.onDragLayerViewRemoved();
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
+                canvas.getHeight(), mTaskbarBackgroundPaint);
+        super.dispatchDraw(canvas);
+    }
+
+    /**
+     * @return Bounds (in our coordinates) where an opened Folder can display.
+     */
+    protected Rect getFolderBoundingBox() {
+        Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
+        boundingBox.inset(mFolderMargin, mFolderMargin);
+        return boundingBox;
+    }
+
+
+    /**
+     * Sets the alpha of the background color behind all the Taskbar contents.
+     * @param alpha 0 is fully transparent, 1 is fully opaque.
+     */
+    protected void setTaskbarBackgroundAlpha(float alpha) {
+        mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
+        invalidate();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
index 68829cd..91cf7ef 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
@@ -26,6 +26,8 @@
 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.
  */
@@ -33,13 +35,12 @@
 
     private final BaseQuickstepLauncher mLauncher;
     private final Hotseat mHotseat;
-    private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks;
+    private final Consumer<ItemInfo[]> 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() {
@@ -47,8 +48,8 @@
         }
     };
 
-    public TaskbarHotseatController(BaseQuickstepLauncher launcher,
-            TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) {
+    public TaskbarHotseatController(
+            BaseQuickstepLauncher launcher, Consumer<ItemInfo[]> taskbarCallbacks) {
         mLauncher = launcher;
         mHotseat = mLauncher.getHotseat();
         mTaskbarCallbacks = taskbarCallbacks;
@@ -85,10 +86,6 @@
             }
         }
 
-        mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos);
-    }
-
-    protected int getNumHotseatIcons() {
-        return mNumHotseatIcons;
+        mTaskbarCallbacks.accept(hotseatItemInfos);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
new file mode 100644
index 0000000..683a5b9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
@@ -0,0 +1,163 @@
+/*
+ * 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 TaskbarDragLayer mDragLayer;
+
+    private final TaskbarView mTaskbarView;
+    private final ImeBarView mImeBarView;
+
+    @NonNull
+    private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
+
+    TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+        mActivity = activity;
+        mDragLayer = dragLayer;
+        mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+        mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
+    }
+
+    public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
+        mDragLayer.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;
+
+        mDragLayer.init(new Callbacks(), mTaskbarView);
+    }
+
+    public void onDestroy() {
+        mDragLayer.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(mDragLayer, isImeVisible);
+    }
+
+    /**
+     * Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
+     */
+    public class Callbacks {
+
+        /**
+         * Called to update the touchable insets
+         */
+        public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+            insetsInfo.touchableRegion.setEmpty();
+            if (mDragLayer.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) {
+                    mDragLayer.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 = mDragLayer.getWidth() - mTaskbarView.getRight();
+            insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
+        }
+
+        public void onDragLayerViewRemoved() {
+            int count = mDragLayer.getChildCount();
+            // Ensure no other children present (like Folders, etc)
+            for (int i = 0; i < count; i++) {
+                View v = mDragLayer.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
new file mode 100644
index 0000000..d026bfb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+    private boolean mUserUnlocked = false;
+
+    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;
+        }
+    }
+
+    /**
+     * Called when the user is unlocked
+     */
+    public void onUserUnlocked() {
+        mUserUnlocked = true;
+        recreateTaskbar();
+    }
+
+    /**
+     * 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 LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+        }
+    }
+
+    private void recreateTaskbar() {
+        destroyExistingTaskbar();
+        if (!FeatureFlags.ENABLE_TASKBAR.get()) {
+            return;
+        }
+        if (!mUserUnlocked) {
+            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 LauncherTaskbarUIController(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.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.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 54e1610..3b5afad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher3.taskbar;
 
-import android.content.Context;
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.content.Intent;
 import android.view.inputmethod.InputMethodManager;
 
@@ -53,11 +54,10 @@
     static final int BUTTON_RECENTS = BUTTON_HOME << 1;
     static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
 
+    private final TouchInteractionService mService;
 
-    private final Context mContext;
-
-    public TaskbarNavButtonController(Context context) {
-        mContext = context;
+    public TaskbarNavButtonController(TouchInteractionService service) {
+        mService = service;
     }
 
     public void onButtonClick(@TaskbarButton int buttonType) {
@@ -78,13 +78,13 @@
     }
 
     private void navigateHome() {
-        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+        mService.startActivity(new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_HOME)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
     }
 
     private void navigateToOverview() {
-        TouchInteractionService.getInstance().getOverviewCommandHelper()
+        mService.getOverviewCommandHelper()
                 .addCommand(OverviewCommandHelper.TYPE_SHOW);
     }
 
@@ -93,8 +93,8 @@
     }
 
     private void showIMESwitcher() {
-        mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
-                true /* showAuxiliarySubtypes */, mContext.getDisplayId());
+        mService.getSystemService(InputMethodManager.class)
+                .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
+                        DEFAULT_DISPLAY);
     }
-
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index 6ea51fa..a701aae 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -24,59 +24,52 @@
 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 #setTaskbarCallbacks} is never called).
+ * isn't present (i.e. {@link #setAnimationController} 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 TaskbarController.TaskbarStateHandlerCallbacks mTaskbarCallbacks = null;
+    private @Nullable TaskbarAnimationController mAnimationController = null;
 
     public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
     }
 
-    public void setTaskbarCallbacks(TaskbarController.TaskbarStateHandlerCallbacks callbacks) {
-        mTaskbarCallbacks = callbacks;
+    public void setAnimationController(TaskbarAnimationController callbacks) {
+        mAnimationController = callbacks;
     }
 
     @Override
     public void setState(LauncherState state) {
-        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));
+        setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
     }
 
     @Override
     public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
             PendingAnimation animation) {
-        if (mTaskbarCallbacks == null) {
+        setState(toState, animation);
+    }
+
+    private void setState(LauncherState toState, PropertySetter setter) {
+        if (mAnimationController == null) {
             return;
         }
 
-        AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
-        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
-        AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget();
         boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
-        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);
+        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);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
new file mode 100644
index 0000000..50adead
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -0,0 +1,41 @@
+/*
+ * 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(TaskbarDragLayer 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 9e8013e..c6573a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,20 +15,14 @@
  */
 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_RECENTS;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
-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;
@@ -51,17 +45,12 @@
 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;
@@ -69,17 +58,16 @@
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
 
-    // Initialized in TaskbarController constructor.
-    private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
-    // Scale on elements that aren't icons.
-    private float mNonIconScale;
-    private int mItemMarginLeftRight;
+    private final int mItemMarginLeftRight;
 
-    // Initialized in init().
-    private LayoutTransition mLayoutTransition;
-    private int mHotseatStartIndex;
-    private int mHotseatEndIndex;
-    private LinearLayout mButtonRegion;
+    private final TaskbarActivityContext mActivityContext;
+
+    // Initialized in TaskbarController constructor.
+    private View.OnClickListener mIconClickListener;
+    private View.OnLongClickListener mIconLongClickListener;
+
+    LinearLayout mSystemButtonContainer;
+    LinearLayout mHotseatIconsContainer;
 
     // Delegate touches to the closest view if within mIconTouchSize.
     private boolean mDelegateTargeted;
@@ -91,10 +79,12 @@
     // 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);
     }
@@ -111,80 +101,58 @@
     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();
     }
 
-    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);
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mSystemButtonContainer = findViewById(R.id.system_button_layout);
+        mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
     }
 
-    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;
+    protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
+                ButtonProvider buttonProvider) {
+        mIconClickListener = clickListener;
+        mIconLongClickListener = longClickListener;
+        mButtonProvider = buttonProvider;
+
+        if (mActivityContext.canShowNavButtons()) {
             createNavButtons();
         } else {
-            mNavButtonStartIndex = -1;
-            removeNavButtons();
+            mSystemButtonContainer.setVisibility(GONE);
         }
 
-        mHotseatStartIndex = mNavButtonStartIndex + 1;
-        mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
+        int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
-
-        mLayoutTransition = new LayoutTransition();
-        addUpdateListenerForAllLayoutTransitions(() -> {
-            if (getLayoutTransition() == mLayoutTransition) {
-                mControllerCallbacks.onItemPositionsChanged(this);
-            }
-        });
-        setLayoutTransition(mLayoutTransition);
     }
 
-    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);
+    /**
+     * 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
         }
     }
 
-    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();
+    private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
+        if (mAreHolesAllowed != areHolesAllowed) {
+            mAreHolesAllowed = areHolesAllowed;
+            updateHotseatItemsVisibility();
+            onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+                    makeMeasureSpec(getMeasuredHeight(), EXACTLY));
+            onLayout(false, getLeft(), getTop(), getRight(), getBottom());
+        }
     }
 
     /**
@@ -192,10 +160,9 @@
      */
     protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
         for (int i = 0; i < hotseatItemInfos.length; i++) {
-            ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i
-                    : hotseatItemInfos.length - i - 1];
-            int hotseatIndex = mHotseatStartIndex + i;
-            View hotseatView = getChildAt(hotseatIndex);
+            ItemInfo hotseatItemInfo = hotseatItemInfos[
+                    !mIsRtl ? i : hotseatItemInfos.length - i - 1];
+            View hotseatView = mHotseatIconsContainer.getChildAt(i);
 
             // Replace any Hotseat views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
@@ -213,23 +180,23 @@
             } else {
                 expectedLayoutResId = R.layout.taskbar_app_icon;
             }
-            if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
+            if (hotseatView == null
+                    || hotseatView.getSourceLayoutResId() != expectedLayoutResId
                     || needsReinflate) {
-                removeView(hotseatView);
-                ActivityContext activityContext = getActivityContext();
+                mHotseatIconsContainer.removeView(hotseatView);
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
-                            getActivityContext(), this, folderInfo);
+                            mActivityContext, this, folderInfo);
                     folderIcon.setTextVisible(false);
                     hotseatView = folderIcon;
                 } else {
                     hotseatView = inflate(expectedLayoutResId);
                 }
-                int iconSize = activityContext.getDeviceProfile().iconSizePx;
+                int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
                 LayoutParams lp = new LayoutParams(iconSize, iconSize);
                 lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
-                addView(hotseatView, hotseatIndex, lp);
+                mHotseatIconsContainer.addView(hotseatView, i, lp);
             }
 
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -237,13 +204,11 @@
                     && hotseatItemInfo instanceof WorkspaceItemInfo) {
                 ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
                         (WorkspaceItemInfo) hotseatItemInfo);
-                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
-                hotseatView.setOnLongClickListener(
-                        mControllerCallbacks.getItemOnLongClickListener());
+                hotseatView.setOnClickListener(mIconClickListener);
+                hotseatView.setOnLongClickListener(mIconLongClickListener);
             } else if (isFolder) {
-                hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
-                hotseatView.setOnLongClickListener(
-                        mControllerCallbacks.getItemOnLongClickListener());
+                hotseatView.setOnClickListener(mIconClickListener);
+                hotseatView.setOnLongClickListener(mIconLongClickListener);
             } else {
                 hotseatView.setOnClickListener(null);
                 hotseatView.setOnLongClickListener(null);
@@ -254,24 +219,14 @@
     }
 
     protected void updateHotseatItemsVisibility() {
-        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
-            updateHotseatItemVisibility(getChildAt(i));
+        for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
+            updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
         }
     }
 
     private void updateHotseatItemVisibility(View hotseatView) {
-        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);
-            }
-        }
+        hotseatView.setVisibility(
+                hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
     }
 
     @Override
@@ -378,49 +333,20 @@
         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(
-                context.getDeviceProfile().iconSizePx,
-                context.getDeviceProfile().iconSizePx
+                mActivityContext.getDeviceProfile().iconSizePx,
+                mActivityContext.getDeviceProfile().iconSizePx
         );
         buttonParams.gravity = Gravity.CENTER;
 
-        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);
+        mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
+        mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
+        mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
     }
 
     @Override
@@ -428,7 +354,7 @@
         switch (event.getAction()) {
             case DragEvent.ACTION_DRAG_STARTED:
                 mIsDraggingItem = true;
-                AbstractFloatingView.closeAllOpenViews(getActivityContext());
+                AbstractFloatingView.closeAllOpenViews(mActivityContext);
                 return true;
             case DragEvent.ACTION_DRAG_ENDED:
                 mIsDraggingItem = false;
@@ -445,26 +371,26 @@
      * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
      */
     protected RectF getHotseatBounds() {
-        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;
-            }
+        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();
         }
-        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.
@@ -495,7 +421,7 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false);
+        return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
     }
 
     @Override
@@ -503,7 +429,11 @@
         // Ignore, we just implement Insettable to draw behind system insets.
     }
 
-    private <T extends Context & ActivityContext> T getActivityContext() {
-        return ActivityContext.lookupContext(getContext());
+    public void setIconsVisibility(boolean isVisible) {
+        mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
+    }
+
+    public boolean areIconsVisible() {
+        return mHotseatIconsContainer.getVisibility() == VISIBLE;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 45bb521..f0b02b3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -165,7 +165,7 @@
     @Override
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
         // Only pause is taskbar controller is not present
-        mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null);
+        mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
         return super.startActivitySafely(v, intent, item);
     }
 
@@ -233,9 +233,9 @@
     @Override
     public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
         super.bindWorkspaceItemsChanged(updated);
-        if (getTaskbarController() != null && updated.stream()
+        if (getTaskbarUIController() != null && updated.stream()
                 .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) {
-            getTaskbarController().onHotseatUpdated();
+            getTaskbarUIController().onHotseatUpdated();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index f5ddd0e..86bf119 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -52,7 +52,6 @@
 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;
@@ -123,11 +122,6 @@
         return null;
     }
 
-    @Nullable
-    public TaskbarController getTaskbarController() {
-        return null;
-    }
-
     public final boolean isResumed() {
         ACTIVITY_TYPE activity = getCreatedActivity();
         return activity != null && activity.hasBeenResumed();
@@ -368,13 +362,6 @@
     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 5217c3b..9014774 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -22,7 +22,6 @@
 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;
@@ -42,7 +41,7 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.taskbar.TaskbarController;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -164,13 +163,12 @@
     }
 
     @Nullable
-    @Override
-    public TaskbarController getTaskbarController() {
+    private LauncherTaskbarUIController getTaskbarController() {
         BaseQuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
         }
-        return launcher.getTaskbarController();
+        return launcher.getTaskbarUIController();
     }
 
     @Nullable
@@ -278,13 +276,13 @@
     @Override
     public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
             long duration) {
-        TaskbarController taskbarController = getTaskbarController();
+        LauncherTaskbarUIController uiController = getTaskbarController();
         Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
-        if (taskbarController == null) {
+        if (uiController == null) {
             return superAnimator;
         }
         LauncherState toState = stateFromGestureEndTarget(endTarget);
-        Animator taskbarAnimator = taskbarController.createAnimToLauncher(toState, duration);
+        Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
         if (superAnimator == null) {
             return taskbarAnimator;
         } else {
@@ -301,31 +299,21 @@
     }
 
     @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) {
+        LauncherTaskbarUIController uiController = getTaskbarController();
+        if (uiController == null) {
             return super.deferStartingActivity(deviceState, ev);
         }
-        return taskbarController.isEventOverAnyTaskbarItem(ev);
+        return uiController.isEventOverAnyTaskbarItem(ev);
     }
 
     @Override
     public boolean shouldCancelCurrentGesture() {
-        TaskbarController taskbarController = getTaskbarController();
-        if (taskbarController == null) {
+        LauncherTaskbarUIController uiController = getTaskbarController();
+        if (uiController == null) {
             return super.shouldCancelCurrentGesture();
         }
-        return taskbarController.isDraggingItem();
+        return uiController.isDraggingItem();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index edc7a3c..7956fcc 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -52,7 +52,6 @@
 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;
@@ -75,6 +74,7 @@
 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,21 +147,9 @@
     private OverscrollPlugin mOverscrollPlugin;
 
     /**
-     * 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.
+     * Local IOverviewProxy implementation with some methods for local components
      */
-    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;
-        }
+    public class TISBinder extends IOverviewProxy.Stub {
 
         @BinderThread
         public void onInitialize(Bundle bundle) {
@@ -274,40 +262,24 @@
 
         @Override
         public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
-                int backDisposition, boolean showImeSwitcher) throws RemoteException {
-            if (mTaskbarOverviewProxyDelegate == null) {
-                return;
-            }
-            MAIN_EXECUTOR.execute(() -> {
-                if (mTaskbarOverviewProxyDelegate == null) {
-                    return;
-                }
-                mTaskbarOverviewProxyDelegate
-                        .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher);
-            });
+                int backDisposition, boolean showImeSwitcher) {
+            MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
+                    displayId, vis, backDisposition, showImeSwitcher));
         }
-    };
 
-    public interface TaskbarOverviewProxyDelegate {
-        void updateImeStatus(int displayId, int vis, int backDisposition,
-                boolean showImeSwitcher);
+        public TaskbarManager getTaskbarManager() {
+            return mTaskbarManager;
+        }
     }
 
     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;
@@ -336,9 +308,7 @@
 
     private DisplayManager mDisplayManager;
 
-    public TouchInteractionService() {
-        sInstance = this;
-    }
+    private TaskbarManager mTaskbarManager;
 
     @Override
     public void onCreate() {
@@ -348,13 +318,15 @@
         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);
+        mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
         ProtoTracer.INSTANCE.get(this).add(this);
-        mDisplayManager = getSystemService(DisplayManager.class);
-
         sConnected = true;
     }
 
@@ -468,8 +440,7 @@
             int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
             SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
             mOverviewComponentObserver.onSystemUiStateChanged();
-            mOverviewComponentObserver.getActivityInterface().onSystemUiFlagsChanged(
-                    systemUiStateFlags);
+            mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
 
             if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
                     (systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
@@ -512,6 +483,7 @@
         getSystemService(AccessibilityManager.class)
                 .unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
 
+        mTaskbarManager.destroy();
         sConnected = false;
         super.onDestroy();
     }
@@ -519,7 +491,7 @@
     @Override
     public IBinder onBind(Intent intent) {
         Log.d(TAG, "Touch service connected: user=" + getUserId());
-        return mMyBinder;
+        return new TISBinder();
     }
 
     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 d430028..49aec93 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, taskbarRow;
+                final int hotseatRow, qsbRow;
                 if (grid.isTaskbarPresent) {
                     qsbRow = grid.inv.numRows + 1;
                     hotseatRow = grid.inv.numRows + 2;
@@ -124,16 +124,12 @@
                     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
deleted file mode 100644
index 96ae43d..0000000
--- a/res/layout/taskbar_view.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index 6b5678c..e1214ff 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -82,7 +82,7 @@
         mTestProfile.numColumns = 5;
         mUserHandle = Process.myUserHandle();
         mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
-                mIconCache, null, null, null);
+                mIconCache, null, null);
         mAdapter.registerAdapterDataObserver(mListener);
 
         doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 12a092d..84a03d5 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -105,8 +105,7 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false,
-                /* searchBarUIHelper= */ null);
+                /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
                 LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
     }
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index e090341..075c58d 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -105,8 +105,7 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false,
-                /* searchBarUIHelper= */ null);
+                /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
                 LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
     }
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0935d1c..0c6e717 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -111,8 +111,7 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false,
-                /* searchBarUIHelper= */ null);
+                /* iconLongClickListener= */ view -> false);
         mViewHolderBinder = new WidgetsListTableViewHolderBinder(
                 mContext,
                 LayoutInflater.from(mTestActivity),
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3d044d6..322c6ee 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -447,10 +447,6 @@
      * @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/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 1df9df6..bfa1769 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1080,9 +1080,10 @@
             cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
 
             // Now get the rect in drag layer coordinates.
-            getBoundsForViewInDragLayer(launcher.getDragLayer(), workspace, mTempRect, false,
+            getBoundsForViewInDragLayer(launcher.getDragLayer(), this, mTempRect, true,
                     mTmpFloatArray, mTempRectF);
             Utilities.setRect(mTempRectF, mTempRect);
+
             ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, pageId);
         }
     }
@@ -2594,7 +2595,9 @@
         final int cellWidth = mCellWidth;
         final int cellHeight = mCellHeight;
 
-        final int hStartPadding = getPaddingLeft();
+        // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates
+        final int hStartPadding = getPaddingLeft()
+                + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         final int vStartPadding = getPaddingTop();
 
         int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b2a9e75..ff380ce 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,7 +49,6 @@
     private final View mQsb;
     private final int mQsbHeight;
 
-    private final View mTaskbarView;
     private final int mTaskbarViewHeight;
 
     public Hotseat(Context context) {
@@ -67,10 +66,7 @@
         mQsbHeight = mQsb.getLayoutParams().height;
         addView(mQsb);
 
-        mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false);
-        mTaskbarViewHeight = mTaskbarView.getLayoutParams().height;
-        // We want taskbar in the back so its background applies to Hotseat as well.
-        addView(mTaskbarView, 0);
+        mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
     }
 
     /**
@@ -187,8 +183,6 @@
         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
@@ -202,13 +196,6 @@
         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);
     }
 
     /**
@@ -244,10 +231,4 @@
         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 cf90216..8889e60 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1942,13 +1942,6 @@
 
     @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
@@ -2860,13 +2853,6 @@
                 .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/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index b6cc6d6..dabbdd3 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -81,6 +81,8 @@
 
     public LauncherAppState(Context context) {
         this(context, LauncherFiles.APP_ICONS_DB);
+        Log.v(Launcher.TAG, "LauncherAppState initiated");
+        Preconditions.assertUIThread();
 
         mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher());
 
@@ -132,8 +134,6 @@
     }
 
     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
-        Log.v(Launcher.TAG, "LauncherAppState initiated");
-        Preconditions.assertUIThread();
         mContext = context;
 
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
@@ -142,6 +142,7 @@
                 iconCacheFileName, mIconProvider);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
         mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
+        mOnTerminateCallback.add(mIconCache::close);
     }
 
     private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7ae729a..2884fba 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -324,15 +324,17 @@
     }
 
     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) {
-            float cx = r.centerX();
-            float cy = r.centerY();
-            r.offset(-cx, -cy);
+            r.offset(-px, -py);
             r.left = r.left * scale;
             r.top = r.top * scale ;
             r.right = r.right * scale;
             r.bottom = r.bottom * scale;
-            r.offset(cx, cy);
+            r.offset(px, py);
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 355ccad..2443b83 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -157,11 +158,17 @@
      * If this app is installed and supports incremental downloads, the progress bar will be updated
      * the app's total download progress. Otherwise, the progress bar will be updated to the app's
      * installation progress.
+     *
+     * If this app is fully downloaded, the app icon will be reapplied.
      */
     public void updateProgressBar(AppInfo app) {
         updateAllIcons((child) -> {
             if (child.getTag() == app) {
-                child.applyProgressLevel();
+                if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
+                    child.applyFromApplicationInfo(app);
+                } else {
+                    child.applyProgressLevel();
+                }
             }
         });
     }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index af7896a..a0c598a 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -298,7 +298,7 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        if (mHeaderCollapsed && mTabLayout.getVisibility() == VISIBLE
+        if (mHeaderCollapsed && !mCollapsed && mTabLayout.getVisibility() == VISIBLE
                 && mHeaderColor != Color.TRANSPARENT) {
             mBGPaint.setColor(mHeaderColor);
             mBGPaint.setAlpha((int) (255 * mHeaderAnimator.getAnimatedFraction()));
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index a03e48d..8ca157b 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -4,6 +4,7 @@
 import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
 import static com.android.launcher3.util.Themes.isThemedIconEnabled;
 
+import android.annotation.TargetApi;
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.pm.PackageManager;
@@ -12,14 +13,24 @@
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Xml;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Executors;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -65,6 +76,11 @@
     private static final String ICON_THEMED = "/icon_themed";
     private static final String BOOLEAN_VALUE = "boolean_value";
 
+    private static final String KEY_SURFACE_PACKAGE = "surface_package";
+    private static final String KEY_CALLBACK = "callback";
+
+    private final ArrayMap<IBinder, PreviewLifecycleObserver> mActivePreviews = new ArrayMap<>();
+
     @Override
     public boolean onCreate() {
         return true;
@@ -177,10 +193,75 @@
             return null;
         }
 
-        if (!METHOD_GET_PREVIEW.equals(method)) {
+        if (!Utilities.ATLEAST_R || !METHOD_GET_PREVIEW.equals(method)) {
             return null;
         }
+        return getPreview(extras);
+    }
 
-        return new PreviewSurfaceRenderer(getContext(), extras).render();
+    @TargetApi(Build.VERSION_CODES.R)
+    private synchronized Bundle getPreview(Bundle request) {
+        PreviewLifecycleObserver observer = null;
+        try {
+            PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request);
+
+            // Destroy previous
+            destroyObserver(mActivePreviews.get(renderer.getHostToken()));
+
+            observer = new PreviewLifecycleObserver(renderer);
+            mActivePreviews.put(renderer.getHostToken(), observer);
+
+            renderer.loadAsync();
+            renderer.getHostToken().linkToDeath(observer, 0);
+
+            Bundle result = new Bundle();
+            result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
+
+            Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(), observer));
+            Message msg = Message.obtain();
+            msg.replyTo = messenger;
+            result.putParcelable(KEY_CALLBACK, msg);
+            return result;
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to generate preview", e);
+            if (observer != null) {
+                destroyObserver(observer);
+            }
+            return null;
+        }
+    }
+
+    private synchronized void destroyObserver(PreviewLifecycleObserver observer) {
+        if (observer == null || observer.destroyed) {
+            return;
+        }
+        observer.destroyed = true;
+        observer.renderer.getHostToken().unlinkToDeath(observer, 0);
+        Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy);
+        PreviewLifecycleObserver cached = mActivePreviews.get(observer.renderer.getHostToken());
+        if (cached == observer) {
+            mActivePreviews.remove(observer.renderer.getHostToken());
+        }
+    }
+
+    private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient {
+
+        public final PreviewSurfaceRenderer renderer;
+        public boolean destroyed = false;
+
+        PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) {
+            this.renderer = renderer;
+        }
+
+        @Override
+        public boolean handleMessage(Message message) {
+            destroyObserver(this);
+            return true;
+        }
+
+        @Override
+        public void binderDied() {
+            destroyObserver(this);
+        }
     }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index f5b6890..2a1aec8 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -23,7 +23,6 @@
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.annotation.TargetApi;
 import android.app.Fragment;
@@ -32,7 +31,6 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
-import android.content.pm.ShortcutInfo;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -43,11 +41,12 @@
 import android.os.Looper;
 import android.os.Process;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.TextClock;
 
 import com.android.launcher3.BubbleTextView;
@@ -57,23 +56,17 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
-import com.android.launcher3.model.LoaderResults;
-import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.FolderInfo;
@@ -100,13 +93,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 /**
  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -120,8 +107,6 @@
 public class LauncherPreviewRenderer extends ContextWrapper
         implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
-    private static final String TAG = "LauncherPreviewRenderer";
-
     /**
      * Context used just for preview. It also provides a few objects (e.g. UserCache) just for
      * preview purposes.
@@ -138,9 +123,15 @@
         private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
                 new ConcurrentLinkedQueue<>();
 
+        private boolean mDestroyed = false;
+
         public PreviewContext(Context base, InvariantDeviceProfile idp) {
             super(base);
             mIdp = idp;
+            mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp);
+            mObjectMap.put(LauncherAppState.INSTANCE,
+                    new LauncherAppState(this, null /* iconCacheFileName */));
+
         }
 
         @Override
@@ -149,11 +140,9 @@
         }
 
         public void onDestroy() {
-            CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get(
-                    CustomWidgetManager.INSTANCE);
-            if (customWidgetManager != null) {
-                customWidgetManager.onDestroy();
-            }
+            CustomWidgetManager.INSTANCE.get(this).onDestroy();
+            LauncherAppState.INSTANCE.get(this).onTerminate();
+            mDestroyed = true;
         }
 
         /**
@@ -162,17 +151,12 @@
          */
         public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
                 MainThreadInitializedObject.ObjectProvider<T> provider) {
+            if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) {
+                throw new RuntimeException("Context already destroyed");
+            }
             if (!mAllowedObjects.contains(mainThreadInitializedObject)) {
                 throw new IllegalStateException("Leaking unknown objects");
             }
-            if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
-                throw new IllegalStateException(
-                        "Should not use MainThreadInitializedObject to initialize this with "
-                                + "PreviewContext");
-            }
-            if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
-                return (T) mIdp;
-            }
             if (mObjectMap.containsKey(mainThreadInitializedObject)) {
                 return (T) mObjectMap.get(mainThreadInitializedObject);
             }
@@ -210,7 +194,6 @@
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
     private final DeviceProfile mDp;
-    private final boolean mMigrated;
     private final Rect mInsets;
     private final WorkspaceItemInfo mWorkspaceItemInfo;
     private final LayoutInflater mHomeElementInflater;
@@ -218,18 +201,26 @@
     private final Hotseat mHotseat;
     private final CellLayout mWorkspace;
 
-    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
+    public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) {
         super(context);
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
         mDp = idp.getDeviceProfile(context).copy(context);
-        mMigrated = migrated;
 
-        // TODO: get correct insets once display cutout API is available.
-        mInsets = new Rect();
-        mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
-        mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
+        if (Utilities.ATLEAST_R) {
+            WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
+                    .getCurrentWindowMetrics().getWindowInsets();
+            mInsets = new Rect(
+                    currentWindowInsets.getSystemWindowInsetLeft(),
+                    currentWindowInsets.getSystemWindowInsetTop(),
+                    currentWindowInsets.getSystemWindowInsetRight(),
+                    currentWindowInsets.getSystemWindowInsetBottom());
+        } else {
+            mInsets = new Rect();
+            mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2;
+            mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2;
+        }
         mDp.updateInsets(mInsets);
 
         BaseIconFactory iconFactory =
@@ -265,8 +256,9 @@
     }
 
     /** Populate preview and render it. */
-    public View getRenderedView() {
-        populate();
+    public View getRenderedView(BgDataModel dataModel,
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+        populate(dataModel, widgetProviderInfoMap);
         return mRootView;
     }
 
@@ -392,38 +384,17 @@
         }
     }
 
-    private void populate() {
-        WorkspaceFetcher fetcher;
-        PreviewContext previewContext = null;
-        if (mMigrated) {
-            previewContext = new PreviewContext(mContext, mIdp);
-            LauncherAppState appForPreview = new LauncherAppState(
-                    previewContext, null /* iconCacheFileName */);
-            fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
-            MODEL_EXECUTOR.execute(fetcher);
-        } else {
-            fetcher = new WorkspaceItemsInfoFetcher();
-            LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                    (LauncherModel.ModelUpdateTask) fetcher);
-        }
-        WorkspaceResult workspaceResult = fetcher.get();
-        if (previewContext != null) {
-            previewContext.onDestroy();
-        }
-
-        if (workspaceResult == null) {
-            return;
-        }
-
+    private void populate(BgDataModel dataModel,
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
         // Separate the items that are on the current screen, and the other remaining items.
         ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
         ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
         filterCurrentWorkspaceItems(0 /* currentScreenId */,
-                workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+                dataModel.workspaceItems, currentWorkspaceItems,
                 otherWorkspaceItems);
-        filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+        filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets,
                 currentAppWidgets, otherAppWidgets);
         sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
         for (ItemInfo itemInfo : currentWorkspaceItems) {
@@ -444,12 +415,12 @@
             switch (itemInfo.itemType) {
                 case Favorites.ITEM_TYPE_APPWIDGET:
                 case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                    if (mMigrated) {
-                        inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
-                                workspaceResult.mWidgetProvidersMap);
+                    if (widgetProviderInfoMap != null) {
+                        inflateAndAddWidgets(
+                                (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap);
                     } else {
                         inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
-                                workspaceResult.mWidgetsModel);
+                                dataModel.widgetsModel);
                     }
                     break;
                 default:
@@ -458,8 +429,10 @@
         }
         IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
                 mDp.numShownHotseatIcons);
-        List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
-                ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
+        FixedContainerItems hotseatpredictions =
+                dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
+        List<ItemInfo> predictions = hotseatpredictions == null
+                ? Collections.emptyList() : hotseatpredictions.items;
         int count = Math.min(ranks.size(), predictions.size());
         for (int i = 0; i < count; i++) {
             int rank = ranks.get(i);
@@ -494,109 +467,4 @@
         view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
         view.layout(0, 0, width, height);
     }
-
-    private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
-            WorkspaceFetcher {
-
-        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
-
-        private LauncherAppState mApp;
-        private LauncherModel mModel;
-        private BgDataModel mBgDataModel;
-        private AllAppsList mAllAppsList;
-
-        @Override
-        public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel,
-                AllAppsList allAppsList, Executor uiExecutor) {
-            mApp = app;
-            mModel = model;
-            mBgDataModel = dataModel;
-            mAllAppsList = allAppsList;
-        }
-
-        @Override
-        public FutureTask<WorkspaceResult> getTask() {
-            return mTask;
-        }
-
-        @Override
-        public void run() {
-            mTask.run();
-        }
-
-        @Override
-        public WorkspaceResult call() throws Exception {
-            if (!mModel.isModelLoaded()) {
-                Log.d(TAG, "Workspace not loaded, loading now");
-                mModel.startLoaderForResults(
-                        new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
-                return null;
-            }
-
-            return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null);
-        }
-    }
-
-    private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
-            WorkspaceFetcher {
-
-        private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
-
-        WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
-            super(app, null, new BgDataModel(), new ModelDelegate(), null);
-        }
-
-        @Override
-        public FutureTask<WorkspaceResult> getTask() {
-            return mTask;
-        }
-
-        @Override
-        public void run() {
-            mTask.run();
-        }
-
-        @Override
-        public WorkspaceResult call() {
-            List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
-                    LauncherSettings.Favorites.SCREEN + " = 0 or "
-                            + LauncherSettings.Favorites.CONTAINER + " = "
-                            + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
-            return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap);
-        }
-    }
-
-    private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
-        FutureTask<WorkspaceResult> getTask();
-
-        default WorkspaceResult get() {
-            try {
-                return getTask().get(5, TimeUnit.SECONDS);
-            } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                Log.d(TAG, "Error fetching workspace items info", e);
-                return null;
-            }
-        }
-    }
-
-    private static class WorkspaceResult {
-        private final ArrayList<ItemInfo> mWorkspaceItems;
-        private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
-        private final FixedContainerItems mHotseatPredictions;
-        private final WidgetsModel mWidgetsModel;
-        private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
-
-        private WorkspaceResult(BgDataModel dataModel,
-                WidgetsModel widgetsModel,
-                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-            synchronized (dataModel) {
-                mWorkspaceItems = dataModel.workspaceItems;
-                mAppWidgets = dataModel.appWidgets;
-                mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
-                mWidgetsModel = widgetsModel;
-                mWidgetProvidersMap = widgetProviderInfoMap;
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 6193570..a8c3d15 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -21,32 +21,49 @@
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.app.WallpaperColors;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
+import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.SurfaceControlViewHost;
+import android.view.SurfaceControlViewHost.SurfacePackage;
 import android.view.View;
+import android.view.WindowManager.LayoutParams;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
+import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.GridSizeMigrationTaskV2;
+import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDelegate;
+import com.android.launcher3.model.ModelPreload;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.widget.LocalColorExtractor;
 
+import java.util.ArrayList;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /** Render preview using surface view. */
 @SuppressWarnings("NewApi")
-public class PreviewSurfaceRenderer implements IBinder.DeathRecipient {
+public class PreviewSurfaceRenderer {
+
+    private static final String TAG = "PreviewSurfaceRenderer";
 
     private static final int FADE_IN_ANIMATION_DURATION = 200;
 
@@ -54,8 +71,6 @@
     private static final String KEY_VIEW_WIDTH = "width";
     private static final String KEY_VIEW_HEIGHT = "height";
     private static final String KEY_DISPLAY_ID = "display_id";
-    private static final String KEY_SURFACE_PACKAGE = "surface_package";
-    private static final String KEY_CALLBACK = "callback";
     private static final String KEY_COLORS = "wallpaper_colors";
 
     private final Context mContext;
@@ -65,10 +80,13 @@
     private final int mHeight;
     private final Display mDisplay;
     private final WallpaperColors mWallpaperColors;
+    private final RunnableList mOnDestroyCallbacks = new RunnableList();
 
-    private SurfaceControlViewHost mSurfaceControlViewHost;
+    private final SurfaceControlViewHost mSurfaceControlViewHost;
 
-    PreviewSurfaceRenderer(Context context, Bundle bundle) {
+    private boolean mDestroyed = false;
+
+    public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception {
         mContext = context;
 
         String gridName = bundle.getString("name");
@@ -77,106 +95,101 @@
             gridName = InvariantDeviceProfile.getCurrentGridName(context);
         }
         mWallpaperColors = bundle.getParcelable(KEY_COLORS);
-
         mIdp = new InvariantDeviceProfile(context, gridName);
 
         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
         mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+        mDisplay = context.getSystemService(DisplayManager.class)
+                .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
 
-        final DisplayManager displayManager = (DisplayManager) context.getSystemService(
-                Context.DISPLAY_SERVICE);
-        mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID));
+        mSurfaceControlViewHost = MAIN_EXECUTOR
+                .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
+                .get(5, TimeUnit.SECONDS);
+        mOnDestroyCallbacks.add(mSurfaceControlViewHost::release);
     }
 
-    /** Handle a received surface view request. */
-    Bundle render() {
-        if (mSurfaceControlViewHost != null) {
-            binderDied();
-        }
+    public IBinder getHostToken() {
+        return mHostToken;
+    }
 
-        SurfaceControlViewHost.SurfacePackage surfacePackage;
-        try {
-            mSurfaceControlViewHost = MAIN_EXECUTOR
-                    .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken))
-                    .get(5, TimeUnit.SECONDS);
-            surfacePackage = mSurfaceControlViewHost.getSurfacePackage();
-            mHostToken.linkToDeath(this, 0);
-        } catch (Exception e) {
-            e.printStackTrace();
-            return null;
-        }
+    public SurfacePackage getSurfacePackage() {
+        return mSurfaceControlViewHost.getSurfacePackage();
+    }
 
-        MODEL_EXECUTOR.post(() -> {
-            final boolean success = doGridMigrationIfNecessary();
+    /**
+     * Destroys the preview and all associated data
+     */
+    @UiThread
+    public void destroy() {
+        mDestroyed = true;
+        mOnDestroyCallbacks.executeAllAndDestroy();
+    }
 
-            final Context inflationContext;
-            if (mWallpaperColors != null) {
-                // Workaround to create a themed context
-                Context context = mContext.createDisplayContext(mDisplay);
-                LocalColorExtractor.newInstance(mContext)
-                        .applyColorsOverride(context, mWallpaperColors);
+    /**
+     * Generates the preview in background
+     */
+    public void loadAsync() {
+        MODEL_EXECUTOR.execute(this::loadModelData);
+    }
 
-                inflationContext = new ContextThemeWrapper(context,
-                        Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
-            } else {
-                inflationContext = new ContextThemeWrapper(mContext,  R.style.AppTheme);
+    @WorkerThread
+    private void loadModelData() {
+        final boolean migrated = doGridMigrationIfNecessary();
+
+        final Context inflationContext;
+        if (mWallpaperColors != null) {
+            // Create a themed context, without affecting the main application context
+            Context context = mContext.createDisplayContext(mDisplay);
+            if (Utilities.ATLEAST_R) {
+                context = context.createWindowContext(
+                        LayoutParams.TYPE_APPLICATION_OVERLAY, null);
             }
-
-            MAIN_EXECUTOR.post(() -> {
-                // If mSurfaceControlViewHost is null due to any reason (e.g. binder died,
-                // happening when user leaves the preview screen before preview rendering finishes),
-                // we should return here.
-                SurfaceControlViewHost host = mSurfaceControlViewHost;
-                if (host == null) {
-                    return;
-                }
-
-                View view = new LauncherPreviewRenderer(inflationContext, mIdp, success)
-                        .getRenderedView();
-                // This aspect scales the view to fit in the surface and centers it
-                final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
-                        mHeight / (float) view.getMeasuredHeight());
-                view.setScaleX(scale);
-                view.setScaleY(scale);
-                view.setPivotX(0);
-                view.setPivotY(0);
-                view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
-                view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
-                view.setAlpha(0);
-                view.animate().alpha(1)
-                        .setInterpolator(new AccelerateDecelerateInterpolator())
-                        .setDuration(FADE_IN_ANIMATION_DURATION)
-                        .start();
-                host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
-            });
-        });
-
-        Bundle result = new Bundle();
-        result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage);
-
-        Handler handler = new Handler(Looper.getMainLooper(), message -> {
-            binderDied();
-            return true;
-        });
-        Messenger messenger = new Messenger(handler);
-        Message msg = Message.obtain();
-        msg.replyTo = messenger;
-        result.putParcelable(KEY_CALLBACK, msg);
-        return result;
-    }
-
-    @Override
-    public void binderDied() {
-        if (mSurfaceControlViewHost != null) {
-            MAIN_EXECUTOR.execute(() -> {
-                mSurfaceControlViewHost.release();
-                mSurfaceControlViewHost = null;
-            });
+            LocalColorExtractor.newInstance(mContext)
+                    .applyColorsOverride(context, mWallpaperColors);
+            inflationContext = new ContextThemeWrapper(context,
+                    Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
+        } else {
+            inflationContext = new ContextThemeWrapper(mContext,  R.style.AppTheme);
         }
-        mHostToken.unlinkToDeath(this, 0);
+
+        if (migrated) {
+            PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
+            new LoaderTask(
+                    LauncherAppState.getInstance(previewContext),
+                    null,
+                    new BgDataModel(),
+                    new ModelDelegate(), null) {
+
+                @Override
+                public void run() {
+                    loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
+                            LauncherSettings.Favorites.SCREEN + " = 0 or "
+                                    + LauncherSettings.Favorites.CONTAINER + " = "
+                                    + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+                    MAIN_EXECUTOR.execute(() -> {
+                        renderView(previewContext, mBgDataModel, mWidgetProvidersMap);
+                        mOnDestroyCallbacks.add(previewContext::onDestroy);
+                    });
+                }
+            }.run();
+        } else {
+            new ModelPreload() {
+
+                @Override
+                public void onComplete(boolean isSuccess) {
+                    if (isSuccess) {
+                        MAIN_EXECUTOR.execute(() ->
+                                renderView(inflationContext, getBgDataModel(), null));
+                    } else {
+                        Log.e(TAG, "Model loading failed");
+                    }
+                }
+            }.start(inflationContext);
+        }
     }
 
+    @WorkerThread
     private boolean doGridMigrationIfNecessary() {
         boolean needsToMigrate =
                 MULTI_DB_GRID_MIRATION_ALGO.get()
@@ -189,4 +202,29 @@
                 ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
                 : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
     }
+
+    @UiThread
+    private void renderView(Context inflationContext, BgDataModel dataModel,
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+        if (mDestroyed) {
+            return;
+        }
+        View view = new LauncherPreviewRenderer(inflationContext, mIdp)
+                .getRenderedView(dataModel, widgetProviderInfoMap);
+        // This aspect scales the view to fit in the surface and centers it
+        final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
+                mHeight / (float) view.getMeasuredHeight());
+        view.setScaleX(scale);
+        view.setScaleY(scale);
+        view.setPivotX(0);
+        view.setPivotY(0);
+        view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
+        view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+        view.setAlpha(0);
+        view.animate().alpha(1)
+                .setInterpolator(new AccelerateDecelerateInterpolator())
+                .setDuration(FADE_IN_ANIMATION_DURATION)
+                .start();
+        mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight());
+    }
 }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 297325a..8e0a388 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -131,6 +131,13 @@
     }
 
     /**
+     * Closes the cache DB. This will clear any in-memory cache.
+     */
+    public void close() {
+        mIconDb.close();
+    }
+
+    /**
      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
      *
      * @return a request ID that can be used to cancel the request.
diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java
index 713492b..756b7da 100644
--- a/src/com/android/launcher3/model/ModelPreload.java
+++ b/src/com/android/launcher3/model/ModelPreload.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.util.Log;
 
@@ -52,8 +54,14 @@
     public final void run() {
         mModel.startLoaderForResultsIfNotLoaded(
                 new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0]));
-        Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
-        onComplete(mModel.isModelLoaded());
+        MODEL_EXECUTOR.post(() -> {
+            Log.d(TAG, "Preload completed : " + mModel.isModelLoaded());
+            onComplete(mModel.isModelLoaded());
+        });
+    }
+
+    public BgDataModel getBgDataModel() {
+        return mBgDataModel;
     }
 
     /**
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index ce7dc07..b5dcd3a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -27,6 +27,7 @@
 
 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;
@@ -194,36 +195,43 @@
     }
 
     /**
+     * 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()) {
-            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;
-            }
+            handleDisabledItemClicked(shortcut, launcher);
+            return;
         }
 
         // Check for abandoned promise
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5deecd4..8685aae 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -319,11 +319,15 @@
         }
 
         mIsScrollable = checkScrollableRecursively(this);
-
         if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) {
+            mCurrentWidgetSize.left = left;
+            mCurrentWidgetSize.right = right;
+            mCurrentWidgetSize.top = top;
+            mCurrentWidgetSize.bottom = bottom;
+
             LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
-            getBoundsForViewInDragLayer(mLauncher.getDragLayer(), this, mCurrentWidgetSize, true,
-                    mTmpFloatArray, mTempRectF);
+            getBoundsForViewInDragLayer(mLauncher.getDragLayer(), (View) getParent(),
+                    mCurrentWidgetSize, true, mTmpFloatArray, mTempRectF);
             setRect(mTempRectF, mCurrentWidgetSize);
             updateColorExtraction(mCurrentWidgetSize,
                     mWorkspace.getPageIndexForScreenId(info.screenId));
diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
index 6781824..6643779 100644
--- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
+++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java
@@ -21,6 +21,7 @@
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
@@ -39,9 +40,11 @@
     private final View mSearchAndRecommendationViewParent;
     private final WidgetsRecyclerView mPrimaryRecyclerView;
     private final WidgetsRecyclerView mSearchRecyclerView;
+    private final TextView mNoWidgetsView;
     private final int mTabsHeight;
     private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
     private final Point mTempOffset = new Point();
+    private int mBottomInset;
 
     // The following are only non null if mHasWorkProfile is true.
     @Nullable private final WidgetsRecyclerView mWorkRecyclerView;
@@ -81,7 +84,8 @@
             @Nullable WidgetsRecyclerView workRecyclerView,
             WidgetsRecyclerView searchRecyclerView,
             @Nullable View personalWorkTabsView,
-            @Nullable PersonalWorkPagedView primaryWorkViewPager) {
+            @Nullable PersonalWorkPagedView primaryWorkViewPager,
+            TextView noWidgetsView) {
         mHasWorkProfile = hasWorkProfile;
         mViewHolder = viewHolder;
         mViewHolder.mContainer.setSearchAndRecommendationScrollController(this);
@@ -92,6 +96,7 @@
         mPrimaryWorkTabsView = personalWorkTabsView;
         mPrimaryWorkViewPager = primaryWorkViewPager;
         mTabsHeight = tabsHeight;
+        mNoWidgetsView = noWidgetsView;
         setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false);
     }
 
@@ -114,6 +119,15 @@
     }
 
     /**
+     * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever
+     * necessary.
+     */
+    public boolean updateBottomInset(int bottomInset) {
+        mBottomInset = bottomInset;
+        return updateMarginAndPadding();
+    }
+
+    /**
      * Updates the margin and padding of {@link WidgetsFullSheet} to accumulate collapsible views.
      *
      * @return {@code true} if margins or/and padding of views in the search and recommendations
@@ -129,6 +143,8 @@
                         + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
 
         int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer);
+        int noWidgetsViewHeight =  topContainerHeight - mBottomInset;
+
         if (mHasWorkProfile) {
             mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle)
                     + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable);
@@ -180,6 +196,10 @@
             int topOffsetAfterAllViewsCollapsed =
                     topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs;
 
+            if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) {
+                noWidgetsViewHeight += mTabsHeight;
+            }
+
             RelativeLayout.LayoutParams viewPagerLayoutParams =
                     (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams();
             if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) {
@@ -222,6 +242,14 @@
                     mSearchRecyclerView.getPaddingBottom());
             hasMarginOrPaddingUpdated = true;
         }
+        if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) {
+            mNoWidgetsView.setPadding(
+                    mNoWidgetsView.getPaddingLeft(),
+                    noWidgetsViewHeight,
+                    mNoWidgetsView.getPaddingRight(),
+                    mNoWidgetsView.getPaddingBottom());
+            hasMarginOrPaddingUpdated = true;
+        }
         return hasMarginOrPaddingUpdated;
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 801ecc2..039cad8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -61,7 +61,6 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
-import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -76,8 +75,7 @@
  */
 public class WidgetsFullSheet extends BaseWidgetSheet
         implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
-        WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
-        WidgetsSearchBarUIHelper {
+        WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
     private static final String TAG = WidgetsFullSheet.class.getSimpleName();
 
     private static final long DEFAULT_OPEN_DURATION = 267;
@@ -94,8 +92,8 @@
     private final boolean mHasWorkProfile;
     private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
     private final UserHandle mCurrentUser = Process.myUserHandle();
-    private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
-            mCurrentUser.equals(entry.mPkgItem.user);
+    private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
+            entry -> mCurrentUser.equals(entry.mPkgItem.user);
     private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
             mPrimaryWidgetsFilter.negate();
     private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
@@ -133,6 +131,7 @@
     @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
     @Nullable private PersonalWorkPagedView mViewPager;
     private boolean mIsInSearchMode;
+    private boolean mIsNoWidgetsViewNeeded;
     private int mMaxSpansPerRow = 4;
     private View mTabsView;
     private TextView mNoWidgetsView;
@@ -189,6 +188,7 @@
 
         layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout,
                 true);
+        mNoWidgetsView = findViewById(R.id.no_widgets_text);
         mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
                 findViewById(R.id.search_and_recommendations_container));
         mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
@@ -199,10 +199,10 @@
                 mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null,
                 findViewById(R.id.search_widgets_list_view),
                 mTabsView,
-                mViewPager);
+                mViewPager,
+                mNoWidgetsView);
         fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController);
 
-        mNoWidgetsView = findViewById(R.id.no_widgets_text);
 
         onRecommendedWidgetsBound();
         onWidgetsBound();
@@ -297,6 +297,7 @@
         if (mHasWorkProfile) {
             setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom);
         }
+        mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom);
         if (insets.bottom > 0) {
             setupNavBarColor();
         } else {
@@ -412,6 +413,16 @@
         } else {
             updateRecyclerViewVisibility(primaryUserAdapterHolder);
         }
+        // Update recommended widgets section so that it occupies appropriate space on screen to
+        // leave enough space for presence/absence of mNoWidgetsView.
+        boolean isNoWidgetsViewNeeded =
+                mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0
+                        || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK)
+                                .mWidgetsListAdapter.getItemCount() == 0);
+        if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
+            mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
+            onRecommendedWidgetsBound();
+        }
     }
 
     @Override
@@ -478,9 +489,19 @@
         WidgetsRecommendationTableLayout table =
                 mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable;
         if (recommendedWidgets.size() > 0) {
-            float maxTableHeight =
-                    (mActivityContext.getDeviceProfile().availableHeightPx - mTabsHeight
-                            - getHeaderViewHeight()) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
+            float noWidgetsViewHeight = 0;
+            if (mIsNoWidgetsViewNeeded) {
+                // Make sure recommended section leaves enough space for noWidgetsView.
+                Rect noWidgetsViewTextBounds = new Rect();
+                mNoWidgetsView.getPaint()
+                        .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0,
+                                mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
+                noWidgetsViewHeight = noWidgetsViewTextBounds.height();
+            }
+            float maxTableHeight = (mActivityContext.getDeviceProfile().availableHeightPx
+                                        - mTabsHeight - getHeaderViewHeight() - noWidgetsViewHeight)
+                                                * RECOMMENDATION_TABLE_HEIGHT_RATIO;
+
             List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
                     WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets,
                             mMaxSpansPerRow);
@@ -540,6 +561,12 @@
             } else if (getPopupContainer().isEventOverView(mContent, ev)) {
                 mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer());
             }
+
+            if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused()
+                    && !getPopupContainer().isEventOverView(
+                            mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) {
+                mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+            }
         }
         return super.onControllerInterceptTouchEvent(ev);
     }
@@ -614,11 +641,6 @@
         getWindowInsetsController().hide(WindowInsets.Type.ime());
     }
 
-    @Override
-    public void clearSearchBarFocus() {
-        mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
-    }
-
     private void showEducationTipOnView(View view) {
         mActivityContext.getSharedPrefs().edit()
                 .putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply();
@@ -686,9 +708,7 @@
                     apps.getWidgetCache(),
                     apps.getIconCache(),
                     /* iconClickListener= */ WidgetsFullSheet.this,
-                    /* iconLongClickListener= */ WidgetsFullSheet.this,
-                    /* WidgetsSearchBarUIHelper= */
-                    mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
+                    /* iconLongClickListener= */ WidgetsFullSheet.this);
             mWidgetsListAdapter.setHasStableIds(true);
             switch (mAdapterType) {
                 case PRIMARY:
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 8345a0e..7963431 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
-import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -72,7 +71,6 @@
     private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
     private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
 
-    @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper;
     private final WidgetsDiffReporter mDiffReporter;
     private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
     private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
@@ -92,9 +90,7 @@
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
             WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
-            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
-            @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) {
-        mSearchBarUIHelper = searchBarUIHelper;
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
         mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
                 layoutInflater, iconClickListener, iconLongClickListener,
@@ -248,9 +244,6 @@
 
     @Override
     public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
-        if (mSearchBarUIHelper != null) {
-            mSearchBarUIHelper.clearSearchBarFocus();
-        }
         if (showWidgets) {
             mWidgetsContentVisiblePackageUserKey = packageUserKey;
             updateVisibleEntries();
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
index 42f1bb2..65937b6 100644
--- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -76,6 +76,11 @@
     }
 
     @Override
+    public boolean isSearchBarFocused() {
+        return mEditText.isFocused();
+    }
+
+    @Override
     public void clearSearchBarFocus() {
         mController.clearFocus();
     }
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
index 0ac47ce..44a5e80 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -32,6 +32,9 @@
      */
     void reset();
 
+    /** Returns {@code true} if the search bar is in focus. */
+    boolean isSearchBarFocused();
+
     /**
      * Clears focus from search bar.
      */
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
deleted file mode 100644
index edfdc65..0000000
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
+++ /dev/null
@@ -1,27 +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.widget.picker.search;
-
-/**
- * UI helper for {@link WidgetsSearchBar}.
- */
-public interface WidgetsSearchBarUIHelper {
-    /**
-     * Clears focus from the search bar.
-     */
-    void clearSearchBarFocus();
-}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 57f40db..4cf52f0 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -496,13 +496,14 @@
     private void fail(String message) {
         checkForAnomaly();
         Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
-                "http://go/tapl test failure:\nOverview: " + getContextDescription()
+                "http://go/tapl test failure:\nSummary: " + getContextDescription()
                         + " - visible state is " + getVisibleStateMessage()
                         + ";\nDetails: " + message, true)));
     }
 
     private String getContextDescription() {
-        return mDiagnosticContext.isEmpty() ? "" : String.join(", ", mDiagnosticContext);
+        return mDiagnosticContext.isEmpty()
+                ? "(no context)" : String.join(", ", mDiagnosticContext);
     }
 
     void assertTrue(String message, boolean condition) {