Merge "Make TasksRepository listen to changes in task visuals and update" into main
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index db03dac..a01ceb2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -43,6 +43,7 @@
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
+import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
@@ -65,7 +66,8 @@
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
- TaskStackChangeListener, TaskVisualsChangeListener, SafeCloseable {
+ TaskStackChangeListener, TaskVisualsChangeListener, TaskVisualsChangeNotifier,
+ SafeCloseable {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -287,6 +289,7 @@
/**
* Adds a listener for visuals changes
*/
+ @Override
public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.add(listener);
}
@@ -294,6 +297,7 @@
/**
* Removes a previously added listener
*/
+ @Override
public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
mThumbnailChangeListeners.remove(listener);
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 3c6c3e4..580dcc2 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -27,6 +27,7 @@
import com.android.launcher3.R;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.recents.data.HighResLoadingStateNotifier;
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource;
import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
import com.android.quickstep.util.TaskKeyCache;
@@ -48,7 +49,7 @@
private final boolean mEnableTaskSnapshotPreloading;
private final Context mContext;
- public static class HighResLoadingState {
+ public static class HighResLoadingState implements HighResLoadingStateNotifier {
private boolean mForceHighResThumbnails;
private boolean mVisible;
private boolean mFlingingFast;
@@ -65,11 +66,13 @@
mForceHighResThumbnails = !supportsLowResThumbnails();
}
- public void addCallback(HighResLoadingStateChangedCallback callback) {
+ @Override
+ public void addCallback(@NonNull HighResLoadingStateChangedCallback callback) {
mCallbacks.add(callback);
}
- public void removeCallback(HighResLoadingStateChangedCallback callback) {
+ @Override
+ public void removeCallback(@NonNull HighResLoadingStateChangedCallback callback) {
mCallbacks.remove(callback);
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
new file mode 100644
index 0000000..df546ca
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.recents.data
+
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+
+/** Notifies added callbacks that high res state has changed */
+interface HighResLoadingStateNotifier {
+ /** Adds a callback for high res loading state */
+ fun addCallback(callback: HighResLoadingStateChangedCallback)
+
+ /** Removes a callback for high res loading state */
+ fun removeCallback(callback: HighResLoadingStateChangedCallback)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 4f7a541..9c4248c 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -41,10 +41,4 @@
* populated e.g. icons/thumbnails etc.
*/
fun setVisibleTasks(visibleTaskIdList: List<Int>)
-
- /**
- * Override [ThumbnailData] with a map of taskId to [ThumbnailData]. The override only applies
- * if the tasks are already visible, and will be invalidated when tasks become invisible.
- */
- fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>)
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt
new file mode 100644
index 0000000..6e7789d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangeNotifier.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.recents.data
+
+import com.android.quickstep.util.TaskVisualsChangeListener
+
+/** Notifies added listeners that task visuals have changed */
+interface TaskVisualsChangeNotifier {
+ /** Adds a listener for visuals changes */
+ fun addThumbnailChangeListener(listener: TaskVisualsChangeListener)
+
+ /** Removes a listener for visuals changes */
+ fun removeThumbnailChangeListener(listener: TaskVisualsChangeListener)
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
new file mode 100644
index 0000000..a141e89
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.recents.data
+
+import android.os.UserHandle
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
+import com.android.quickstep.util.TaskVisualsChangeListener
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/** Delegates the checking of task visuals (thumbnails, high res changes, icons) */
+interface TaskVisualsChangedDelegate :
+ TaskVisualsChangeListener, HighResLoadingStateChangedCallback {
+ /** Registers a callback for visuals relating to icons */
+ fun registerTaskIconChangedCallback(
+ taskKey: Task.TaskKey,
+ taskIconChangedCallback: TaskIconChangedCallback
+ )
+
+ /** Unregisters a callback for visuals relating to icons */
+ fun unregisterTaskIconChangedCallback(taskKey: Task.TaskKey)
+
+ /** Registers a callback for visuals relating to thumbnails */
+ fun registerTaskThumbnailChangedCallback(
+ taskKey: Task.TaskKey,
+ taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+ )
+
+ /** Unregisters a callback for visuals relating to thumbnails */
+ fun unregisterTaskThumbnailChangedCallback(taskKey: Task.TaskKey)
+
+ /** A callback for task icon changes */
+ interface TaskIconChangedCallback {
+ /** Informs the listener that the task icon has changed */
+ fun onTaskIconChanged()
+ }
+
+ /** A callback for task thumbnail changes */
+ interface TaskThumbnailChangedCallback {
+ /** Informs the listener that the task thumbnail data has changed to [thumbnailData] */
+ fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?)
+
+ /** Informs the listener that the default resolution for loading thumbnails has changed */
+ fun onHighResLoadingStateChanged()
+ }
+}
+
+class TaskVisualsChangedDelegateImpl(
+ private val taskVisualsChangeNotifier: TaskVisualsChangeNotifier,
+ private val highResLoadingStateNotifier: HighResLoadingStateNotifier,
+) : TaskVisualsChangedDelegate {
+ private val taskIconChangedCallbacks =
+ mutableMapOf<Int, Pair<Task.TaskKey, TaskIconChangedCallback>>()
+ private val taskThumbnailChangedCallbacks =
+ mutableMapOf<Int, Pair<Task.TaskKey, TaskThumbnailChangedCallback>>()
+ private var isListening = false
+
+ @Synchronized
+ private fun onCallbackRegistered() {
+ if (isListening) return
+
+ taskVisualsChangeNotifier.addThumbnailChangeListener(this)
+ highResLoadingStateNotifier.addCallback(this)
+ isListening = true
+ }
+
+ @Synchronized
+ private fun onCallbackUnregistered() {
+ if (!isListening) return
+
+ if (taskIconChangedCallbacks.size + taskThumbnailChangedCallbacks.size == 0) {
+ taskVisualsChangeNotifier.removeThumbnailChangeListener(this)
+ highResLoadingStateNotifier.removeCallback(this)
+ }
+
+ isListening = false
+ }
+
+ override fun onTaskIconChanged(taskId: Int) {
+ taskIconChangedCallbacks[taskId]?.let { (_, callback) -> callback.onTaskIconChanged() }
+ }
+
+ override fun onTaskIconChanged(pkg: String, user: UserHandle) {
+ taskIconChangedCallbacks.values
+ .filter { (taskKey, _) ->
+ pkg == taskKey.packageName && user.identifier == taskKey.userId
+ }
+ .forEach { (_, callback) -> callback.onTaskIconChanged() }
+ }
+
+ override fun onTaskThumbnailChanged(taskId: Int, thumbnailData: ThumbnailData?): Task? {
+ taskThumbnailChangedCallbacks[taskId]?.let { (_, callback) ->
+ callback.onTaskThumbnailChanged(thumbnailData)
+ }
+ return null
+ }
+
+ override fun onHighResLoadingStateChanged(enabled: Boolean) {
+ taskThumbnailChangedCallbacks.values.forEach { (_, callback) ->
+ callback.onHighResLoadingStateChanged()
+ }
+ }
+
+ override fun registerTaskIconChangedCallback(
+ taskKey: Task.TaskKey,
+ taskIconChangedCallback: TaskIconChangedCallback
+ ) {
+ taskIconChangedCallbacks[taskKey.id] = taskKey to taskIconChangedCallback
+ onCallbackRegistered()
+ }
+
+ override fun unregisterTaskIconChangedCallback(taskKey: Task.TaskKey) {
+ taskIconChangedCallbacks.remove(taskKey.id)
+ onCallbackUnregistered()
+ }
+
+ override fun registerTaskThumbnailChangedCallback(
+ taskKey: Task.TaskKey,
+ taskThumbnailChangedCallback: TaskThumbnailChangedCallback
+ ) {
+ taskThumbnailChangedCallbacks[taskKey.id] = taskKey to taskThumbnailChangedCallback
+ onCallbackRegistered()
+ }
+
+ override fun unregisterTaskThumbnailChangedCallback(taskKey: Task.TaskKey) {
+ taskThumbnailChangedCallbacks.remove(taskKey.id)
+ onCallbackUnregistered()
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6f9d157..eb3c2d1 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -18,6 +18,8 @@
import android.graphics.drawable.Drawable
import com.android.launcher3.util.coroutines.DispatcherProvider
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.quickstep.util.GroupTask
@@ -26,18 +28,20 @@
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -46,12 +50,12 @@
private val recentsModel: RecentTasksDataSource,
private val taskThumbnailDataSource: TaskThumbnailDataSource,
private val taskIconDataSource: TaskIconDataSource,
+ private val taskVisualsChangedDelegate: TaskVisualsChangedDelegate,
recentsCoroutineScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
) : RecentTasksRepository {
private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
- private val thumbnailOverride = MutableStateFlow(mapOf<Int, ThumbnailData>())
private val taskData =
groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } }
@@ -85,15 +89,13 @@
.distinctUntilChanged()
private val augmentedTaskData: Flow<List<Task>> =
- combine(taskData, thumbnailQueryResults, iconQueryResults, thumbnailOverride) {
+ combine(taskData, thumbnailQueryResults, iconQueryResults) {
tasks,
thumbnailQueryResults,
- iconQueryResults,
- thumbnailOverride ->
+ iconQueryResults ->
tasks.onEach { task ->
// Add retrieved thumbnails + remove unnecessary thumbnails (e.g. invisible)
- task.thumbnail =
- thumbnailOverride[task.key.id] ?: thumbnailQueryResults[task.key.id]
+ task.thumbnail = thumbnailQueryResults[task.key.id]
// TODO(b/352331675) don't load icons for DesktopTaskView
// Add retrieved icons + remove unnecessary icons
@@ -121,61 +123,76 @@
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
this.visibleTaskIds.value = visibleTaskIdList.toSet()
- addOrUpdateThumbnailOverride(emptyMap())
- }
-
- override fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- this.thumbnailOverride.value =
- this.thumbnailOverride.value
- .toMutableMap()
- .apply { putAll(thumbnailOverride) }
- .filterKeys(this.visibleTaskIds.value::contains)
}
/** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
- private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest = flow {
- emit(task.key.id to task.thumbnail)
- val thumbnailDataResult: ThumbnailData? =
- withContext(dispatcherProvider.main) {
- suspendCancellableCoroutine { continuation ->
- val cancellableTask =
- taskThumbnailDataSource.getThumbnailInBackground(task) {
- continuation.resume(it)
- }
- continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest = callbackFlow {
+ trySend(task.key.id to task.thumbnail)
+ trySend(task.key.id to getThumbnailFromDataSource(task))
+
+ val callback =
+ object : TaskThumbnailChangedCallback {
+ override fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?) {
+ trySend(task.key.id to thumbnailData)
+ }
+
+ override fun onHighResLoadingStateChanged() {
+ launch { trySend(task.key.id to getThumbnailFromDataSource(task)) }
}
}
- emit(task.key.id to thumbnailDataResult)
+ taskVisualsChangedDelegate.registerTaskThumbnailChangedCallback(task.key, callback)
+ awaitClose { taskVisualsChangedDelegate.unregisterTaskThumbnailChangedCallback(task.key) }
}
- /** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
+ /** Flow wrapper for [TaskIconDataSource.getIconInBackground] api */
private fun getIconDataRequest(task: Task): IconDataRequest =
- flow {
- emit(task.key.id to task.getTaskIconQueryResponse())
- val iconDataResponse: TaskIconQueryResponse? =
- withContext(dispatcherProvider.main) {
- suspendCancellableCoroutine { continuation ->
- val cancellableTask =
- taskIconDataSource.getIconInBackground(task) {
- icon,
- contentDescription,
- title ->
- icon.constantState?.let {
- continuation.resume(
- TaskIconQueryResponse(
- it.newDrawable().mutate(),
- contentDescription,
- title
- )
- )
- }
- }
- continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ callbackFlow {
+ trySend(task.key.id to task.getTaskIconQueryResponse())
+ trySend(task.key.id to getIconFromDataSource(task))
+
+ val callback =
+ object : TaskIconChangedCallback {
+ override fun onTaskIconChanged() {
+ launch { trySend(task.key.id to getIconFromDataSource(task)) }
}
}
- emit(task.key.id to iconDataResponse)
+ taskVisualsChangedDelegate.registerTaskIconChangedCallback(task.key, callback)
+ awaitClose {
+ taskVisualsChangedDelegate.unregisterTaskIconChangedCallback(task.key)
+ }
}
.distinctUntilChanged()
+
+ private suspend fun getThumbnailFromDataSource(task: Task) =
+ withContext(dispatcherProvider.main) {
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskThumbnailDataSource.getThumbnailInBackground(task) {
+ continuation.resume(it)
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ }
+
+ private suspend fun getIconFromDataSource(task: Task) =
+ withContext(dispatcherProvider.main) {
+ suspendCancellableCoroutine { continuation ->
+ val cancellableTask =
+ taskIconDataSource.getIconInBackground(task) { icon, contentDescription, title
+ ->
+ icon.constantState?.let {
+ continuation.resume(
+ TaskIconQueryResponse(
+ it.newDrawable().mutate(),
+ contentDescription,
+ title
+ )
+ )
+ }
+ }
+ continuation.invokeOnCancellation { cancellableTask?.cancel() }
+ }
+ }
}
data class TaskIconQueryResponse(
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 0b672d1..0a5544f 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -22,6 +22,8 @@
import com.android.launcher3.util.coroutines.ProductionDispatchers
import com.android.quickstep.RecentsModel
import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
import com.android.quickstep.recents.data.TasksRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.GetThumbnailUseCase
@@ -60,14 +62,22 @@
val recentsCoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("RecentsView"))
set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
+ val recentsModel = RecentsModel.INSTANCE.get(appContext)
+ val taskVisualsChangedDelegate =
+ TaskVisualsChangedDelegateImpl(
+ recentsModel,
+ recentsModel.thumbnailCache.highResLoadingState
+ )
+ set(TaskVisualsChangedDelegate::class.java.simpleName, taskVisualsChangedDelegate)
// Create RecentsTaskRepository singleton
val recentTasksRepository: RecentTasksRepository =
- with(RecentsModel.INSTANCE.get(appContext)) {
+ with(recentsModel) {
TasksRepository(
this,
thumbnailCache,
iconCache,
+ taskVisualsChangedDelegate,
recentsCoroutineScope,
ProductionDispatchers
)
@@ -155,6 +165,7 @@
thumbnailCache,
iconCache,
get(),
+ get(),
ProductionDispatchers
)
}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index b1f46a3..1716f2e 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -58,10 +58,6 @@
recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
}
- fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- recentsTasksRepository.addOrUpdateThumbnailOverride(thumbnailOverride)
- }
-
suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>) {
combine(
updatedThumbnails.map {
diff --git a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
index 66bff73..519ef60 100644
--- a/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskVisualsChangeListener.java
@@ -16,6 +16,7 @@
package com.android.quickstep.util;
+import android.annotation.NonNull;
import android.os.UserHandle;
import com.android.systemui.shared.recents.model.Task;
@@ -36,7 +37,7 @@
/**
* Called when the icon for a task changes
*/
- default void onTaskIconChanged(String pkg, UserHandle user) {}
+ default void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {}
/**
* Called when the icon for a task changes
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 69a9690..255619a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1079,7 +1079,6 @@
@Nullable
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
if (enableRefactorTaskThumbnail()) {
- mHelper.onTaskThumbnailChanged(taskId, thumbnailData);
return null;
}
if (mHandleTaskStackChanges) {
@@ -1100,8 +1099,7 @@
}
@Override
- public void onTaskIconChanged(String pkg, UserHandle user) {
- // TODO(b/342560598): Listen in TaskRepository and reload.
+ public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = requireTaskViewAt(i);
Task task = tv.getFirstTask();
@@ -2549,7 +2547,6 @@
}
if (enableRefactorTaskThumbnail()) {
- // TODO(b/342560598): Listen in TaskRepository and reload.
return;
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index 5b71da1..f5b2176 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -68,8 +68,4 @@
ViewUtils.postFrameDrawn(taskView, onFinishRunnable)
}
}
-
- fun onTaskThumbnailChanged(taskId: Int, thumbnailData: ThumbnailData) {
- recentsViewModel.addOrUpdateThumbnailOverride(mapOf(taskId to thumbnailData))
- }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
new file mode 100644
index 0000000..7d09efd
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.recents.data
+
+import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+
+class FakeHighResLoadingStateNotifier : HighResLoadingStateNotifier {
+ val listeners = mutableListOf<HighResLoadingStateChangedCallback>()
+
+ override fun addCallback(callback: HighResLoadingStateChangedCallback) {
+ listeners.add(callback)
+ }
+
+ override fun removeCallback(callback: HighResLoadingStateChangedCallback) {
+ listeners.remove(callback)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
index fee4979..5de876a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -27,7 +27,8 @@
class FakeTaskIconDataSource : TaskIconDataSource {
- val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mockCopyableDrawable() }
+ val taskIdToDrawable: MutableMap<Int, Drawable> =
+ (0..10).associateWith { mockCopyableDrawable() }.toMutableMap()
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
@@ -52,15 +53,17 @@
return null
}
- private fun mockCopyableDrawable(): Drawable {
- val mutableDrawable = mock<Drawable>()
- val immutableDrawable =
- mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
- val constantState =
- mock<Drawable.ConstantState>().apply {
- whenever(newDrawable()).thenReturn(immutableDrawable)
- }
- return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+ companion object {
+ fun mockCopyableDrawable(): Drawable {
+ val mutableDrawable = mock<Drawable>()
+ val immutableDrawable =
+ mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
+ val constantState =
+ mock<Drawable.ConstantState>().apply {
+ whenever(newDrawable()).thenReturn(immutableDrawable)
+ }
+ return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+ }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
index 30fc491..d12c0b0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -27,7 +27,8 @@
class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
- val taskIdToBitmap: Map<Int, Bitmap> = (0..10).associateWith { mock() }
+ val taskIdToBitmap: MutableMap<Int, Bitmap> =
+ (0..10).associateWith { mock<Bitmap>() }.toMutableMap()
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt
new file mode 100644
index 0000000..765f0d1
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskVisualsChangeNotifier.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.recents.data
+
+import com.android.quickstep.util.TaskVisualsChangeListener
+
+class FakeTaskVisualsChangeNotifier : TaskVisualsChangeNotifier {
+ val listeners = mutableListOf<TaskVisualsChangeListener>()
+
+ override fun addThumbnailChangeListener(listener: TaskVisualsChangeListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeThumbnailChangeListener(listener: TaskVisualsChangeListener) {
+ listeners.remove(listener)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d94a351..7a17872 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -28,7 +28,6 @@
private var taskIconDataMap: Map<Int, TaskIconQueryResponse> = emptyMap()
private var tasks: MutableStateFlow<List<Task>> = MutableStateFlow(emptyList())
private var visibleTasks: MutableStateFlow<List<Int>> = MutableStateFlow(emptyList())
- private var thumbnailOverrideMap: Map<Int, ThumbnailData> = emptyMap()
override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> = tasks
@@ -39,7 +38,7 @@
.map { taskList ->
val task = taskList.firstOrNull { it.key.id == taskId } ?: return@map null
Task(task).apply {
- thumbnail = thumbnailOverrideMap[taskId] ?: task.thumbnail
+ thumbnail = task.thumbnail
icon = task.icon
titleDescription = task.titleDescription
title = task.title
@@ -62,16 +61,6 @@
}
}
}
- setThumbnailOverrideInternal(thumbnailOverrideMap)
- }
-
- override fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
- setThumbnailOverrideInternal(thumbnailOverride)
- }
-
- private fun setThumbnailOverrideInternal(thumbnailOverride: Map<Int, ThumbnailData>) {
- thumbnailOverrideMap =
- thumbnailOverride.filterKeys(this.visibleTasks.value::contains).toMap()
}
fun seedTasks(tasks: List<Task>) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
new file mode 100644
index 0000000..41f6bfd
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
@@ -0,0 +1,191 @@
+/*
+ * 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.recents.data
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
+import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+class TaskVisualsChangedDelegateTest {
+ private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
+ private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
+
+ val systemUnderTest =
+ TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)
+
+ @Test
+ fun addingFirstListener_addsListenerToNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+
+ assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ }
+
+ @Test
+ fun addingAndRemovingListener_removesListenerFromNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+ systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1))
+
+ assertThat(taskVisualsChangeNotifier.listeners).isEmpty()
+ assertThat(highResLoadingStateNotifier.listeners).isEmpty()
+ }
+
+ @Test
+ fun addingTwoAndRemovingOneListener_doesNotRemoveListenerFromNotifiers() {
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), mock())
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 2), mock())
+ systemUnderTest.unregisterTaskThumbnailChangedCallback(createTaskKey(id = 1))
+
+ assertThat(taskVisualsChangeNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ assertThat(highResLoadingStateNotifier.listeners.single()).isEqualTo(systemUnderTest)
+ }
+
+ @Test
+ fun onTaskIconChangedWithTaskId_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskIconChangedCallback>()
+ val additionalListener = mock<TaskIconChangedCallback>()
+ systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 1), expectedListener)
+ systemUnderTest.registerTaskIconChangedCallback(createTaskKey(id = 2), additionalListener)
+
+ systemUnderTest.onTaskIconChanged(1)
+
+ verify(expectedListener).onTaskIconChanged()
+ verifyNoMoreInteractions(additionalListener)
+ }
+
+ @Test
+ fun onTaskIconChangedWithoutTaskId_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskIconChangedCallback>()
+ val listener = mock<TaskIconChangedCallback>()
+ // Correct match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ expectedListener
+ )
+ // 1 out of 2 match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1),
+ listener
+ )
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2),
+ listener
+ )
+ // 0 out of 2 match
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2),
+ listener
+ )
+
+ systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
+
+ verify(expectedListener).onTaskIconChanged()
+ verifyNoMoreInteractions(listener)
+ }
+
+ @Test
+ fun replacedTaskIconChangedCallbacks_notCalled() {
+ val replacedListener = mock<TaskIconChangedCallback>()
+ val newListener = mock<TaskIconChangedCallback>()
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ replacedListener
+ )
+ systemUnderTest.registerTaskIconChangedCallback(
+ createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
+ newListener
+ )
+
+ systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
+
+ verifyNoMoreInteractions(replacedListener)
+ verify(newListener).onTaskIconChanged()
+ }
+
+ @Test
+ fun onTaskThumbnailChanged_notifiesCorrectListenerOnly() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener
+ )
+
+ systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ verify(expectedListener).onTaskThumbnailChanged(expectedThumbnailData)
+ verifyNoMoreInteractions(additionalListener)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_notifiesAllListeners() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener
+ )
+
+ systemUnderTest.onHighResLoadingStateChanged(true)
+
+ verify(expectedListener).onHighResLoadingStateChanged()
+ verify(additionalListener).onHighResLoadingStateChanged()
+ }
+
+ @Test
+ fun replacedTaskThumbnailChangedCallbacks_notCalled() {
+ val replacedListener1 = mock<TaskThumbnailChangedCallback>()
+ val newListener1 = mock<TaskThumbnailChangedCallback>()
+ val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ replacedListener1
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1)
+
+ systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ verifyNoMoreInteractions(replacedListener1)
+ verify(newListener1).onTaskThumbnailChanged(expectedThumbnailData)
+ }
+
+ private fun createTaskKey(id: Int = 1, pkg: String = PACKAGE_NAME, userId: Int = 1) =
+ TaskKey(id, 0, Intent().setPackage(pkg), ComponentName("", ""), userId, 0)
+
+ private companion object {
+ const val PACKAGE_NAME = "com.test.test"
+ const val ALTERNATIVE_PACKAGE_NAME = "com.test.test2"
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index e6534eb..f31467f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,7 +19,7 @@
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
-import android.view.Surface
+import android.graphics.drawable.Drawable
import com.android.launcher3.util.TestDispatcherProvider
import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.quickstep.util.DesktopTask
@@ -29,6 +29,9 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -48,6 +51,10 @@
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
private val taskIconDataSource = FakeTaskIconDataSource()
+ private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
+ private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
+ private val taskVisualsChangedDelegate =
+ TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)
private val dispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -56,6 +63,7 @@
recentsModel,
taskThumbnailDataSource,
taskIconDataSource,
+ taskVisualsChangedDelegate,
testScope.backgroundScope,
TestDispatcherProvider(dispatcher)
)
@@ -203,87 +211,81 @@
}
@Test
- fun addThumbnailOverrideOverrideThumbnails() =
+ fun onTaskThumbnailChanged_setsNewThumbnailDataOnTask() =
testScope.runTest {
recentsModel.seedTasks(defaultTaskList)
- val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
- val thumbnailOverride2 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride2.thumbnail)
- }
-
- @Test
- fun addThumbnailOverrideMultipleOverrides() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val thumbnailOverride1 = createThumbnailData()
- val thumbnailOverride2 = createThumbnailData()
- val thumbnailOverride3 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(1 to thumbnailOverride1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride3))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride1.thumbnail)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride3.thumbnail)
- }
-
- @Test
- fun addThumbnailOverrideClearedWhenTaskBecomeInvisible() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
- val thumbnailOverride1 = createThumbnailData()
- val thumbnailOverride2 = createThumbnailData()
- systemUnderTest.getAllTaskData(forceRefresh = true)
-
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(1 to thumbnailOverride1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride2))
- // Making task 2 invisible and visible again should clear the override
- systemUnderTest.setVisibleTasks(listOf(1))
- systemUnderTest.setVisibleTasks(listOf(1, 2))
-
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail)
- .isEqualTo(thumbnailOverride1.thumbnail)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail).isEqualTo(bitmap2)
- }
-
- @Test
- fun addThumbnailOverrideDoesNotOverrideInvisibleTasks() =
- testScope.runTest {
- recentsModel.seedTasks(defaultTaskList)
- val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
- val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
- val thumbnailOverride = createThumbnailData()
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1))
- systemUnderTest.addOrUpdateThumbnailOverride(mapOf(2 to thumbnailOverride))
- systemUnderTest.setVisibleTasks(listOf(1, 2))
- assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
- assertThat(systemUnderTest.getThumbnailById(2).first()!!.thumbnail).isEqualTo(bitmap2)
+ val expectedThumbnailData = createThumbnailData()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
+ }
+ taskVisualsChangedDelegate.onTaskThumbnailChanged(1, expectedThumbnailData)
+
+ assertThat(task1ThumbnailValues[1]!!.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(task1ThumbnailValues.last()).isEqualTo(expectedThumbnailData)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_setsNewThumbnailDataOnTask() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1))
+
+ val expectedBitmap = mock<Bitmap>()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<Bitmap?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail?.thumbnail }.toList(task1ThumbnailValues)
+ }
+ taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
+
+ assertThat(task1ThumbnailValues[1]).isEqualTo(expectedPreviousBitmap)
+ assertThat(task1ThumbnailValues.last()).isEqualTo(expectedBitmap)
+ }
+
+ @Test
+ fun onTaskIconChanged_setsNewIconOnTask() =
+ testScope.runTest {
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(listOf(1))
+
+ val expectedIcon = FakeTaskIconDataSource.mockCopyableDrawable()
+ val expectedPreviousIcon = taskIconDataSource.taskIdToDrawable[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1IconValues = mutableListOf<Drawable?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.icon }.toList(task1IconValues)
+ }
+ taskIconDataSource.taskIdToDrawable[1] = expectedIcon
+ taskVisualsChangedDelegate.onTaskIconChanged(1)
+
+ assertThat(task1IconValues[1]).isEqualTo(expectedPreviousIcon)
+ assertThat(task1IconValues.last()).isEqualTo(expectedIcon)
}
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000))
- private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
+ private fun createThumbnailData(): ThumbnailData {
val bitmap = mock<Bitmap>()
whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
- return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+ return ThumbnailData(thumbnail = bitmap)
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
index dc16475..fe67313 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
@@ -70,44 +70,6 @@
assertThat(thumbnailDataFlow2.first()).isNull()
}
- @Test
- fun thumbnailOverrideWaitAndReset() = runTest {
- val thumbnailData1 = createThumbnailData().apply { snapshotId = 1 }
- val thumbnailData2 = createThumbnailData().apply { snapshotId = 2 }
- tasksRepository.seedTasks(tasks)
- tasksRepository.seedThumbnailData(mapOf(1 to thumbnailData1, 2 to thumbnailData2))
-
- val thumbnailDataFlow1 = tasksRepository.getThumbnailById(1)
- val thumbnailDataFlow2 = tasksRepository.getThumbnailById(2)
-
- systemUnderTest.refreshAllTaskData()
- systemUnderTest.updateVisibleTasks(listOf(1, 2))
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
-
- systemUnderTest.setRunningTaskShowScreenshot(true)
- val thumbnailOverride = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
- systemUnderTest.addOrUpdateThumbnailOverride(thumbnailOverride)
-
- systemUnderTest.waitForRunningTaskShowScreenshotToUpdate()
- val expectedUpdate = mapOf(2 to createThumbnailData().apply { snapshotId = 3 })
- systemUnderTest.waitForThumbnailsToUpdate(expectedUpdate)
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()?.snapshotId).isEqualTo(3)
-
- systemUnderTest.onReset()
-
- assertThat(thumbnailDataFlow1.first()).isNull()
- assertThat(thumbnailDataFlow2.first()).isNull()
-
- systemUnderTest.updateVisibleTasks(listOf(1, 2))
-
- assertThat(thumbnailDataFlow1.first()).isEqualTo(thumbnailData1)
- assertThat(thumbnailDataFlow2.first()).isEqualTo(thumbnailData2)
- }
-
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
colorBackground = Color.argb(taskId, taskId, taskId, taskId)