From 33c6559d41e7343fcef719fcbf58d5a6380e7571 Mon Sep 17 00:00:00 2001 From: Uwais Ashraf Date: Wed, 5 Mar 2025 14:01:19 +0000 Subject: [PATCH] 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 --- .../android/quickstep/TaskOverlayFactory.java | 65 ++++----- .../recents/ui/viewmodel/TaskTileUiState.kt | 1 + .../recents/ui/viewmodel/TaskViewModel.kt | 20 ++- .../task/thumbnail/TaskOverlayUiState.kt | 26 ---- .../quickstep/task/util/TaskOverlayHelper.kt | 128 ------------------ .../task/viewmodel/TaskOverlayViewModel.kt | 75 ---------- .../android/quickstep/views/TaskContainer.kt | 40 +++++- .../com/android/quickstep/views/TaskView.kt | 25 ++-- .../recents/ui/viewmodel/TaskViewModelTest.kt | 81 +++++++++-- 9 files changed, 173 insertions(+), 288 deletions(-) delete mode 100644 quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt delete mode 100644 quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt delete mode 100644 quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java index deb06c98d8..df66a5e30e 100644 --- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java @@ -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); diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt index 118a93199e..8a6a8051f1 100644 --- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt +++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt @@ -36,6 +36,7 @@ data class TaskTileUiState( val isLiveTile: Boolean, val hasHeader: Boolean, val sysUiStatusNavFlags: Int, + val taskOverlayEnabled: Boolean, ) sealed class TaskData { diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt index 3c4a3848d2..09e20711d0 100644 --- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt +++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt @@ -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 = - 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, isLiveTile: Boolean): TaskTileUiState { + private fun mapToTaskTile( + tasks: List, + 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, ) } diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt deleted file mode 100644 index 5fb5b90bd0..0000000000 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt +++ /dev/null @@ -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() -} diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt deleted file mode 100644 index d8aea9dfd3..0000000000 --- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt +++ /dev/null @@ -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 - } -} diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt deleted file mode 100644 index 9bff3ac004..0000000000 --- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt +++ /dev/null @@ -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) -} diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt index 6d7ae7001d..ec6d1c4432 100644 --- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt +++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt @@ -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) { 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" + } } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index c878d56e4a..b7f1d1d533 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -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) } } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt index a97ef0c00c..18b9fe9685 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt @@ -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