Replace taskbar hotseat with real hotseat when folder is open

- Seamlessly show real hotseat and hide taskbar hotseat, while
  keeping rest of taskbar visible
- Update MultiValueAlpha to allow for taking max alpha instead
  of blending, and use that for Hotseat
- Fix folder open bounds on home screen when taskbar is present

Test: Open folder from taskbar on home, can drag out items
Bug: 182079330
Bug: 171917176
Change-Id: I7c1983e3219b1341cf233260f0ccac9051c4dc14
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index b124b33..84e2304 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -25,7 +25,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:background="@color/taskbar_background"
-        android:gravity="center"
-        android:animateLayoutChanges="true"/>
+        android:gravity="center"/>
 
 </com.android.launcher3.taskbar.TaskbarContainerView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 641e108..ff47eae 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -315,6 +315,10 @@
     @Override
     public void onDragLayerHierarchyChanged() {
         onLauncherStateOrFocusChanged();
+
+        if (mTaskbarController != null) {
+            mTaskbarController.onLauncherDragLayerHierarchyChanged();
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 544835c..d96cf7c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,6 +19,8 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
@@ -40,6 +42,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepAppTransitionManagerImpl;
 import com.android.launcher3.R;
@@ -144,22 +147,13 @@
                         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;
-                            });
-                        });
+                        if (mLauncher.hasBeenResumed()) {
+                            FolderInfo folderInfo = (FolderInfo) tag;
+                            onClickedOnFolderFromHome(folderInfo);
+                        } else {
+                            FolderIcon folderIcon = (FolderIcon) view;
+                            onClickedOnFolderInApp(folderIcon);
+                        }
                     } else {
                         ItemClickHandler.INSTANCE.onClick(view);
                     }
@@ -169,6 +163,34 @@
                 };
             }
 
+            // Open the real folder in Launcher.
+            private void onClickedOnFolderFromHome(FolderInfo folderInfo) {
+                alignRealHotseatWithTaskbar();
+
+                FolderIcon folderIcon = (FolderIcon) mLauncher.getHotseat()
+                        .getFirstItemMatch((info, v) -> info == folderInfo);
+                folderIcon.post(folderIcon::performClick);
+            }
+
+            // Open the Taskbar folder, and handle clicks on folder items.
+            private void onClickedOnFolderInApp(FolderIcon folderIcon) {
+                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;
+                    });
+                });
+            }
+
             @Override
             public View.OnLongClickListener getItemOnLongClickListener() {
                 return view -> {
@@ -306,6 +328,7 @@
             mAnimator = createAnimToLauncher(null, duration);
         } else {
             mAnimator = createAnimToApp(duration);
+            replaceTaskbarWithHotseatOrViceVersa();
         }
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -355,6 +378,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mTaskbarView.updateHotseatItemsVisibility();
+                setReplaceTaskbarWithHotseat(false);
             }
         });
         return anim.buildAnim();
@@ -451,6 +475,35 @@
                 mTaskbarView.getHeight() - hotseatBounds.bottom);
     }
 
+    /**
+     * A view was added or removed from DragLayer, check if we need to hide our hotseat copy and
+     * show the real one instead.
+     */
+    public void onLauncherDragLayerHierarchyChanged() {
+        replaceTaskbarWithHotseatOrViceVersa();
+    }
+
+    private void replaceTaskbarWithHotseatOrViceVersa() {
+        boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
+                TYPE_ALL & TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
+        if (!mLauncher.hasBeenResumed()) {
+            replaceTaskbarWithHotseat = false;
+        }
+        setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
+    }
+
+    private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
+        Hotseat hotseat = mLauncher.getHotseat();
+        if (replaceTaskbarWithHotseat) {
+            alignRealHotseatWithTaskbar();
+            hotseat.getReplaceTaskbarAlpha().setValue(1f);
+            mTaskbarView.setHotseatViewsHidden(true);
+        } else {
+            hotseat.getReplaceTaskbarAlpha().setValue(0f);
+            mTaskbarView.setHotseatViewsHidden(false);
+        }
+    }
+
     private float getTaskbarScaleOnHome() {
         return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a729e77..1d762e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -63,6 +64,7 @@
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
 
     // Initialized in init().
+    private LayoutTransition mLayoutTransition;
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
     private View mHotseatRecentsDivider;
@@ -76,6 +78,7 @@
     private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
+    private boolean mIsHotseatHidden;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -107,6 +110,9 @@
     }
 
     protected void init(int numHotseatIcons, int numRecentIcons) {
+        mLayoutTransition = new LayoutTransition();
+        setLayoutTransitionsEnabled(true);
+
         mHotseatStartIndex = 0;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
@@ -119,6 +125,10 @@
         updateRecentTasks(new Task[numRecentIcons]);
     }
 
+    private void setLayoutTransitionsEnabled(boolean enabled) {
+        setLayoutTransition(enabled ? mLayoutTransition : null);
+    }
+
     protected void cleanup() {
         removeAllViews();
     }
@@ -206,9 +216,19 @@
         }
     }
 
+    /**
+     * Hides or shows the hotseat items immediately (without layout transitions).
+     */
+    protected void setHotseatViewsHidden(boolean hidden) {
+        mIsHotseatHidden = hidden;
+        setLayoutTransitionsEnabled(false);
+        updateHotseatItemsVisibility();
+        setLayoutTransitionsEnabled(true);
+    }
+
     private void updateHotseatItemVisibility(View hotseatView) {
         if (hotseatView.getTag() != null) {
-            hotseatView.setVisibility(VISIBLE);
+            hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE);
         } else {
             int oldVisibility = hotseatView.getVisibility();
             int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 95cdbdd..e263c7a 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -98,6 +98,9 @@
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
 
+    // When these types of floating views are open, hide the taskbar hotseat and show the real one.
+    public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP;
+
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU;
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 2440854..fb7a99f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -708,10 +708,11 @@
                     mInsets.top + availableHeightPx);
         } else {
             // Folders should only appear below the drop target bar and above the hotseat
+            int hotseatTop = isTaskbarPresent ? taskbarSize : hotseatBarSizePx;
             return new Rect(mInsets.left + edgeMarginPx,
                     mInsets.top + dropTargetBarSizePx + edgeMarginPx,
                     mInsets.left + availableWidthPx - edgeMarginPx,
-                    mInsets.top + availableHeightPx - hotseatBarSizePx
+                    mInsets.top + availableHeightPx - hotseatTop
                             - workspacePageIndicatorHeight - edgeMarginPx);
         }
     }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b2112ad..e5b75c1 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.MultiValueAlpha;
 
 import java.util.function.Consumer;
 
@@ -37,6 +38,10 @@
  */
 public class Hotseat extends CellLayout implements Insettable {
 
+    private static final int ALPHA_INDEX_STATE = 0;
+    private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1;
+    private static final int NUM_ALPHA_CHANNELS = 2;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
     private Workspace mWorkspace;
@@ -44,6 +49,8 @@
     @Nullable
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
+    private final MultiValueAlpha mMultiValueAlpha;
+
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -54,6 +61,8 @@
 
     public Hotseat(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX);
+        mMultiValueAlpha.setUpdateVisibility(true);
     }
 
     /**
@@ -174,4 +183,12 @@
     public View getFirstItemMatch(Workspace.ItemOperator itemOperator) {
         return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
     }
+
+    public MultiValueAlpha.AlphaProperty getStateAlpha() {
+        return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE);
+    }
+
+    public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() {
+        return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR);
+    }
 }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 660eeab..d6d2f73 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.plugins.ResourceProvider;
 
 /**
@@ -143,8 +144,8 @@
             }
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha,
-                    config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
+            propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE,
+                    hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
             float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     workspacePageIndicatorAlpha, fadeInterpolator);
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 5be9529..c79b1f6 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -42,16 +42,49 @@
                 }
             };
 
+    /**
+     * Determines how each alpha should factor into the final alpha.
+     */
+    public enum Mode {
+        BLEND(1f) {
+            @Override
+            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
+                return currentAlpha * otherAlpha;
+            }
+        },
+
+        MAX(0f) {
+            @Override
+            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
+                return Math.max(currentAlpha, otherAlpha);
+            }
+        };
+
+        Mode(float startAlpha) {
+            mStartAlpha = startAlpha;
+        }
+
+        protected final float mStartAlpha;
+        protected abstract float calculateNewAlpha(float currentAlpha, float otherAlpha);
+    }
+
     private final View mView;
     private final AlphaProperty[] mMyProperties;
+    private final Mode mMode;
 
     private int mValidMask;
     // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
     private boolean mUpdateVisibility;
 
     public MultiValueAlpha(View view, int size) {
+        this(view, size, Mode.BLEND);
+    }
+
+    public MultiValueAlpha(View view, int size, Mode mode) {
         mView = view;
         mMyProperties = new AlphaProperty[size];
+        mMode = mode;
+        mView.setAlpha(mMode.mStartAlpha);
 
         mValidMask = 0;
         for (int i = 0; i < size; i++) {
@@ -79,9 +112,9 @@
 
         private final int mMyMask;
 
-        private float mValue = 1;
+        private float mValue = mMode.mStartAlpha;
         // Factor of all other alpha channels, only valid if mMyMask is present in mValidMask.
-        private float mOthers = 1;
+        private float mOthers = mMode.mStartAlpha;
 
         AlphaProperty(int myMask) {
             mMyMask = myMask;
@@ -94,10 +127,10 @@
 
             if ((mValidMask & mMyMask) == 0) {
                 // Our cache value is not correct, recompute it.
-                mOthers = 1;
+                mOthers = mMode.mStartAlpha;
                 for (AlphaProperty prop : mMyProperties) {
                     if (prop != this) {
-                        mOthers *= prop.mValue;
+                        mOthers = mMode.calculateNewAlpha(mOthers, prop.mValue);
                     }
                 }
             }
@@ -107,7 +140,7 @@
             mValidMask = mMyMask;
             mValue = value;
 
-            mView.setAlpha(mOthers * mValue);
+            mView.setAlpha(mMode.calculateNewAlpha(mOthers, mValue));
             if (mUpdateVisibility) {
                 AlphaUpdateListener.updateVisibility(mView);
             }