From a5b6c155b68738f5db8aebb59753e7452c186df9 Mon Sep 17 00:00:00 2001 From: Toni Barzic Date: Fri, 11 Oct 2024 21:59:05 +0000 Subject: [PATCH] Update taskbar overflow button Creates a new view for the taskbar overflow button that draws up to 4 recent item previews. The items are stacked on top of each other in counter clockwise order, with overlapping bounds, recent items closer to the top. The item icons have a 2 dip ring around them, of the taskbar background color. Adjusts logic to calculate which items become part of the overflow button, so more recent items get shown in the taskbar. Initial consideration was to usse FolderIcon to represent the overflow button, but decided against it because: * FolderIcon is fairly entangled with the associated folder view * item information uses different data structure (ItemInfo) than recent items (GroupTasks) * item preview layout within the main icon is similar, but sufficiently different that using clipped folder layout rules felt like hacking around assumptions made for folder icon UI Bug: 368119679 Test: Keep opening apps until the task bar enters overflow - verify that overview button shows up, and contains least recent task representations. Keep adding items, then closing windows, and verify the icon gets updated accordingly. Done in landscape and portrait, and ltr and rtl layout. Flag: com.android.launcher3.taskbar_overflow Change-Id: I2824cb0db1f7516ebd74361ce00fb8887857325d --- ...w_button.xml => taskbar_overflow_view.xml} | 3 +- quickstep/res/values/dimens.xml | 1 + .../taskbar/TaskbarOverflowView.java | 212 ++++++++++++++++++ .../launcher3/taskbar/TaskbarView.java | 48 ++-- .../taskbar/TaskbarViewController.java | 2 + 5 files changed, 251 insertions(+), 15 deletions(-) rename quickstep/res/layout/{taskbar_overflow_button.xml => taskbar_overflow_view.xml} (87%) create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java diff --git a/quickstep/res/layout/taskbar_overflow_button.xml b/quickstep/res/layout/taskbar_overflow_view.xml similarity index 87% rename from quickstep/res/layout/taskbar_overflow_button.xml rename to quickstep/res/layout/taskbar_overflow_view.xml index 20104f257c..7444e59281 100644 --- a/quickstep/res/layout/taskbar_overflow_button.xml +++ b/quickstep/res/layout/taskbar_overflow_view.xml @@ -15,8 +15,7 @@ --> -12dp 4dp 6dp + 2dp 12dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java new file mode 100644 index 0000000000..fad5ca3d5d --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2024 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.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.launcher3.R; +import com.android.launcher3.Reorderable; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiTranslateDelegate; +import com.android.systemui.shared.recents.model.Task; + +import java.util.ArrayList; +import java.util.List; + +/** + * View used as overflow icon within task bar, when the list of recent/running apps overflows the + * available display bounds - if display is not wide enough to show all running apps in the taskbar, + * this icon is added to the taskbar as an entry point to open UI that surfaces all running apps. + * The icon contains icon representations of up to 4 more recent tasks in overflow, stacked on top + * each other in counter clockwise manner (icons of tasks partially overlapping with each other). + */ +public class TaskbarOverflowView extends FrameLayout implements Reorderable { + private final List mItems = new ArrayList(); + private int mIconSize; + private int mPadding; + private Paint mItemBackgroundPaint; + private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); + private float mScaleForReorderBounce = 1f; + + public TaskbarOverflowView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TaskbarOverflowView(Context context) { + super(context); + init(); + } + + /** + * Inflates the taskbar overflow button view. + * @param resId The resource to inflate the view from. + * @param group The parent view. + * @param iconSize The size of the overflow button icon. + * @param padding The internal padding of the overflow view. + * @return A taskbar overflow button. + */ + public static TaskbarOverflowView inflateIcon(int resId, ViewGroup group, int iconSize, + int padding) { + LayoutInflater inflater = LayoutInflater.from(group.getContext()); + TaskbarOverflowView icon = (TaskbarOverflowView) inflater.inflate(resId, group, false); + + icon.mIconSize = iconSize; + icon.mPadding = padding; + return icon; + } + + private void init() { + mItemBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mItemBackgroundPaint.setColor(getContext().getColor(R.color.taskbar_background)); + + setWillNotDraw(false); + } + + @Override + protected void onDraw(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); + + int itemsToShow = Math.min(mItems.size(), 4); + for (int i = itemsToShow - 1; i >= 0; --i) { + Drawable icon = mItems.get(mItems.size() - i - 1).icon; + if (icon == null) { + continue; + } + + // 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 itemCenterX = getItemXOffset(itemCenterOffset, isRtlLayout, i, itemsToShow); + float itemCenterY = getItemYOffset(itemCenterOffset, i, itemsToShow); + + Drawable iconCopy = icon.getConstantState().newDrawable().mutate(); + iconCopy.setBounds(0, 0, (int) itemIconSize, (int) itemIconSize); + + canvas.save(); + float itemIconRadius = itemIconSize / 2; + canvas.translate( + mPadding + itemCenterX + radius - itemIconRadius, + mPadding + itemCenterY + radius - itemIconRadius); + canvas.drawCircle(itemIconRadius, itemIconRadius, + itemIconRadius + itemPreviewStrokeWidth, mItemBackgroundPaint); + iconCopy.draw(canvas); + canvas.restore(); + } + } + + /** + * Clears the list of tasks tracked by the view. + */ + public void clearItems() { + mItems.clear(); + invalidate(); + } + + /** + * Update the view to represent a new list of recent tasks. + * @param items Items to be shown in the view. + */ + public void setItems(List items) { + mItems.clear(); + mItems.addAll(items); + invalidate(); + } + + /** + * Called when a task is updated. If the task is contained within the view, it's cached value + * gets updated. If the task is shown within the icon, invalidates the view, so the task icon + * gets updated. + * @param task The updated task. + */ + public void updateTaskIsShown(Task task) { + 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) { + invalidate(); + } + break; + } + } + } + + @Override + public MultiTranslateDelegate getTranslateDelegate() { + return mTranslateDelegate; + } + + @Override + public float getReorderBounceScale() { + return mScaleForReorderBounce; + } + + @Override + public void setReorderBounceScale(float scale) { + mScaleForReorderBounce = scale; + super.setScaleX(scale); + super.setScaleY(scale); + } + + private float getItemXOffset(float baseOffset, boolean isRtl, int itemIndex, int itemCount) { + // Item with index 1 is on the left in all cases. + if (itemIndex == 1) { + return (isRtl ? 1 : -1) * baseOffset; + } + + // First item is centered if total number of items shown is 3, on the right otherwise. + if (itemIndex == 0) { + if (itemCount == 3) { + return 0; + } + return (isRtl ? -1 : 1) * baseOffset; + } + + // Last item is on the right when there are more than 2 items (case which is already handled + // as `itemIndex == 1`). + if (itemIndex == itemCount - 1) { + return (isRtl ? -1 : 1) * baseOffset; + } + + return (isRtl ? 1 : -1) * baseOffset; + } + + private float getItemYOffset(float baseOffset, int itemIndex, int itemCount) { + // If icon contains two items, they are both centered vertically. + if (itemCount == 2) { + return 0; + } + // First half of items is on top, later half is on bottom. + return (itemIndex + 1 <= itemCount / 2 ? -1 : 1) * baseOffset; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index fcb583ab4c..4449d92518 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -64,12 +64,12 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.LauncherBindableItemsContainer; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.IconButtonView; import com.android.quickstep.util.GroupTask; import com.android.quickstep.views.TaskViewType; import com.android.systemui.shared.recents.model.Task; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; @@ -106,7 +106,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar @Nullable private TaskbarDividerContainer mTaskbarDividerContainer; // Only non-null when device supports having a Taskbar Overflow button. - @Nullable private IconButtonView mTaskbarOverflowView; + @Nullable private TaskbarOverflowView mTaskbarOverflowView; /** * Whether the divider is between Hotseat icons and Recents, @@ -179,12 +179,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar } if (Flags.taskbarOverflow()) { - mTaskbarOverflowView = (IconButtonView) LayoutInflater.from(context) - .inflate(R.layout.taskbar_overflow_button, this, false); - mTaskbarOverflowView.setIconDrawable( - resources.getDrawable(R.drawable.taskbar_overflow_icon)); - mTaskbarOverflowView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding); + mTaskbarOverflowView = TaskbarOverflowView.inflateIcon( + R.layout.taskbar_overflow_view, this, + mIconTouchSize, mItemPadding); } + // TODO: Disable touch events on QSB otherwise it can crash. mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false); @@ -449,24 +448,47 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar int nonTaskIconsToBeAdded = 1; boolean supportsOverflow = Flags.taskbarOverflow(); + int overflowSize = 0; if (supportsOverflow) { int numberOfSupportedRecents = 0; for (GroupTask task : recentTasks) { // TODO(b/343289567 and b/316004172): support app pairs and desktop mode. - if (!task.hasMultipleTasks()) { + if (!task.supportsMultipleTasks()) { ++numberOfSupportedRecents; } } - if (nextViewIndex + numberOfSupportedRecents + nonTaskIconsToBeAdded > mMaxNumIcons - && mTaskbarOverflowView != null) { + + overflowSize = + nextViewIndex + numberOfSupportedRecents + nonTaskIconsToBeAdded - mMaxNumIcons; + if (overflowSize > 0 && mTaskbarOverflowView != null) { addView(mTaskbarOverflowView, nextViewIndex++); + } else if (mTaskbarOverflowView != null) { + mTaskbarOverflowView.clearItems(); } } + List overflownTasks = null; + // An extra item needs to be added to overflow button to account for the space taken up by + // the overflow button. + final int itemsToAddToOverflow = overflowSize > 0 ? overflowSize + 1 : 0; + if (overflowSize > 0) { + overflownTasks = new ArrayList(itemsToAddToOverflow); + } + // Add Recent/Running icons. for (GroupTask task : recentTasks) { - if (supportsOverflow && nextViewIndex + nonTaskIconsToBeAdded >= mMaxNumIcons) { - break; + if (mTaskbarOverflowView != null && overflownTasks != null + && overflownTasks.size() < itemsToAddToOverflow) { + // TODO(b/343289567 and b/316004172): support app pairs and desktop mode. + if (task.supportsMultipleTasks()) { + continue; + } + + overflownTasks.add(task.task1); + if (overflownTasks.size() == itemsToAddToOverflow) { + mTaskbarOverflowView.setItems(overflownTasks); + } + continue; } // Replace any Recent views with the appropriate type if it's not already that type. @@ -789,7 +811,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar * Returns the taskbar overflow view in the taskbar. */ @Nullable - public IconButtonView getTaskbarOverflowView() { + public TaskbarOverflowView getTaskbarOverflowView() { return mTaskbarOverflowView; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 253d0259db..5cc211d85b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -1090,6 +1090,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar if (groupTask.containsTask(task.key.id)) { mTaskbarView.applyGroupTaskToBubbleTextView(btv, groupTask); } + } else if (view instanceof TaskbarOverflowView overflowButton) { + overflowButton.updateTaskIsShown(task); } } }