Merge "Make TasksRepository listen to changes in task visuals and update" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
f73bca7a2c
@@ -43,6 +43,7 @@ import com.android.launcher3.util.Executors.SimpleThreadFactory;
|
||||
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 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
@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 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
|
||||
/**
|
||||
* Adds a listener for visuals changes
|
||||
*/
|
||||
@Override
|
||||
public void addThumbnailChangeListener(TaskVisualsChangeListener listener) {
|
||||
mThumbnailChangeListeners.add(listener);
|
||||
}
|
||||
@@ -294,6 +297,7 @@ public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
|
||||
/**
|
||||
* Removes a previously added listener
|
||||
*/
|
||||
@Override
|
||||
public void removeThumbnailChangeListener(TaskVisualsChangeListener listener) {
|
||||
mThumbnailChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
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 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
|
||||
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 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -41,10 +41,4 @@ interface RecentTasksRepository {
|
||||
* 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>)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package com.android.quickstep.recents.data
|
||||
|
||||
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 com.android.systemui.shared.recents.model.ThumbnailData
|
||||
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 @@ class TasksRepository(
|
||||
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 @@ class TasksRepository(
|
||||
.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 @@ class TasksRepository(
|
||||
|
||||
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(
|
||||
|
||||
@@ -22,6 +22,8 @@ import android.view.View
|
||||
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 @@ class RecentsDependencies private constructor(private val appContext: Context) {
|
||||
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 @@ class RecentsDependencies private constructor(private val appContext: Context) {
|
||||
thumbnailCache,
|
||||
iconCache,
|
||||
get(),
|
||||
get(),
|
||||
ProductionDispatchers
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,10 +58,6 @@ class RecentsViewModel(
|
||||
recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
|
||||
}
|
||||
|
||||
fun addOrUpdateThumbnailOverride(thumbnailOverride: Map<Int, ThumbnailData>) {
|
||||
recentsTasksRepository.addOrUpdateThumbnailOverride(thumbnailOverride)
|
||||
}
|
||||
|
||||
suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>) {
|
||||
combine(
|
||||
updatedThumbnails.map {
|
||||
|
||||
@@ -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 @@ public interface TaskVisualsChangeListener {
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -1079,7 +1079,6 @@ public abstract class RecentsView<
|
||||
@Nullable
|
||||
public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
|
||||
if (enableRefactorTaskThumbnail()) {
|
||||
mHelper.onTaskThumbnailChanged(taskId, thumbnailData);
|
||||
return null;
|
||||
}
|
||||
if (mHandleTaskStackChanges) {
|
||||
@@ -1100,8 +1099,7 @@ public abstract class RecentsView<
|
||||
}
|
||||
|
||||
@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 @@ public abstract class RecentsView<
|
||||
}
|
||||
|
||||
if (enableRefactorTaskThumbnail()) {
|
||||
// TODO(b/342560598): Listen in TaskRepository and reload.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,4 @@ class RecentsViewModelHelper(private val recentsViewModel: RecentsViewModel) {
|
||||
ViewUtils.postFrameDrawn(taskView, onFinishRunnable)
|
||||
}
|
||||
}
|
||||
|
||||
fun onTaskThumbnailChanged(taskId: Int, thumbnailData: ThumbnailData) {
|
||||
recentsViewModel.addOrUpdateThumbnailOverride(mapOf(taskId to thumbnailData))
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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)
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -27,7 +27,8 @@ import org.mockito.kotlin.whenever
|
||||
|
||||
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 @@ class FakeTaskIconDataSource : TaskIconDataSource {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@ import org.mockito.kotlin.whenever
|
||||
|
||||
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
|
||||
|
||||
|
||||
+31
@@ -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)
|
||||
}
|
||||
}
|
||||
+1
-12
@@ -28,7 +28,6 @@ class FakeTasksRepository : RecentTasksRepository {
|
||||
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 @@ class FakeTasksRepository : RecentTasksRepository {
|
||||
.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 @@ class FakeTasksRepository : RecentTasksRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
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>) {
|
||||
|
||||
+191
@@ -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"
|
||||
}
|
||||
}
|
||||
+70
-68
@@ -19,7 +19,7 @@ package com.android.quickstep.recents.data
|
||||
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.android.systemui.shared.recents.model.ThumbnailData
|
||||
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 @@ class TasksRepositoryTest {
|
||||
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 @@ class TasksRepositoryTest {
|
||||
recentsModel,
|
||||
taskThumbnailDataSource,
|
||||
taskIconDataSource,
|
||||
taskVisualsChangedDelegate,
|
||||
testScope.backgroundScope,
|
||||
TestDispatcherProvider(dispatcher)
|
||||
)
|
||||
@@ -203,87 +211,81 @@ class TasksRepositoryTest {
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
-38
@@ -70,44 +70,6 @@ class RecentsViewModelTest {
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user