Move overlay setup functionality to TaskView/Container

Bug: 395294954
Bug: 396064850
Flag: com.android.launcher3.enable_refactor_task_thumbnail
Test: Perfetto trace comparison
Test: OverviewDesktopTaskImageTest
Change-Id: I69ac43350b261532a47f7989848a650ae8a70e9a
This commit is contained in:
Uwais Ashraf
2025-03-08 13:49:23 +00:00
parent a5c9d86e1d
commit 33c6559d41
9 changed files with 173 additions and 288 deletions
@@ -42,8 +42,7 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.Snackbar;
import com.android.quickstep.task.thumbnail.TaskOverlayUiState;
import com.android.quickstep.task.util.TaskOverlayHelper;
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.GroupedTaskView;
@@ -53,6 +52,7 @@ import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
import java.util.List;
@@ -135,54 +135,37 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
private T mActionsView;
protected ImageActionsApi mImageApi;
protected TaskOverlayHelper mHelper;
private ThumbnailData mThumbnailData = null;
protected TaskOverlay(TaskContainer taskContainer) {
mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
mTaskContainer = taskContainer;
if (enableRefactorTaskThumbnail()) {
mHelper = new TaskOverlayHelper(mTaskContainer.getTask(), this);
}
mImageApi = new ImageActionsApi(mApplicationContext, this::getThumbnail);
}
/**
* Initialize the overlay when a Task is bound to the TaskView.
*/
public void init() {
if (enableRefactorTaskThumbnail()) {
mHelper.init();
}
}
/**
* Destroy the overlay when the TaskView is recycled.
*/
public void destroy() {
if (enableRefactorTaskThumbnail()) {
mHelper.destroy();
}
public void setThumbnailState(@Nullable ThumbnailData thumbnailData) {
mThumbnailData = thumbnailData;
}
protected @Nullable Bitmap getThumbnail() {
return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnail()
: mTaskContainer.getThumbnailViewDeprecated().getThumbnail();
if (enableRefactorTaskThumbnail()) {
return mThumbnailData == null ? null : mThumbnailData.getThumbnail();
} else {
return mTaskContainer.getThumbnailViewDeprecated().getThumbnail();
}
}
/**
* Returns whether the snapshot is real. If the device is locked for the user of the task,
* the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
*/
public boolean isRealSnapshot() {
protected boolean isRealSnapshot() {
if (enableRefactorTaskThumbnail()) {
if (mHelper.getUiState() instanceof TaskOverlayUiState.Enabled) {
return mHelper.getEnabledState().isRealSnapshot();
} else {
return false;
}
}
if (mThumbnailData == null) return false;
return mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
return mThumbnailData.isRealSnapshot && !mTaskContainer.getTask().isLocked;
} else {
return mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
}
}
/**
@@ -190,7 +173,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
*/
public boolean isThumbnailRotationDifferentFromTask() {
if (enableRefactorTaskThumbnail()) {
return mHelper.getThumbnailPositionState().isRotated();
ThumbnailPosition thumbnailPosition = mTaskContainer.getThumbnailPosition();
return thumbnailPosition != null && thumbnailPosition.isRotated();
}
return mTaskContainer.getThumbnailViewDeprecated()
@@ -340,9 +324,16 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
// inverse tells us where the view would be in the bitmaps coordinates. The insets are
// the difference between the bitmap bounds and the projected view bounds.
Matrix boundsToBitmapSpace = new Matrix();
Matrix thumbnailMatrix = enableRefactorTaskThumbnail()
? mHelper.getThumbnailMatrix()
: mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
Matrix thumbnailMatrix;
if (enableRefactorTaskThumbnail()) {
if (mTaskContainer.getThumbnailPosition() != null) {
thumbnailMatrix = mTaskContainer.getThumbnailPosition().getMatrix();
} else {
thumbnailMatrix = Matrix.IDENTITY_MATRIX;
}
} else {
thumbnailMatrix = mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
}
thumbnailMatrix.invert(boundsToBitmapSpace);
RectF boundsInBitmapSpace = new RectF();
boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
@@ -36,6 +36,7 @@ data class TaskTileUiState(
val isLiveTile: Boolean,
val hasHeader: Boolean,
val sysUiStatusNavFlags: Int,
val taskOverlayEnabled: Boolean,
)
sealed class TaskData {
@@ -29,6 +29,7 @@ import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.views.TaskViewType
import com.android.quickstep.views.TaskViewType.SINGLE
import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -74,8 +75,18 @@ class TaskViewModel(
)
}
private val overlayEnabled =
combine(recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds) {
isOverlayEnabled,
settledFullyVisibleTaskIds ->
taskViewType == SINGLE &&
isOverlayEnabled &&
settledFullyVisibleTaskIds.any { it in taskIds.value }
}
.distinctUntilChanged()
val state: Flow<TaskTileUiState> =
combine(taskData, isLiveTile) { tasks, isLiveTile -> mapToTaskTile(tasks, isLiveTile) }
combine(taskData, isLiveTile, overlayEnabled, ::mapToTaskTile)
.distinctUntilChanged()
.flowOn(dispatcherProvider.background)
@@ -99,13 +110,18 @@ class TaskViewModel(
isRtl = isRtl,
)
private fun mapToTaskTile(tasks: List<TaskData>, isLiveTile: Boolean): TaskTileUiState {
private fun mapToTaskTile(
tasks: List<TaskData>,
isLiveTile: Boolean,
overlayEnabled: Boolean,
): TaskTileUiState {
val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
return TaskTileUiState(
tasks = tasks,
isLiveTile = isLiveTile,
hasHeader = taskViewType == TaskViewType.DESKTOP,
sysUiStatusNavFlags = getSysUiStatusNavFlagsUseCase(firstThumbnailData),
taskOverlayEnabled = overlayEnabled,
)
}
@@ -1,26 +0,0 @@
/*
* 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.task.thumbnail
import android.graphics.Bitmap
/** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */
sealed class TaskOverlayUiState {
data object Disabled : TaskOverlayUiState()
data class Enabled(val isRealSnapshot: Boolean, val thumbnail: Bitmap?) : TaskOverlayUiState()
}
@@ -1,128 +0,0 @@
/*
* 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.task.util
import android.util.Log
import android.view.View.OnLayoutChangeListener
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.recents.di.get
import com.android.quickstep.task.thumbnail.TaskOverlayUiState
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
* Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
* should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
*/
class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
private val scope = overlay.taskView.context
private val recentsCoroutineScope: CoroutineScope = RecentsDependencies.get(scope)
private val dispatcherProvider: DispatcherProvider = RecentsDependencies.get(scope)
private lateinit var overlayInitializedScope: CoroutineScope
var uiState: TaskOverlayUiState = Disabled
private lateinit var viewModel: TaskOverlayViewModel
// TODO(b/331753115): TaskOverlay should listen for state changes and react.
val enabledState: Enabled
get() = uiState as Enabled
private val snapshotLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
(uiState as? Enabled)?.let { initOverlay(it) }
}
fun getThumbnailMatrix() = getThumbnailPositionState().matrix
fun getThumbnailPositionState() =
viewModel.getThumbnailPositionState(
overlay.snapshotView.width,
overlay.snapshotView.height,
overlay.snapshotView.isLayoutRtl,
)
fun init() {
overlayInitializedScope =
CoroutineScope(
SupervisorJob() + Dispatchers.Main.immediate + CoroutineName("TaskOverlayHelper")
)
viewModel =
TaskOverlayViewModel(
task = task,
recentsViewData = RecentsDependencies.get(scope),
getThumbnailPositionUseCase = RecentsDependencies.get(scope),
recentTasksRepository = RecentsDependencies.get(scope),
dispatcherProvider = RecentsDependencies.get(scope),
)
viewModel.overlayState
.dropWhile { it == Disabled }
.flowOn(dispatcherProvider.background)
.onEach {
uiState = it
if (it is Enabled) {
initOverlay(it)
} else {
reset()
}
}
.launchIn(overlayInitializedScope)
overlay.snapshotView.addOnLayoutChangeListener(snapshotLayoutChangeListener)
}
private fun initOverlay(enabledState: Enabled) {
if (DEBUG) {
Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${enabledState.thumbnail}")
}
with(getThumbnailPositionState()) {
overlay.initOverlay(task, enabledState.thumbnail, matrix, isRotated)
}
}
private fun reset() {
if (DEBUG) {
Log.d(TAG, "reset - taskId: ${task.key.id}")
}
overlay.reset()
}
fun destroy() {
recentsCoroutineScope.launch(dispatcherProvider.background) {
overlayInitializedScope.cancel("TaskOverlay being destroyed")
}
uiState = Disabled
overlay.snapshotView.removeOnLayoutChangeListener(snapshotLayoutChangeListener)
reset()
}
companion object {
private const val TAG = "TaskOverlayHelper"
private const val DEBUG = false
}
}
@@ -1,75 +0,0 @@
/*
* 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.task.viewmodel
import android.graphics.Matrix
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.data.RecentTasksRepository
import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
/** View model for TaskOverlay */
class TaskOverlayViewModel(
private val task: Task,
recentsViewData: RecentsViewData,
private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
private val recentTasksRepository: RecentTasksRepository,
dispatcherProvider: DispatcherProvider,
) {
val overlayState =
combine(
recentsViewData.overlayEnabled,
recentsViewData.settledFullyVisibleTaskIds
.map { it.contains(task.key.id) }
.distinctUntilChanged(),
recentTasksRepository.getThumbnailById(task.key.id),
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
if (isOverlayEnabled && isFullyVisible) {
Enabled(
isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked,
thumbnailData?.thumbnail,
)
} else {
Disabled
}
}
.distinctUntilChanged()
.flowOn(dispatcherProvider.background)
fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
val thumbnailPositionState =
getThumbnailPositionUseCase(
thumbnailData = recentTasksRepository.getCurrentThumbnailById(task.key.id),
width = width,
height = height,
isRtl = isRtl,
)
return ThumbnailPositionState(
thumbnailPositionState.matrix,
thumbnailPositionState.isRotated,
)
}
data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
}
@@ -18,6 +18,7 @@ package com.android.quickstep.views
import android.graphics.Bitmap
import android.graphics.Matrix
import android.util.Log
import android.view.View
import android.view.View.OnClickListener
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
@@ -26,6 +27,7 @@ import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.ViewUtils.addAccessibleChildToList
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.task.thumbnail.TaskContentView
@@ -54,6 +56,8 @@ class TaskContainer(
taskOverlayFactory: TaskOverlayFactory,
) {
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
var thumbnailPosition: ThumbnailPosition? = null
private var overlayEnabledStatus = false
init {
if (enableRefactorTaskThumbnail()) {
@@ -101,14 +105,13 @@ class TaskContainer(
if (!enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.bind(task, overlay, taskView)
}
overlay.init()
}
fun destroy() {
digitalWellBeingToast?.destroy()
taskContentView.scaleX = 1f
taskContentView.scaleY = 1f
overlay.destroy()
overlay.reset()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
thumbnailData = null
@@ -124,6 +127,34 @@ class TaskContainer(
}
}
fun setOverlayEnabled(enabled: Boolean, thumbnailPosition: ThumbnailPosition?) {
if (enableRefactorTaskThumbnail()) {
if (overlayEnabledStatus != enabled || this.thumbnailPosition != thumbnailPosition) {
overlayEnabledStatus = enabled
refreshOverlay(thumbnailPosition)
}
}
}
fun refreshOverlay(thumbnailPosition: ThumbnailPosition?) {
this.thumbnailPosition = thumbnailPosition
when {
!overlayEnabledStatus -> overlay.reset()
thumbnailPosition == null -> {
Log.e(TAG, "Thumbnail position was null during overlay refresh", Exception())
overlay.reset()
}
else ->
overlay.initOverlay(
task,
thumbnailData?.thumbnail,
thumbnailPosition.matrix,
thumbnailPosition.isRotated,
)
}
}
fun addChildForAccessibility(outChildren: ArrayList<View>) {
addAccessibleChildToList(iconView.asView(), outChildren)
addAccessibleChildToList(snapshotView, outChildren)
@@ -144,6 +175,7 @@ class TaskContainer(
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
overlay.setThumbnailState(thumbnailData)
}
fun updateTintAmount(tintAmount: Float) {
@@ -184,4 +216,8 @@ class TaskContainer(
fun updateThumbnailMatrix(matrix: Matrix) {
thumbnailView.setImageMatrix(matrix)
}
companion object {
const val TAG = "TaskContainer"
}
}
@@ -82,6 +82,7 @@ import com.android.quickstep.orientation.RecentsPagedOrientationHandler
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.di.inject
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
@@ -775,11 +776,13 @@ constructor(
},
)
updateThumbnailValidity(container)
updateThumbnailMatrix(
container = container,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
)
val thumbnailPosition =
updateThumbnailMatrix(
container = container,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
)
container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition)
if (enableOverviewIconMenu()) {
setIconState(container, containerState)
@@ -808,11 +811,16 @@ constructor(
* @param width The desired width of the thumbnail's container.
* @param height The desired height of the thumbnail's container.
*/
private fun updateThumbnailMatrix(container: TaskContainer, width: Int, height: Int) {
private fun updateThumbnailMatrix(
container: TaskContainer,
width: Int,
height: Int,
): ThumbnailPosition? {
val thumbnailPosition =
viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
?: return
?: return null
container.updateThumbnailMatrix(thumbnailPosition.matrix)
return thumbnailPosition
}
override fun onDetachedFromWindow() {
@@ -877,7 +885,8 @@ constructor(
thumbnailFullscreenParams.currentCornerRadius
container.taskContentView.doOnSizeChange { width, height ->
updateThumbnailValidity(container)
updateThumbnailMatrix(container, width, height)
val thumbnailPosition = updateThumbnailMatrix(container, width, height)
container.refreshOverlay(thumbnailPosition)
}
}
}
@@ -66,16 +66,7 @@ class TaskViewModelTest {
@Before
fun setUp() {
sut =
TaskViewModel(
taskViewType = TaskViewType.SINGLE,
recentsViewData = recentsViewData,
getTaskUseCase = getTaskUseCase,
getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
isThumbnailValidUseCase = isThumbnailValidUseCase,
getThumbnailPositionUseCase = getThumbnailPositionUseCase,
dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
)
sut = createTaskViewModel(TaskViewType.SINGLE)
whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) })
whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) })
@@ -93,6 +84,7 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -139,6 +131,7 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -161,6 +154,7 @@ class TaskViewModelTest {
isLiveTile = true,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -183,6 +177,7 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -204,6 +199,7 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -221,6 +217,7 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@@ -235,10 +232,63 @@ class TaskViewModelTest {
isLiveTile = false,
hasHeader = false,
sysUiStatusNavFlags = FLAGS_APPEARANCE_DEFAULT,
taskOverlayEnabled = false,
)
assertThat(sut.state.first()).isEqualTo(expectedResult)
}
@Test
fun taskOverlayEnabled_when_OverlayIsEnabledForVisibleSingleTask() =
testScope.runTest {
sut.bind(TASK_MODEL_1.id)
recentsViewData.overlayEnabled.value = true
recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
assertThat(sut.state.first().taskOverlayEnabled).isTrue()
}
@Test
fun taskOverlayDisabled_when_usingGroupedTask() =
testScope.runTest {
sut = createTaskViewModel(TaskViewType.GROUPED)
sut.bind(TASK_MODEL_1.id)
recentsViewData.overlayEnabled.value = true
recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
assertThat(sut.state.first().taskOverlayEnabled).isFalse()
}
@Test
fun taskOverlayDisabled_when_usingDesktopTask() =
testScope.runTest {
sut = createTaskViewModel(TaskViewType.DESKTOP)
sut.bind(TASK_MODEL_1.id)
recentsViewData.overlayEnabled.value = true
recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
assertThat(sut.state.first().taskOverlayEnabled).isFalse()
}
@Test
fun taskOverlayDisabled_when_OverlayIsEnabledForInvisibleTask() =
testScope.runTest {
sut.bind(TASK_MODEL_1.id)
recentsViewData.overlayEnabled.value = true
recentsViewData.settledFullyVisibleTaskIds.value = setOf(2)
assertThat(sut.state.first().taskOverlayEnabled).isFalse()
}
@Test
fun taskOverlayDisabled_when_OverlayIsDisabledForVisibleTask() =
testScope.runTest {
sut.bind(TASK_MODEL_1.id)
recentsViewData.overlayEnabled.value = false
recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
assertThat(sut.state.first().taskOverlayEnabled).isFalse()
}
@Test
fun shouldShowSplash_calls_useCase() {
sut.isThumbnailValid(null, 0, 0)
@@ -256,6 +306,17 @@ class TaskViewModelTest {
isLocked = isLocked,
)
private fun createTaskViewModel(taskViewType: TaskViewType) =
TaskViewModel(
taskViewType = taskViewType,
recentsViewData = recentsViewData,
getTaskUseCase = getTaskUseCase,
getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
isThumbnailValidUseCase = isThumbnailValidUseCase,
getThumbnailPositionUseCase = getThumbnailPositionUseCase,
dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
)
private companion object {
const val INVALID_TASK_ID = -1
const val FLAGS_APPEARANCE_LIGHT_THEME = FLAG_LIGHT_STATUS or FLAG_LIGHT_NAV