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
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index fc3b4c7..0a81f78 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -149,20 +149,39 @@
taskListChangeId =
recentsModel.getTasks { tasks ->
allRecentTasks = tasks
+ val oldRunningPackages = runningAppPackages
+ val oldMinimizedPackages = minimizedAppPackages
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().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 @@
}
}
}
+ return shownTasksChanged
}
private fun computeShownRunningTasks(): List<GroupTask> {
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 8d99069..307b2fa 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.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 @@
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 945ffe3..e8b611c 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -26,6 +26,7 @@
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 @@
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 0000000..7aed579
--- /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<Task> {
+ 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 0000000..a6d3887
--- /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 486dc68..13c4f72 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.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 @@
@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 @@
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<String>,
runningTaskPackages: List<String>,
@@ -556,9 +632,11 @@
}
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"),