• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.recents.ui.viewmodel
18 
19 import android.annotation.ColorInt
20 import android.util.Log
21 import androidx.core.graphics.ColorUtils
22 import com.android.launcher3.util.coroutines.DispatcherProvider
23 import com.android.quickstep.recents.domain.model.TaskId
24 import com.android.quickstep.recents.domain.model.TaskModel
25 import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
26 import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
27 import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
28 import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
29 import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
30 import com.android.quickstep.recents.viewmodel.RecentsViewData
31 import com.android.quickstep.views.TaskViewType
32 import com.android.systemui.shared.recents.model.ThumbnailData
33 import kotlinx.coroutines.ExperimentalCoroutinesApi
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.MutableStateFlow
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.distinctUntilChanged
38 import kotlinx.coroutines.flow.flatMapLatest
39 import kotlinx.coroutines.flow.flowOn
40 import kotlinx.coroutines.flow.map
41 
42 /**
43  * ViewModel used for [com.android.quickstep.views.TaskView],
44  * [com.android.quickstep.views.DesktopTaskView] and [com.android.quickstep.views.GroupedTaskView].
45  */
46 @OptIn(ExperimentalCoroutinesApi::class)
47 class TaskViewModel(
48     private val taskViewType: TaskViewType,
49     recentsViewData: RecentsViewData,
50     private val getTaskUseCase: GetTaskUseCase,
51     private val getSysUiStatusNavFlagsUseCase: GetSysUiStatusNavFlagsUseCase,
52     private val isThumbnailValidUseCase: IsThumbnailValidUseCase,
53     private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
54     dispatcherProvider: DispatcherProvider,
55 ) {
56     private var taskIds = MutableStateFlow(emptySet<Int>())
57 
58     private val isLiveTile =
59         combine(
60                 taskIds,
61                 recentsViewData.runningTaskIds,
62                 recentsViewData.runningTaskShowScreenshot,
63             ) { taskIds, runningTaskIds, runningTaskShowScreenshot ->
64                 runningTaskIds == taskIds && !runningTaskShowScreenshot
65             }
66             .distinctUntilChanged()
67 
68     private val isCentralTask =
69         combine(taskIds, recentsViewData.centralTaskIds) { taskIds, centralTaskIds ->
70                 taskIds == centralTaskIds
71             }
72             .distinctUntilChanged()
73 
74     private val taskData =
75         taskIds.flatMapLatest { ids ->
76             // Combine Tasks requests
77             combine(
78                 ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
79                 ::mapToTaskData,
80             )
81         }
82 
83     private val overlayEnabled =
84         combine(recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds) {
85                 isOverlayEnabled,
86                 settledFullyVisibleTaskIds ->
87                 isOverlayEnabled && settledFullyVisibleTaskIds.any { it in taskIds.value }
88             }
89             .distinctUntilChanged()
90 
91     val state: Flow<TaskTileUiState> =
92         combine(taskData, isLiveTile, overlayEnabled, isCentralTask, ::mapToTaskTile)
93             .distinctUntilChanged()
94             .flowOn(dispatcherProvider.background)
95 
96     fun bind(vararg taskId: TaskId) {
97         taskIds.value = taskId.toSet().also { Log.d(TAG, "bind: $it") }
98     }
99 
100     fun isThumbnailValid(thumbnail: ThumbnailData?, width: Int, height: Int): Boolean =
101         isThumbnailValidUseCase(thumbnail, width, height)
102 
103     fun getThumbnailPosition(
104         thumbnail: ThumbnailData?,
105         width: Int,
106         height: Int,
107         isRtl: Boolean,
108     ): ThumbnailPosition =
109         getThumbnailPositionUseCase(
110             thumbnailData = thumbnail,
111             width = width,
112             height = height,
113             isRtl = isRtl,
114         )
115 
116     private fun mapToTaskTile(
117         tasks: List<TaskData>,
118         isLiveTile: Boolean,
119         overlayEnabled: Boolean,
120         isCentralTask: Boolean,
121     ): TaskTileUiState {
122         val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
123         return TaskTileUiState(
124             tasks = tasks,
125             isLiveTile = isLiveTile,
126             hasHeader = taskViewType == TaskViewType.DESKTOP,
127             sysUiStatusNavFlags = getSysUiStatusNavFlagsUseCase(firstThumbnailData),
128             taskOverlayEnabled = overlayEnabled,
129             isCentralTask = isCentralTask,
130         )
131     }
132 
133     private fun mapToTaskData(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
134         result.map { mapToTaskData(it.first, it.second) }
135 
136     private fun mapToTaskData(taskId: TaskId, result: TaskModel?): TaskData =
137         result?.let {
138             TaskData.Data(
139                 taskId = taskId,
140                 title = result.title,
141                 titleDescription = result.titleDescription,
142                 icon = result.icon,
143                 thumbnailData = result.thumbnail,
144                 backgroundColor = result.backgroundColor.removeAlpha(),
145                 isLocked = result.isLocked,
146             )
147         } ?: TaskData.NoData(taskId)
148 
149     @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
150 
151     private companion object {
152         const val TAG = "TaskViewModel"
153     }
154 }
155