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:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+71
-10
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user