From 79f3cefae201198fc60ed1bfe19d54e3b9226f40 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Fri, 16 Aug 2024 21:00:00 +0000 Subject: [PATCH] Animate away Recent Apps section when going to home Also fix calculation for divider position during this transition when there are recent apps. Added tests as well. Flag: com.android.launcher3.enable_recents_in_taskbar Flag: com.android.window.flags.enable_desktop_windowing_taskbar_running_apps Test: TaskbarViewControllerTest Bug: 343522351 Change-Id: Ifffd620c1d4c156b37561584d6e3278b39804439 --- .../launcher3/taskbar/TaskbarView.java | 24 +- .../taskbar/TaskbarViewController.java | 93 +++++-- .../taskbar/TaskbarViewControllerTest.kt | 233 ++++++++++++++++++ 3 files changed, 326 insertions(+), 24 deletions(-) create mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index fc76972105..54a7fdc613 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -108,9 +108,15 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar private long mAllAppsButtonTouchDelayMs; private boolean mAllAppsTouchTriggered; - // Only non-null when device supports having an All Apps button. + // Only non-null when device supports having an All Apps button, or Recent Apps. private @Nullable IconButtonView mTaskbarDivider; + /** + * Whether the divider is between Hotseat icons and Recents, + * instead of between All Apps button and Hotseat. + */ + private boolean mAddedDividerForRecents; + private final View mQsb; private final float mTransientTaskbarMinWidth; @@ -340,7 +346,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar protected void updateHotseatItems(ItemInfo[] hotseatItemInfos, List recentTasks) { int nextViewIndex = 0; int numViewsAnimated = 0; - boolean addedDividerForRecents = false; + mAddedDividerForRecents = false; if (mAllAppsButton != null) { removeView(mAllAppsButton); @@ -435,7 +441,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar if (mTaskbarDivider != null && !recentTasks.isEmpty()) { addView(mTaskbarDivider, nextViewIndex++); - addedDividerForRecents = true; + mAddedDividerForRecents = true; } // Add Recent/Running icons. @@ -499,10 +505,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar } if (mAllAppsButton != null) { - addView(mAllAppsButton, mIsRtl ? getChildCount() : 0); + addView(mAllAppsButton, mIsRtl ? hotseatItemInfos.length : 0); // If there are no recent tasks, add divider after All Apps (unless it's the only view). - if (!addedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) { + if (!mAddedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) { addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1); } } @@ -733,6 +739,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar return mTaskbarDivider; } + /** + * Returns whether the divider is between Hotseat icons and Recents, + * instead of between All Apps button and Hotseat. + */ + public boolean isDividerForRecents() { + return mAddedDividerForRecents; + } + /** * Returns the QSB in the taskbar. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index b8b85d120a..871efa503a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -100,6 +100,12 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar public static final int ALPHA_INDEX_BUBBLE_BAR = 7; private static final int NUM_ALPHA_CHANNELS = 8; + /** Only used for animation purposes, to position the divider between two item indices. */ + public static final float DIVIDER_VIEW_POSITION_OFFSET = 0.5f; + + /** Used if an unexpected edge case is hit in {@link #getPositionInHotseat}. */ + private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100; + private static boolean sEnableModelLoadingForTests = true; private final TaskbarActivityContext mActivity; @@ -741,17 +747,22 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar ? mTransientTaskbarDp.taskbarBottomMargin : mPersistentTaskbarDp.taskbarBottomMargin; + int firstRecentTaskIndex = -1; for (int i = 0; i < mTaskbarView.getChildCount(); i++) { View child = mTaskbarView.getChildAt(i); boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView(); boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView(); + boolean isRecentTask = child.getTag() instanceof GroupTask; + // TODO(b/343522351): show recents on the home screen. + final boolean isRecentsInHotseat = false; if (!mIsHotseatIconOnTopWhenAligned) { // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController // plays iconAlignment to 1 really fast, therefore moving the fading towards the end // to avoid icons disappearing rather than fading out visually. setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f)); } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) - || (isTaskbarDividerView && enableTaskbarPinning())) { + || (isTaskbarDividerView && enableTaskbarPinning()) + || (isRecentTask && !isRecentsInHotseat)) { if (!isToHome && mIsHotseatIconOnTopWhenAligned && mIsStashed) { @@ -812,25 +823,17 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar continue; } - float positionInHotseat; - if (isAllAppsButton) { - // Note that there is no All Apps button in the hotseat, - // this position is only used as its convenient for animation purposes. - positionInHotseat = Utilities.isRtl(child.getResources()) - ? taskbarDp.numShownHotseatIcons - : -1; - } else if (isTaskbarDividerView) { - // Note that there is no taskbar divider view in the hotseat, - // this position is only used as its convenient for animation purposes. - positionInHotseat = Utilities.isRtl(child.getResources()) - ? taskbarDp.numShownHotseatIcons - 0.5f - : -0.5f; - } else if (child.getTag() instanceof ItemInfo) { - positionInHotseat = ((ItemInfo) child.getTag()).screenId; - } else { - Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); - continue; + int recentTaskIndex = -1; + if (isRecentTask) { + if (firstRecentTaskIndex < 0) { + firstRecentTaskIndex = i; + } + recentTaskIndex = i - firstRecentTaskIndex; } + float positionInHotseat = getPositionInHotseat(taskbarDp.numShownHotseatIcons, child, + mIsRtl, isAllAppsButton, isTaskbarDividerView, + mTaskbarView.isDividerForRecents(), recentTaskIndex); + if (positionInHotseat == ERROR_POSITION_IN_HOTSEAT_NOT_FOUND) continue; float hotseatAdjustedBorderSpace = launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext()); @@ -866,6 +869,58 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar return controller; } + /** + * Returns the index of the given child relative to its position in hotseat. + * Examples: + * -1 is the item before the first hotseat item. + * -0.5 is between those (e.g. for the divider). + * {@link #ERROR_POSITION_IN_HOTSEAT_NOT_FOUND} if there's no calculation relative to hotseat. + */ + @VisibleForTesting + float getPositionInHotseat(int numShownHotseatIcons, View child, boolean isRtl, + boolean isAllAppsButton, boolean isTaskbarDividerView, boolean isDividerForRecents, + int recentTaskIndex) { + float positionInHotseat; + // Note that there is no All Apps button in the hotseat, + // this position is only used as it's convenient for animation purposes. + float allAppsButtonPositionInHotseat = isRtl + // Right after all hotseat items. + // [HHHHHH]|[>A<] + ? numShownHotseatIcons + // Right before all hotseat items. + // [>A<]|[HHHHHH] + : -1; + // Note that there are no recent tasks in the hotseat, + // this position is only used as it's convenient for animation purposes. + float firstRecentTaskPositionInHotseat = isRtl + // After all hotseat icons and All Apps button. + // [HHHHHH][A]|[>RR 0 + ? relativePosition - DIVIDER_VIEW_POSITION_OFFSET + : relativePosition + DIVIDER_VIEW_POSITION_OFFSET; + } else if (child.getTag() instanceof ItemInfo) { + positionInHotseat = ((ItemInfo) child.getTag()).screenId; + } else if (recentTaskIndex >= 0) { + positionInHotseat = firstRecentTaskPositionInHotseat + recentTaskIndex; + } else { + Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); + return ERROR_POSITION_IN_HOTSEAT_NOT_FOUND; + } + return positionInHotseat; + } + private boolean bubbleBarHasBubbles() { return mControllers.bubbleControllers.isPresent() && mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles(); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt new file mode 100644 index 0000000000..4aac1dc83f --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt @@ -0,0 +1,233 @@ +/* + * 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.view.View +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.launcher3.taskbar.TaskbarViewController.DIVIDER_VIEW_POSITION_OFFSET +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule +import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController +import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) +/** + * Legend for the comments below: + * A: All Apps Button + * H: Hotseat item + * |: Divider + * R: Recent item + * + * The comments are formatted in two lines: + * // Items in taskbar, e.g. A | HHHHHH + * // Index of items relative to Hotseat: -1 -.5 012345 + */ +class TaskbarViewControllerTest { + + private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) + @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(this, context) + + @InjectController lateinit var taskbarViewController: TaskbarViewController + + @Test + fun testGetPositionInHotseat_allAppsButton_nonRtl() { + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ 6, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ true, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1 + ) + // [>A<] | [HHHHHH] + // -1 -.5 012345 + assertThat(position).isEqualTo(-1) + } + + @Test + fun testGetPositionInHotseat_allAppsButton_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ true, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1 + ) + // [HHHHHH] | [>A<] + // 012345 5.5 6 + assertThat(position).isEqualTo(numShownHotseatIcons) + } + + @Test + fun testGetPositionInHotseat_dividerView_notForRecents_nonRtl() { + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ 6, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1 + ) + // [A] >|< [HHHHHH] + // -1 -.5 012345 + assertThat(position).isEqualTo(-DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_forRecents_nonRtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ true, + /* recentTaskIndex = */ -1 + ) + // [A] [HHHHHH] >|< [RR] + // -1 012345 5.5 67 + assertThat(position).isEqualTo(numShownHotseatIcons - DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_notForRecents_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ -1 + ) + // [HHHHHH] >|< [A] + // 012345 5.5 6 + assertThat(position).isEqualTo(numShownHotseatIcons - DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_dividerView_forRecents_rtl() { + val numShownHotseatIcons = 6 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ true, + /* isDividerForRecents = */ true, + /* recentTaskIndex = */ -1 + ) + // [HHHHHH][A] >|< [RR] + // 012345 6 6.5 78 + assertThat(position).isEqualTo(numShownHotseatIcons + DIVIDER_VIEW_POSITION_OFFSET) + } + + @Test + fun testGetPositionInHotseat_recentTasks_firstRecentIndex_nonRtl() { + val numShownHotseatIcons = 6 + val recentTaskIndex = 0 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ false, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ recentTaskIndex + ) + // [A][HHHHHH] | [>RR<] + // -1 012345 5.5 6 7 + assertThat(position).isEqualTo(numShownHotseatIcons + recentTaskIndex) + } + + @Test + fun testGetPositionInHotseat_recentTasks_firstRecentIndex_rtl() { + val numShownHotseatIcons = 6 + val recentTaskIndex = 0 + val position = + taskbarViewController.getPositionInHotseat( + /* numShownHotseatIcons = */ numShownHotseatIcons, + /* child = */ View(context), + /* isRtl = */ true, + /* isAllAppsButton = */ false, + /* isTaskbarDividerView = */ false, + /* isDividerForRecents = */ false, + /* recentTaskIndex = */ recentTaskIndex + ) + // [HHHHHH][A] | [>RR<] + // 012345 6 6.5 7 8 + assertThat(position).isEqualTo(numShownHotseatIcons + 1 + recentTaskIndex) + } +}