From 7829a1ced26b81872eb302666577fcec35bde598 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Thu, 27 Jun 2024 00:14:02 +0000 Subject: [PATCH] Only commitRunningAppsToUI if shownTasks changed - Add GroupTask and DesktopTask equals() (with tests) - Add tests to verify multiple onRecentTasksChanged doesn't commitRunningAppsToUI if there's no change - Add tests to verify commitRunningAppsToUI is still called if minimized or running apps set changes Bug: 348802109 Bug: 348787176 Test: TaskbarRecentAppsControllerTest Test: GroupTaskTest Test: log TaskbarView#onMeasure() locally, ensure it is called much less despite noisy onRecentTasksChanged callbacks Flag: com.android.window.flags.enable_desktop_windowing_taskbar_running_apps Change-Id: I3efff7f4492272f88aa2bdbd7cc45bd2bf8156f6 --- .../taskbar/TaskbarRecentAppsController.kt | 26 ++++- .../android/quickstep/util/DesktopTask.java | 13 +++ .../com/android/quickstep/util/GroupTask.java | 14 +++ .../android/quickstep/util/DesktopTaskTest.kt | 67 +++++++++++ .../android/quickstep/util/GroupTaskTest.kt | 109 ++++++++++++++++++ .../TaskbarRecentAppsControllerTest.kt | 82 ++++++++++++- 6 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt index fc3b4c758e..0a81f78c98 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt @@ -149,20 +149,39 @@ class TaskbarRecentAppsController( taskListChangeId = recentsModel.getTasks { tasks -> allRecentTasks = tasks + val oldRunningPackages = runningAppPackages + val oldMinimizedPackages = minimizedAppPackages desktopTask = allRecentTasks.filterIsInstance().firstOrNull() - onRecentsOrHotseatChanged() - controllers.taskbarViewController.commitRunningAppsToUI() + val runningPackagesChanged = oldRunningPackages != runningAppPackages + val minimizedPackagessChanged = oldMinimizedPackages != minimizedAppPackages + if ( + onRecentsOrHotseatChanged() || + runningPackagesChanged || + minimizedPackagessChanged + ) { + controllers.taskbarViewController.commitRunningAppsToUI() + } } } } - private fun onRecentsOrHotseatChanged() { + /** + * Updates [shownTasks] when Recents or Hotseat changes. + * + * @return Whether [shownTasks] changed. + */ + private fun onRecentsOrHotseatChanged(): Boolean { + val oldShownTasks = shownTasks shownTasks = if (isInDesktopMode) { computeShownRunningTasks() } else { computeShownRecentTasks() } + val shownTasksChanged = oldShownTasks != shownTasks + if (!shownTasksChanged) { + return shownTasksChanged + } for (groupTask in shownTasks) { for (task in groupTask.tasks) { @@ -174,6 +193,7 @@ class TaskbarRecentAppsController( } } } + return shownTasksChanged } private fun computeShownRunningTasks(): List { diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java index 8d99069c19..307b2faf85 100644 --- a/quickstep/src/com/android/quickstep/util/DesktopTask.java +++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java @@ -22,6 +22,7 @@ import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import java.util.List; +import java.util.Objects; /** * A {@link Task} container that can contain N number of tasks that are part of the desktop in @@ -68,4 +69,16 @@ public class DesktopTask extends GroupTask { return "type=" + taskViewType + " tasks=" + tasks; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DesktopTask that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(tasks, that.tasks); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), tasks); + } } diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java index 945ffe31f6..e8b611c959 100644 --- a/quickstep/src/com/android/quickstep/util/GroupTask.java +++ b/quickstep/src/com/android/quickstep/util/GroupTask.java @@ -26,6 +26,7 @@ import com.android.systemui.shared.recents.model.Task; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * A {@link Task} container that can contain one or two tasks, depending on if the two tasks @@ -91,4 +92,17 @@ public class GroupTask { return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GroupTask that)) return false; + return taskViewType == that.taskViewType && Objects.equals(task1, + that.task1) && Objects.equals(task2, that.task2) + && Objects.equals(mSplitBounds, that.mSplitBounds); + } + + @Override + public int hashCode() { + return Objects.hash(task1, task2, mSplitBounds, taskViewType); + } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt new file mode 100644 index 0000000000..7aed579f44 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt @@ -0,0 +1,67 @@ +/* + * 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.quickstep.util + +import android.content.ComponentName +import android.content.Intent +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +class DesktopTaskTest { + + @Test + fun testDesktopTask_sameInstance_isEqual() { + val task = DesktopTask(createTasks(1)) + assertThat(task).isEqualTo(task) + } + + @Test + fun testDesktopTask_identicalConstructor_isEqual() { + val task1 = DesktopTask(createTasks(1)) + val task2 = DesktopTask(createTasks(1)) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testDesktopTask_copy_isEqual() { + val task1 = DesktopTask(createTasks(1)) + val task2 = task1.copy() + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testDesktopTask_differentId_isNotEqual() { + val task1 = DesktopTask(createTasks(1)) + val task2 = DesktopTask(createTasks(2)) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testDesktopTask_differentLength_isNotEqual() { + val task1 = DesktopTask(createTasks(1)) + val task2 = DesktopTask(createTasks(1, 2)) + assertThat(task1).isNotEqualTo(task2) + } + + private fun createTasks(vararg ids: Int): List { + return ids.map { Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 0)) } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt new file mode 100644 index 0000000000..a6d3887538 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt @@ -0,0 +1,109 @@ +/* + * 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.quickstep.util + +import android.content.ComponentName +import android.content.Intent +import android.graphics.Rect +import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.SplitConfigurationOptions +import com.android.quickstep.views.TaskView +import com.android.systemui.shared.recents.model.Task +import com.android.wm.shell.common.split.SplitScreenConstants +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(LauncherMultivalentJUnit::class) +class GroupTaskTest { + + @Test + fun testGroupTask_sameInstance_isEqual() { + val task = GroupTask(createTask(1)) + assertThat(task).isEqualTo(task) + } + + @Test + fun testGroupTask_identicalConstructor_isEqual() { + val task1 = GroupTask(createTask(1)) + val task2 = GroupTask(createTask(1)) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_copy_isEqual() { + val task1 = GroupTask(createTask(1)) + val task2 = task1.copy() + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_differentId_isNotEqual() { + val task1 = GroupTask(createTask(1)) + val task2 = GroupTask(createTask(2)) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testGroupTask_equalSplitTasks_isEqual() { + val splitBounds = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_50_50 + ) + val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED) + val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED) + assertThat(task1).isEqualTo(task2) + } + + @Test + fun testGroupTask_differentSplitTasks_isNotEqual() { + val splitBounds1 = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_50_50 + ) + val splitBounds2 = + SplitConfigurationOptions.SplitBounds( + Rect(), + Rect(), + 1, + 2, + SplitScreenConstants.SNAP_TO_30_70 + ) + val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskView.Type.GROUPED) + val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskView.Type.GROUPED) + assertThat(task1).isNotEqualTo(task2) + } + + @Test + fun testGroupTask_differentType_isNotEqual() { + val task1 = GroupTask(createTask(1), null, null, TaskView.Type.SINGLE) + val task2 = GroupTask(createTask(1), null, null, TaskView.Type.DESKTOP) + assertThat(task1).isNotEqualTo(task2) + } + + private fun createTask(id: Int): Task { + return Task(Task.TaskKey(id, 0, Intent(), ComponentName("", ""), 0, 0)) + } +} diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt index 486dc688d8..13c4f72dd9 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -44,6 +44,7 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -56,7 +57,6 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { @Mock private lateinit var mockRecentsModel: RecentsModel @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController - private var nextTaskId: Int = 500 private var taskListChangeId: Int = 1 private lateinit var recentAppsController: TaskbarRecentAppsController @@ -478,6 +478,82 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { assertThat(shownPackages).containsExactlyElementsIn(expectedPackages) } + @Test + fun onRecentTasksChanged_notInDesktopMode_noActualChangeToRecents_commitRunningAppsToUI_notCalled() { + setInDesktopMode(false) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = emptyList(), + recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2) + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() { + setInDesktopMode(true) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with the same tasks, verify it's a no-op. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() { + setInDesktopMode(true) + val runningTasks = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = runningTasks, + minimizedTaskIndices = setOf(0), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with a new minimized app, verify we update UI. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = emptyList(), + runningTaskPackages = runningTasks, + minimizedTaskIndices = setOf(0, 1), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(2)).commitRunningAppsToUI() + } + + @Test + fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() { + setInDesktopMode(true) + val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2) + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(1)).commitRunningAppsToUI() + // Call onRecentTasksChanged() again with a new running app, verify we update UI. + prepareHotseatAndRunningAndRecentApps( + hotseatPackages = hotseatPackages, + runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, HOTSEAT_PACKAGE_1), + recentTaskPackages = emptyList() + ) + verify(taskbarViewController, times(2)).commitRunningAppsToUI() + } + private fun prepareHotseatAndRunningAndRecentApps( hotseatPackages: List, runningTaskPackages: List, @@ -556,9 +632,11 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { } private fun createTask(packageName: String, isVisible: Boolean = true): Task { + // Use the number at the end of the test packageName as the id. + val id = packageName[packageName.length - 1].code return Task( Task.TaskKey( - nextTaskId++, + id, WINDOWING_MODE_FREEFORM, Intent().apply { `package` = packageName }, ComponentName(packageName, "TestActivity"),