diff --git a/quickstep/res/drawable/bg_overview_add_desktop_button.xml b/quickstep/res/drawable/bg_overview_add_desktop_button.xml new file mode 100644 index 0000000000..12581bf8cc --- /dev/null +++ b/quickstep/res/drawable/bg_overview_add_desktop_button.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml index 7f58cf8c4f..2f286890a8 100644 --- a/quickstep/res/drawable/bg_overview_clear_all_button.xml +++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml @@ -15,8 +15,7 @@ limitations under the License. --> + xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/quickstep/res/layout/overview_add_desktop_button.xml b/quickstep/res/layout/overview_add_desktop_button.xml index e36cf72414..a1c64f32ee 100644 --- a/quickstep/res/layout/overview_add_desktop_button.xml +++ b/quickstep/res/layout/overview_add_desktop_button.xml @@ -16,9 +16,11 @@ --> \ No newline at end of file + android:background="@drawable/bg_overview_add_desktop_button" + launcher:focusBorderColor="@color/materialColorOutline" + android:padding="10dp" /> diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml index 18a6240b43..034c3c2ec1 100644 --- a/quickstep/res/layout/overview_clear_all_button.xml +++ b/quickstep/res/layout/overview_clear_all_button.xml @@ -16,7 +16,6 @@ --> \ No newline at end of file + android:textSize="14sp" /> diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml index 7fd6b5c037..28c0d5c9e8 100644 --- a/quickstep/res/values/attrs.xml +++ b/quickstep/res/values/attrs.xml @@ -36,6 +36,11 @@ + + + + + 56dp + 2dp diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java index 498078b423..ceffbe4a81 100644 --- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java +++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java @@ -29,6 +29,7 @@ import java.util.List; */ public class TaskGridNavHelper { public static final int CLEAR_ALL_PLACEHOLDER_ID = -1; + public static final int ADD_DESK_PLACEHOLDER_ID = -2; public static final int DIRECTION_UP = 0; public static final int DIRECTION_DOWN = 1; @@ -41,44 +42,42 @@ public class TaskGridNavHelper { public @interface TASK_NAV_DIRECTION {} private final IntArray mOriginalTopRowIds; - private IntArray mTopRowIds; - private IntArray mBottomRowIds; + private final IntArray mTopRowIds = new IntArray(); + private final IntArray mBottomRowIds = new IntArray(); public TaskGridNavHelper(IntArray topIds, IntArray bottomIds, - List largeTileIds) { + List largeTileIds, boolean hasAddDesktopButton) { mOriginalTopRowIds = topIds.clone(); - generateTaskViewIdGrid(topIds, bottomIds, largeTileIds); + generateTaskViewIdGrid(topIds, bottomIds, largeTileIds, hasAddDesktopButton); } private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray, - List largeTileIds) { - - int maxSize = Math.max(topRowIdArray.size(), bottomRowIdArray.size()) - + largeTileIds.size(); - int minSize = Math.min(topRowIdArray.size(), bottomRowIdArray.size()) - + largeTileIds.size(); - - // Add Large tile task views first at the beginning - for (int i = 0; i < largeTileIds.size(); i++) { - topRowIdArray.add(i, largeTileIds.get(i)); - bottomRowIdArray.add(i, largeTileIds.get(i)); + List largeTileIds, boolean hasAddDesktopButton) { + // Add AddDesktopButton and lage tiles to both rows. + if (hasAddDesktopButton) { + mTopRowIds.add(ADD_DESK_PLACEHOLDER_ID); + mBottomRowIds.add(ADD_DESK_PLACEHOLDER_ID); } + for (Integer tileId : largeTileIds) { + mTopRowIds.add(tileId); + mBottomRowIds.add(tileId); + } + + // Add row ids to their respective rows. + mTopRowIds.addAll(topRowIdArray); + mBottomRowIds.addAll(bottomRowIdArray); // Fill in the shorter array with the ids from the longer one. - for (int i = minSize; i < maxSize; i++) { - if (i >= topRowIdArray.size()) { - topRowIdArray.add(bottomRowIdArray.get(i)); - } else { - bottomRowIdArray.add(topRowIdArray.get(i)); - } + while (mTopRowIds.size() > mBottomRowIds.size()) { + mBottomRowIds.add(mTopRowIds.get(mBottomRowIds.size())); + } + while (mBottomRowIds.size() > mTopRowIds.size()) { + mTopRowIds.add(mBottomRowIds.get(mTopRowIds.size())); } - // Add the clear all button to the end of both arrays - topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); - bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID); - - mTopRowIds = topRowIdArray; - mBottomRowIds = bottomRowIdArray; + // Add the clear all button to the end of both arrays. + mTopRowIds.add(CLEAR_ALL_PLACEHOLDER_ID); + mBottomRowIds.add(CLEAR_ALL_PLACEHOLDER_ID); } /** diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt index e353160c0c..9f3c017fbe 100644 --- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt +++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt @@ -17,13 +17,15 @@ package com.android.quickstep.views import android.content.Context -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.RoundRectShape +import android.graphics.Canvas +import android.graphics.Rect import android.util.AttributeSet import android.widget.ImageButton import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X import com.android.launcher3.R import com.android.launcher3.util.MultiPropertyFactory +import com.android.quickstep.util.BorderAnimator +import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator /** * Button for supporting multiple desktop sessions. The button will be next to the first TaskView @@ -55,20 +57,49 @@ class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: Attrib multiTranslationX[TranslationX.OFFSET.ordinal].value = value } - override fun onFinishInflate() { - super.onFinishInflate() + private val focusBorderAnimator: BorderAnimator = + createSimpleBorderAnimator( + context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_size), + context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), + this::getBorderBounds, + this, + context + .obtainStyledAttributes(attrs, R.styleable.AddDesktopButton) + .getColor( + R.styleable.AddDesktopButton_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) - background = - ShapeDrawable().apply { - shape = - RoundRectShape( - FloatArray(8) { R.dimen.add_desktop_button_size.toFloat() }, - null, - null, - ) - setTint( - resources.getColor(android.R.color.system_surface_bright_light, context.theme) - ) + var borderEnabled = false + set(value) { + if (field == value) { + return } + field = value + focusBorderAnimator.setBorderVisibility(visible = field && isFocused, animated = true) + } + + public override fun onFocusChanged( + gainFocus: Boolean, + direction: Int, + previouslyFocusedRect: Rect?, + ) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) + if (borderEnabled) { + focusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true) + } + } + + private fun getBorderBounds(bounds: Rect) { + bounds.set(0, 0, width, height) + val outlinePadding = + context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_outline_padding) + bounds.inset(-outlinePadding, -outlinePadding) + } + + override fun draw(canvas: Canvas) { + focusBorderAnimator.drawBorder(canvas) + super.draw(canvas) } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 28ab49685d..519d48eb13 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1632,6 +1632,9 @@ public abstract class RecentsView< taskView.setBorderEnabled(enabled); } mClearAllButton.setBorderEnabled(enabled); + if (mAddDesktopButton != null) { + mAddDesktopButton.setBorderEnabled(enabled); + } } /** @@ -4540,24 +4543,32 @@ public abstract class RecentsView< // Init task grid nav helper with top/bottom id arrays. TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(), - getBottomRowIdArray(), mUtils.getLargeTaskViewIds()); + getBottomRowIdArray(), mUtils.getLargeTaskViewIds(), mAddDesktopButton != null); // Get current page's task view ID. TaskView currentPageTaskView = getCurrentPageTaskView(); int currentPageTaskViewId; + final int clearAllButtonIndex = indexOfChild(mClearAllButton); + final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); if (currentPageTaskView != null) { currentPageTaskViewId = currentPageTaskView.getTaskViewId(); - } else if (mCurrentPage == indexOfChild(mClearAllButton)) { + } else if (mCurrentPage == clearAllButtonIndex) { currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; + } else if (mCurrentPage == addDesktopButtonIndex) { + currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID; } else { return INVALID_PAGE; } - int nextGridPage = + final int nextGridPage = taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); - return nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID - ? indexOfChild(mClearAllButton) - : indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); + if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) { + return clearAllButtonIndex; + } + if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) { + return addDesktopButtonIndex; + } + return indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); } private void runDismissAnimation(PendingAnimation pendingAnim) { @@ -6095,8 +6106,10 @@ public abstract class RecentsView< } private int getFirstViewIndex() { - final TaskView firstView; - if (mShowAsGridLastOnLayout) { + final View firstView; + if (mAddDesktopButton != null) { + firstView = mAddDesktopButton; + } else if (mShowAsGridLastOnLayout) { // For grid Overview, it always start if a large tile (focused task or desktop task) if // they exist, otherwise it start with the first task. TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 4b1b8dc48f..0465dbc0bb 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -248,8 +248,43 @@ constructor( ) private val tempCoordinates = FloatArray(2) - private val focusBorderAnimator: BorderAnimator? - private val hoverBorderAnimator: BorderAnimator? + private val focusBorderAnimator: BorderAnimator? = + focusBorderAnimator + ?: createSimpleBorderAnimator( + TaskCornerRadius.get(context).toInt(), + context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width), + this::getThumbnailBounds, + this, + context + .obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes) + .getColor( + R.styleable.TaskView_focusBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + + private val hoverBorderAnimator: BorderAnimator? = + hoverBorderAnimator + ?: if (enableCursorHoverStates()) + createSimpleBorderAnimator( + TaskCornerRadius.get(context).toInt(), + context.resources.getDimensionPixelSize(R.dimen.task_hover_border_width), + this::getThumbnailBounds, + this, + context + .obtainStyledAttributes( + attrs, + R.styleable.TaskView, + defStyleAttr, + defStyleRes, + ) + .getColor( + R.styleable.TaskView_hoverBorderColor, + BorderAnimator.DEFAULT_BORDER_COLOR, + ), + ) + else null + private val rootViewDisplayId: Int get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY @@ -519,40 +554,7 @@ constructor( init { setOnClickListener { _ -> onClick() } - val cursorHoverStatesEnabled = enableCursorHoverStates() - setWillNotDraw(!cursorHoverStatesEnabled) - context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use { - this.focusBorderAnimator = - focusBorderAnimator - ?: createSimpleBorderAnimator( - TaskCornerRadius.get(context).toInt(), - context.resources.getDimensionPixelSize( - R.dimen.keyboard_quick_switch_border_width - ), - { bounds: Rect -> getThumbnailBounds(bounds) }, - this, - it.getColor( - R.styleable.TaskView_focusBorderColor, - BorderAnimator.DEFAULT_BORDER_COLOR, - ), - ) - this.hoverBorderAnimator = - hoverBorderAnimator - ?: if (cursorHoverStatesEnabled) - createSimpleBorderAnimator( - TaskCornerRadius.get(context).toInt(), - context.resources.getDimensionPixelSize( - R.dimen.task_hover_border_width - ), - { bounds: Rect -> getThumbnailBounds(bounds) }, - this, - it.getColor( - R.styleable.TaskView_hoverBorderColor, - BorderAnimator.DEFAULT_BORDER_COLOR, - ), - ) - else null - } + setWillNotDraw(!enableCursorHoverStates()) } @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt index 7aab75f590..7066d21b0f 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt @@ -16,6 +16,7 @@ package com.android.quickstep.util import com.android.launcher3.util.IntArray +import com.android.quickstep.util.TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID import com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT @@ -619,6 +620,161 @@ class TaskGridNavHelperTest { .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) } + /* + 5 3 1→----| + ↓ + CLEAR_ALL ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromTop_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 1, + DIRECTION_RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL ADD_DESKTOP + ↑ + 6 4 2→----↑ + */ + @Test + fun withAddDesktopButton_pressRightFromBottom_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = 2, + DIRECTION_RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + ↓-------------------------------←| + | ↑ + ↓ 5 3 1 | + CLEAR_ALL ADD_DESKTOP--→ + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromAddDesktopButton_goesToClearAllButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + DIRECTION_RIGHT, + delta = -1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID) + } + + /* + |→--------------------------------| + | | + ↑ 5 3 1 ↓ + ←------CLEAR_ALL ADD_DESKTOP + + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressLeftFromClearAllButton_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, + DIRECTION_LEFT, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + ←--↑ + CLEAR_ALL ↓-→ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressUpOnAddDesktop_stayOnAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + DIRECTION_UP, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL ↑--→ADD_DESKTOP + ↑←--↓ + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressDownOnAddDesktop_stayOnAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + DIRECTION_DOWN, + delta = 1, + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + + /* + 5 3 1 + CLEAR_ALL DESKTOP--→ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressRightFromDesktopTask_goesToAddDesktopButton() { + assertThat( + getNextGridPage( + currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID, + DIRECTION_LEFT, + delta = 1, + largeTileIds = listOf(DESKTOP_TASK_ID), + hasAddDesktopButton = true, + ) + ) + .isEqualTo(DESKTOP_TASK_ID) + } + + /* + 5 3 1 + CLEAR_ALL DESKTOP←--ADD_DESKTOP + 6 4 2 + */ + @Test + fun withAddDesktopButton_pressLeftFromAddDesktopButton_goesToDesktopTask() { + assertThat( + getNextGridPage( + currentPageTaskViewId = DESKTOP_TASK_ID, + DIRECTION_RIGHT, + delta = -1, + largeTileIds = listOf(DESKTOP_TASK_ID), + hasAddDesktopButton = true, + ) + ) + .isEqualTo(ADD_DESK_PLACEHOLDER_ID) + } + private fun getNextGridPage( currentPageTaskViewId: Int, direction: Int, @@ -626,8 +782,10 @@ class TaskGridNavHelperTest { topIds: IntArray = IntArray.wrap(1, 3, 5), bottomIds: IntArray = IntArray.wrap(2, 4, 6), largeTileIds: List = emptyList(), + hasAddDesktopButton: Boolean = false, ): Int { - val taskGridNavHelper = TaskGridNavHelper(topIds, bottomIds, largeTileIds) + val taskGridNavHelper = + TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton) return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, true) }