1 /*
<lambda>null2  * Copyright (C) 2024 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.util
18 
19 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
20 import com.android.quickstep.RecentsAnimationController
21 import com.android.quickstep.views.DesktopTaskView
22 import com.android.quickstep.views.TaskView
23 import com.android.quickstep.views.TaskViewType
24 import com.android.systemui.shared.recents.model.ThumbnailData
25 
26 /**
27  * Helper class for [com.android.quickstep.views.RecentsView]. This util class contains refactored
28  * and extracted functions from RecentsView to facilitate the implementation of unit tests.
29  */
30 class RecentsViewUtils {
31     /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
32     fun screenshotTasks(
33         taskView: TaskView,
34         recentsAnimationController: RecentsAnimationController,
35     ): Map<Int, ThumbnailData> =
36         taskView.taskContainers.associate {
37             it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
38         }
39 
40     /**
41      * Sorts task groups to move desktop tasks to the end of the list.
42      *
43      * @param tasks List of group tasks to be sorted.
44      * @return Sorted list of GroupTasks to be used in the RecentsView.
45      */
46     fun sortDesktopTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
47         val (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP }
48         return otherTasks + desktopTasks
49     }
50 
51     /** Counts [TaskView]s that are [DesktopTaskView] instances. */
52     fun getDesktopTaskViewCount(taskViews: Iterable<TaskView>): Int =
53         taskViews.count { it is DesktopTaskView }
54 
55     /** Returns a list of all large TaskView Ids from [TaskView]s */
56     fun getLargeTaskViewIds(taskViews: Iterable<TaskView>): List<Int> =
57         taskViews.filter { it.isLargeTile }.map { it.taskViewId }
58 
59     /** Counts [TaskView]s that are large tiles. */
60     fun getLargeTileCount(taskViews: Iterable<TaskView>): Int = taskViews.count { it.isLargeTile }
61 
62     /**
63      * Returns the first TaskView that should be displayed as a large tile.
64      *
65      * @param taskViews List of [TaskView]s
66      * @param splitSelectActive current split state
67      */
68     fun getFirstLargeTaskView(
69         taskViews: MutableIterable<TaskView>,
70         splitSelectActive: Boolean,
71     ): TaskView? =
72         taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
73 
74     /** Returns the expected focus task. */
75     fun getExpectedFocusedTask(taskViews: Iterable<TaskView>): TaskView? =
76         if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
77         else taskViews.firstOrNull()
78 
79     /**
80      * Returns the [TaskView] that should be the current page during task binding, in the following
81      * priorities:
82      * 1. Running task
83      * 2. Focused task
84      * 3. First non-desktop task
85      * 4. Last desktop task
86      * 5. null otherwise
87      */
88     fun getExpectedCurrentTask(
89         runningTaskView: TaskView?,
90         focusedTaskView: TaskView?,
91         taskViews: Iterable<TaskView>,
92     ): TaskView? =
93         runningTaskView
94             ?: focusedTaskView
95             ?: taskViews.firstOrNull { it !is DesktopTaskView }
96             ?: taskViews.lastOrNull()
97 
98     /**
99      * Returns the first TaskView that is not large
100      *
101      * @param taskViews List of [TaskView]s
102      */
103     fun getFirstSmallTaskView(taskViews: MutableIterable<TaskView>): TaskView? =
104         taskViews.firstOrNull { !it.isLargeTile }
105 
106     /** Returns the last TaskView that should be displayed as a large tile. */
107     fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
108         taskViews.lastOrNull { it.isLargeTile }
109 
110     /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
111     fun getFirstTaskViewInCarousel(
112         nonRunningTaskCarouselHidden: Boolean,
113         taskViews: Iterable<TaskView>,
114         runningTaskView: TaskView?,
115     ): TaskView? =
116         taskViews.firstOrNull {
117             it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
118         }
119 
120     /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
121     fun getLastTaskViewInCarousel(
122         nonRunningTaskCarouselHidden: Boolean,
123         taskViews: Iterable<TaskView>,
124         runningTaskView: TaskView?,
125     ): TaskView? =
126         taskViews.lastOrNull {
127             it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
128         }
129 
130     /** Returns if any small tasks are fully visible */
131     fun isAnySmallTaskFullyVisible(
132         taskViews: Iterable<TaskView>,
133         isTaskViewFullyVisible: (TaskView) -> Boolean,
134     ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
135 
136     /** Returns the current list of [TaskView] children. */
137     fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
138         (0 until taskViewCount).map(requireTaskViewAt)
139 
140     /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
141     fun applyAttachAlpha(
142         taskViews: Iterable<TaskView>,
143         runningTaskView: TaskView?,
144         runningTaskAttachAlpha: Float,
145         nonRunningTaskCarouselHidden: Boolean,
146     ) {
147         taskViews.forEach { taskView ->
148             taskView.attachAlpha =
149                 if (taskView == runningTaskView) {
150                     runningTaskAttachAlpha
151                 } else {
152                     if (taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden))
153                         1f
154                     else 0f
155                 }
156         }
157     }
158 
159     fun TaskView.isVisibleInCarousel(
160         runningTaskView: TaskView?,
161         nonRunningTaskCarouselHidden: Boolean,
162     ): Boolean =
163         if (!nonRunningTaskCarouselHidden) true
164         else getCarouselType() == runningTaskView.getCarouselType()
165 
166     /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
167     private fun TaskView?.getCarouselType(): TaskViewCarousel =
168         if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
169 
170     private enum class TaskViewCarousel {
171         FULL_SCREEN,
172         DESKTOP,
173     }
174 }
175