Draw leave-behind circle after pressing taskbar overflow button

This CL does not include animating app icons / leave behind circle.

Bug: 376281549
Flag: com.android.launcher3.taskbar_overflow
Test: press the oveflow button, dismiss KQS in different ways
Change-Id: I1f830c6859a5be4fb5947ec38e6d30da79138b7f
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 711a49a..23a5a27 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -54,6 +54,8 @@
     public static final int MAX_TASKS = 6;
 
     @NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
+    // Callback used to notify when the KQS view is closed.
+    @Nullable private Runnable mOnClosed;
 
     // Initialized on init
     @Nullable private RecentsModel mModel;
@@ -112,8 +114,12 @@
      * Opens or closes the view in response to taskbar action. The view shows a filtered list of
      * tasks.
      * @param taskIdsToExclude A list of tasks to exclude in the opened view.
+     * @param onClosed A callback used to notify when the KQS view is closed.
      */
-    void toggleQuickSwitchViewForTaskbar(@NonNull Set<Integer> taskIdsToExclude) {
+    void toggleQuickSwitchViewForTaskbar(@NonNull Set<Integer> taskIdsToExclude,
+            @NonNull Runnable onClosed) {
+        mOnClosed = onClosed;
+
         // Close the view if its shown, and was opened from the taskbar.
         if (mQuickSwitchViewController != null
                 && !mQuickSwitchViewController.isCloseAnimationRunning()
@@ -264,6 +270,10 @@
             return;
         }
         mQuickSwitchViewController.closeQuickSwitchView(animate);
+        if (mOnClosed != null) {
+            mOnClosed.run();
+            mOnClosed = null;
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index fad5ca3..126e9bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -25,10 +25,13 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.R;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MultiTranslateDelegate;
+import com.android.launcher3.util.Themes;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.ArrayList;
@@ -42,12 +45,22 @@
  * each other in counter clockwise manner (icons of tasks partially overlapping with each other).
  */
 public class TaskbarOverflowView extends FrameLayout implements Reorderable {
+    private static final int MAX_ITEMS_IN_PREVIEW = 4;
+
+    private boolean mIsRtlLayout;
     private final List<Task> mItems = new ArrayList<Task>();
     private int mIconSize;
     private int mPadding;
     private Paint mItemBackgroundPaint;
     private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
     private float mScaleForReorderBounce = 1f;
+    private int mItemBackgroundColor;
+    private int mLeaveBehindColor;
+    private float mItemPreviewStrokeWidth;
+
+    // Active means the overflow icon has been pressed, which replaces the app icons with the
+    // leave-behind circle and shows the KQS UI.
+    private boolean mIsActive = false;
 
     public TaskbarOverflowView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -78,22 +91,32 @@
     }
 
     private void init() {
+        mIsRtlLayout = Utilities.isRtl(getResources());
         mItemBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mItemBackgroundPaint.setColor(getContext().getColor(R.color.taskbar_background));
+        mItemBackgroundColor = getContext().getColor(R.color.taskbar_background);
+        mLeaveBehindColor = Themes.getAttrColor(getContext(), android.R.attr.textColorTertiary);
+        mItemPreviewStrokeWidth = getResources().getDimension(
+                R.dimen.taskbar_overflow_button_preview_stroke);
 
         setWillNotDraw(false);
     }
 
     @Override
-    protected void onDraw(Canvas canvas) {
+    protected void onDraw(@NonNull Canvas canvas) {
         super.onDraw(canvas);
 
-        boolean isRtlLayout = Utilities.isRtl(getResources());
-        float radius = mIconSize / 2.0f - mPadding;
-        float itemPreviewStrokeWidth =
-                getResources().getDimension(R.dimen.taskbar_overflow_button_preview_stroke);
+        if (mIsActive) {
+            drawLeaveBehindCircle(canvas);
+        } else {
+            drawAppIcons(canvas);
+        }
+    }
 
-        int itemsToShow = Math.min(mItems.size(), 4);
+    private void drawAppIcons(@NonNull Canvas canvas) {
+        mItemBackgroundPaint.setColor(mItemBackgroundColor);
+        float radius = mIconSize / 2f - mPadding;
+
+        int itemsToShow = Math.min(mItems.size(), MAX_ITEMS_IN_PREVIEW);
         for (int i = itemsToShow - 1; i >= 0; --i) {
             Drawable icon = mItems.get(mItems.size() - i - 1).icon;
             if (icon == null) {
@@ -103,11 +126,11 @@
             // Set the item icon size so two items fit within the overflow icon with stroke width
             // included, and overlap of 4 stroke width sizes between base item preview items.
             // 2 * strokeWidth + 2 * itemIconSize - 4 * strokeWidth = iconSize = 2 * radius.
-            float itemIconSize = radius + itemPreviewStrokeWidth;
-            // Offset item icon from center so item icon stroke edge mateches the parent icon edge.
-            float itemCenterOffset = radius - itemIconSize / 2 - itemPreviewStrokeWidth;
+            float itemIconSize = radius + mItemPreviewStrokeWidth;
+            // Offset item icon from center so item icon stroke edge matches the parent icon edge.
+            float itemCenterOffset = radius - itemIconSize / 2 - mItemPreviewStrokeWidth;
 
-            float itemCenterX = getItemXOffset(itemCenterOffset, isRtlLayout, i, itemsToShow);
+            float itemCenterX = getItemXOffset(itemCenterOffset, mIsRtlLayout, i, itemsToShow);
             float itemCenterY = getItemYOffset(itemCenterOffset, i, itemsToShow);
 
             Drawable iconCopy = icon.getConstantState().newDrawable().mutate();
@@ -119,12 +142,19 @@
                     mPadding + itemCenterX + radius - itemIconRadius,
                     mPadding + itemCenterY + radius - itemIconRadius);
             canvas.drawCircle(itemIconRadius, itemIconRadius,
-                    itemIconRadius + itemPreviewStrokeWidth, mItemBackgroundPaint);
+                    itemIconRadius + mItemPreviewStrokeWidth, mItemBackgroundPaint);
             iconCopy.draw(canvas);
             canvas.restore();
         }
     }
 
+    private void drawLeaveBehindCircle(@NonNull Canvas canvas) {
+        mItemBackgroundPaint.setColor(mLeaveBehindColor);
+
+        final var xyCenter = mIconSize / 2f;
+        canvas.drawCircle(xyCenter, xyCenter, mIconSize / 4f, mItemBackgroundPaint);
+    }
+
     /**
      * Clears the list of tasks tracked by the view.
      */
@@ -153,7 +183,7 @@
         for (int i = 0; i < mItems.size(); ++i) {
             if (mItems.get(i).key.id == task.key.id) {
                 mItems.set(i, task);
-                if (i >= mItems.size() - 4) {
+                if (i >= mItems.size() - MAX_ITEMS_IN_PREVIEW) {
                     invalidate();
                 }
                 break;
@@ -161,6 +191,24 @@
         }
     }
 
+    /**
+     * Returns the view's state (whether it shows a set of app icons or a leave-behind circle).
+     */
+    public boolean getIsActive() {
+        return mIsActive;
+    }
+
+    /**
+     * Updates the view's state to draw either a set of app icons or a leave-behind circle.
+     * @param isActive The next state of the view.
+     */
+    public void setIsActive(boolean isActive) {
+        if (mIsActive != isActive) {
+            mIsActive = isActive;
+            invalidate();
+        }
+    }
+
     @Override
     public MultiTranslateDelegate getTranslateDelegate() {
         return mTranslateDelegate;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 5d769d2..834f92e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -148,8 +148,7 @@
         return new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mControllers.keyboardQuickSwitchController.toggleQuickSwitchViewForTaskbar(
-                        mControllers.taskbarViewController.getTaskIdsForPinnedApps());
+                toggleKeyboardQuickSwitchView();
             }
         };
     }
@@ -159,13 +158,28 @@
         return new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                mControllers.keyboardQuickSwitchController.toggleQuickSwitchViewForTaskbar(
-                        mControllers.taskbarViewController.getTaskIdsForPinnedApps());
+                toggleKeyboardQuickSwitchView();
                 return true;
             }
         };
     }
 
+    private void toggleKeyboardQuickSwitchView() {
+        if (mTaskbarView.getTaskbarOverflowView() != null) {
+            mTaskbarView.getTaskbarOverflowView().setIsActive(
+                    !mTaskbarView.getTaskbarOverflowView().getIsActive());
+        }
+        mControllers.keyboardQuickSwitchController.toggleQuickSwitchViewForTaskbar(
+                mControllers.taskbarViewController.getTaskIdsForPinnedApps(),
+                this::onKeyboardQuickSwitchViewClosed);
+    }
+
+    private void onKeyboardQuickSwitchViewClosed() {
+        if (mTaskbarView.getTaskbarOverflowView() != null) {
+            mTaskbarView.getTaskbarOverflowView().setIsActive(false);
+        }
+    }
+
     private float getDividerCenterX() {
         View divider = mTaskbarView.getTaskbarDividerViewContainer();
         if (divider == null) {