diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt new file mode 100644 index 0000000000..c1eef0b393 --- /dev/null +++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt @@ -0,0 +1,37 @@ +/* + * 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.systemui.shared.recents.model.Task +import kotlinx.coroutines.flow.Flow + +interface RecentTasksRepository { + /** Gets all the recent tasks, refreshing from data sources if [forceRefresh] is true. */ + fun getAllTaskData(forceRefresh: Boolean = false): Flow> + + /** + * Gets the data associated with a task that has id [taskId]. Flow will settle on null if the + * task was not found. + */ + fun getTaskDataById(taskId: Int): Flow + + /** + * Sets the tasks that are visible, indicating that properties relating to visuals need to be + * populated e.g. icons/thumbnails etc. + */ + fun setVisibleTasks(visibleTaskIdList: List) +} diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt index ad8ae207d1..b21a1b4add 100644 --- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt +++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt @@ -38,7 +38,7 @@ class TasksRepository( private val recentsModel: RecentTasksDataSource, private val taskThumbnailDataSource: TaskThumbnailDataSource, private val taskIconCache: TaskIconCache, -) { +) : RecentTasksRepository { private val groupedTaskData = MutableStateFlow(emptyList()) private val _taskData = groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } } @@ -53,17 +53,17 @@ class TasksRepository( tasks } - fun getAllTaskData(forceRefresh: Boolean = false): Flow> { + override fun getAllTaskData(forceRefresh: Boolean): Flow> { if (forceRefresh) { recentsModel.getTasks { groupedTaskData.value = it } } return taskData } - fun getTaskDataById(taskId: Int): Flow = + override fun getTaskDataById(taskId: Int): Flow = taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } } - fun setVisibleTasks(visibleTaskIdList: List) { + override fun setVisibleTasks(visibleTaskIdList: List) { this.visibleTaskIds.value = visibleTaskIdList.toSet() } diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt index 0843ae3d33..40f9b28eec 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt @@ -16,11 +16,19 @@ package com.android.quickstep.task.thumbnail -import com.android.systemui.shared.recents.model.Task +import android.graphics.Bitmap +import android.graphics.Rect +import androidx.annotation.ColorInt sealed class TaskThumbnailUiState { data object Uninitialized : TaskThumbnailUiState() data object LiveTile : TaskThumbnailUiState() + data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState() + data class Snapshot( + val bitmap: Bitmap, + val drawnRect: Rect, + @ColorInt val backgroundColor: Int + ) : TaskThumbnailUiState() } -data class TaskThumbnail(val task: Task, val isRunning: Boolean) +data class TaskThumbnail(val taskId: Int, val isRunning: Boolean) diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt index 876297664c..2836c892c6 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt @@ -19,15 +19,20 @@ package com.android.quickstep.task.thumbnail import android.content.Context import android.content.res.Configuration import android.graphics.Canvas +import android.graphics.Color import android.graphics.Outline import android.graphics.Paint import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode +import android.graphics.Rect import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider +import androidx.annotation.ColorInt import com.android.launcher3.Utilities +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized import com.android.quickstep.util.TaskCornerRadius import com.android.quickstep.views.RecentsView @@ -42,17 +47,26 @@ class TaskThumbnailView : View { // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. val viewModel by lazy { - TaskThumbnailViewModel( + val recentsView = RecentsViewContainer.containerFromContext(context) .getOverviewPanel>() - .mRecentsViewData, - (parent as TaskView).taskViewData + TaskThumbnailViewModel( + recentsView.mRecentsViewData, + (parent as TaskView).taskViewData, + recentsView.mTasksRepository, ) } private var uiState: TaskThumbnailUiState = Uninitialized private var inheritedScale: Float = 1f + private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val _measuredBounds = Rect() + private val measuredBounds: Rect + get() { + _measuredBounds.set(0, 0, measuredWidth, measuredHeight) + return _measuredBounds + } private var cornerRadius: Float = TaskCornerRadius.get(context) private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context) @@ -85,24 +99,25 @@ class TaskThumbnailView : View { outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.measuredWidth, - view.measuredHeight, - getCurrentCornerRadius() - ) + outline.setRoundRect(measuredBounds, getCurrentCornerRadius()) } } } override fun onDraw(canvas: Canvas) { - when (uiState) { - is Uninitialized -> {} + when (val uiStateVal = uiState) { + is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK) is LiveTile -> drawTransparentUiState(canvas) + is Snapshot -> drawSnapshotState(canvas, uiStateVal) + is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor) } } + private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) { + backgroundPaint.color = backgroundColor + canvas.drawRect(measuredBounds, backgroundPaint) + } + override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) @@ -112,7 +127,12 @@ class TaskThumbnailView : View { } private fun drawTransparentUiState(canvas: Canvas) { - canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT) + canvas.drawRect(measuredBounds, CLEAR_PAINT) + } + + private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) { + drawBackgroundOnly(canvas, snapshot.backgroundColor) + canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null) } private fun getCurrentCornerRadius() = diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt index 71bc865f85..4511ea714c 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt @@ -16,32 +16,76 @@ package com.android.quickstep.task.thumbnail +import android.annotation.ColorInt +import android.graphics.Rect +import androidx.core.graphics.ColorUtils +import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized import com.android.quickstep.task.viewmodel.TaskViewData +import com.android.systemui.shared.recents.model.Task +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -class TaskThumbnailViewModel(recentsViewData: RecentsViewData, taskViewData: TaskViewData) { - private val task = MutableStateFlow(null) +@OptIn(ExperimentalCoroutinesApi::class) +class TaskThumbnailViewModel( + recentsViewData: RecentsViewData, + taskViewData: TaskViewData, + private val tasksRepository: RecentTasksRepository, +) { + private val task = MutableStateFlow>(flowOf(null)) + private var boundTaskIsRunning = false val recentsFullscreenProgress = recentsViewData.fullscreenProgress val inheritedScale = combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale -> recentsScale * taskScale } - val uiState = - task.map { taskVal -> - when { - taskVal == null -> Uninitialized - taskVal.isRunning -> LiveTile - else -> Uninitialized + val uiState: Flow = + task + .flatMapLatest { taskFlow -> + taskFlow.map { taskVal -> + when { + taskVal == null -> Uninitialized + boundTaskIsRunning -> LiveTile + isBackgroundOnly(taskVal) -> + BackgroundOnly(taskVal.colorBackground.removeAlpha()) + isSnapshotState(taskVal) -> { + val bitmap = taskVal.thumbnail?.thumbnail!! + Snapshot( + bitmap, + Rect(0, 0, bitmap.width, bitmap.height), + taskVal.colorBackground.removeAlpha() + ) + } + else -> Uninitialized + } + } } - } + .distinctUntilChanged() - fun bind(task: TaskThumbnail) { - this.task.value = task + fun bind(taskThumbnail: TaskThumbnail) { + boundTaskIsRunning = taskThumbnail.isRunning + task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId) } + + private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null + + private fun isSnapshotState(task: Task): Boolean { + val thumbnailPresent = task.thumbnail?.thumbnail != null + val taskLocked = task.isLocked + + return thumbnailPresent && !taskLocked + } + + @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff) } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 5eee64d843..4804e56844 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -188,6 +188,7 @@ import com.android.quickstep.TaskViewUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.ViewUtils; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.recents.data.TasksRepository; import com.android.quickstep.recents.viewmodel.RecentsViewData; import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.quickstep.util.ActiveGestureLog; @@ -305,6 +306,7 @@ public abstract class RecentsView mSizeStrategy; @@ -800,6 +803,12 @@ public abstract class RecentsView visibleTaskIds = new ArrayList<>(); + // Update the task data for the in/visible children for (int i = 0; i < getTaskViewCount(); i++) { TaskView taskView = requireTaskViewAt(i); @@ -2381,6 +2391,10 @@ public abstract class RecentsView tasksToUpdate = containers.stream() .map(TaskContainer::getTask) .collect(Collectors.toCollection(ArrayList::new)); + if (enableRefactorTaskThumbnail()) { + visibleTaskIds.addAll( + tasksToUpdate.stream().map((task) -> task.key.id).toList()); + } if (mTmpRunningTasks != null) { for (Task t : mTmpRunningTasks) { // Skip loading if this is the task that we are animating into @@ -2416,6 +2430,9 @@ public abstract class RecentsView - it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData) - } - ?.also { request -> pendingThumbnailLoadRequests.add(request) } - } else { - it.thumbnailViewDeprecated.setThumbnail(null, null) - // Reset the task thumbnail reference as well (it will be fetched from the - // cache or reloaded next time we need it) - it.task.thumbnail = null - } + if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL) && !enableRefactorTaskThumbnail()) { + taskContainers.forEach { + if (visible) { + recentsModel.thumbnailCache + .updateThumbnailInBackground(it.task) { thumbnailData -> + it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData) + } + ?.also { request -> pendingThumbnailLoadRequests.add(request) } + } else { + it.thumbnailViewDeprecated.setThumbnail(null, null) + // Reset the task thumbnail reference as well (it will be fetched from the + // cache or reloaded next time we need it) + it.task.thumbnail = null } } } @@ -861,7 +859,7 @@ constructor( open fun refreshThumbnails(thumbnailDatas: HashMap?) { if (enableRefactorTaskThumbnail()) { - // TODO(b/334825222) add thumbnail logic + // TODO(b/342560598) add thumbnail logic return } @@ -1591,7 +1589,9 @@ constructor( // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView fun bindThumbnailView() { - thumbnailView?.viewModel?.bind(TaskThumbnail(task, isRunningTask)) + // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but + // this should be decided inside TaskThumbnailViewModel. + thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, isRunningTask)) } } 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 new file mode 100644 index 0000000000..e160627d75 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt @@ -0,0 +1,47 @@ +/* + * 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.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map + +class FakeTasksRepository : RecentTasksRepository { + private var thumbnailDataMap: Map = emptyMap() + private var tasks: MutableStateFlow> = MutableStateFlow(emptyList()) + private var visibleTasks: MutableStateFlow> = MutableStateFlow(emptyList()) + + override fun getAllTaskData(forceRefresh: Boolean): Flow> = tasks + + override fun getTaskDataById(taskId: Int): Flow = + getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } } + + override fun setVisibleTasks(visibleTaskIdList: List) { + visibleTasks.value = visibleTaskIdList + tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } } + } + + fun seedTasks(tasks: List) { + this.tasks.value = tasks + } + + fun seedThumbnailData(thumbnailDataMap: Map) { + this.thumbnailDataMap = thumbnailDataMap + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt index efd7becf79..3b8754c200 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt @@ -16,33 +16,51 @@ package com.android.quickstep.task.thumbnail +import android.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.Rect import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.quickstep.recents.data.FakeTasksRepository import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized import com.android.quickstep.task.viewmodel.TaskViewData import com.android.systemui.shared.recents.model.Task +import com.android.systemui.shared.recents.model.ThumbnailData import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class TaskThumbnailViewModelTest { private val recentsViewData = RecentsViewData() private val taskViewData = TaskViewData() - private val systemUnderTest = TaskThumbnailViewModel(recentsViewData, taskViewData) + private val tasksRepository = FakeTasksRepository() + private val systemUnderTest = + TaskThumbnailViewModel(recentsViewData, taskViewData, tasksRepository) + + private val tasks = (0..5).map(::createTaskWithId) @Test fun initialStateIsUninitialized() = runTest { - assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.Uninitialized) + assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized) } @Test fun bindRunningTask_thenStateIs_LiveTile() = runTest { - val taskThumbnail = TaskThumbnail(Task(), isRunning = true) + tasksRepository.seedTasks(tasks) + val taskThumbnail = TaskThumbnail(taskId = 1, isRunning = true) systemUnderTest.bind(taskThumbnail) - assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.LiveTile) + assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile) } @Test @@ -65,15 +83,96 @@ class TaskThumbnailViewModelTest { } @Test - fun bindRunningTaskThenStoppedTask_thenStateIs_Uninitialized() = runTest { - // TODO(b/334825222): Change the expectation here when snapshot state is implemented - val task = Task() - val runningTask = TaskThumbnail(task, isRunning = true) - val stoppedTask = TaskThumbnail(task, isRunning = false) - systemUnderTest.bind(runningTask) - assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.LiveTile) + fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() = + runTest { + tasksRepository.seedTasks(tasks) + val runningTask = TaskThumbnail(taskId = 1, isRunning = true) + val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false) + systemUnderTest.bind(runningTask) + assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile) + + systemUnderTest.bind(stoppedTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() = runTest { + tasksRepository.seedTasks(tasks) + val stoppedTask = TaskThumbnail(taskId = 2, isRunning = false) systemUnderTest.bind(stoppedTask) - assertThat(systemUnderTest.uiState.first()).isEqualTo(TaskThumbnailUiState.Uninitialized) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() = runTest { + tasksRepository.seedThumbnailData(mapOf(2 to createThumbnailData())) + tasks[2].isLocked = true + tasksRepository.seedTasks(tasks) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + } + + @Test + fun bindStoppedTaskWithThumbnail_thenStateIs_Snapshot_withAlphaRemoved() = runTest { + val expectedThumbnailData = createThumbnailData() + tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData)) + tasksRepository.seedTasks(tasks) + tasksRepository.setVisibleTasks(listOf(2)) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo( + Snapshot( + backgroundColor = Color.rgb(2, 2, 2), + bitmap = expectedThumbnailData.thumbnail!!, + drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + ) + ) + } + + @Test + fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshot() = runTest { + val expectedThumbnailData = createThumbnailData() + tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData)) + tasksRepository.seedTasks(tasks) + val recentTask = TaskThumbnail(taskId = 2, isRunning = false) + + systemUnderTest.bind(recentTask) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2))) + tasksRepository.setVisibleTasks(listOf(2)) + assertThat(systemUnderTest.uiState.first()) + .isEqualTo( + Snapshot( + backgroundColor = Color.rgb(2, 2, 2), + bitmap = expectedThumbnailData.thumbnail!!, + drawnRect = Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT) + ) + ) + } + + private fun createTaskWithId(taskId: Int) = + Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { + colorBackground = Color.argb(taskId, taskId, taskId, taskId) + } + + private fun createThumbnailData(): ThumbnailData { + val bitmap = mock() + whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH) + whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT) + + return ThumbnailData(thumbnail = bitmap) + } + + companion object { + const val THUMBNAIL_WIDTH = 100 + const val THUMBNAIL_HEIGHT = 200 } }