1 /*
<lambda>null2  * Copyright (C) 2022 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 package com.android.quickstep.views
17 
18 import android.content.Context
19 import android.graphics.Point
20 import android.graphics.PointF
21 import android.graphics.Rect
22 import android.graphics.drawable.LayerDrawable
23 import android.graphics.drawable.ShapeDrawable
24 import android.graphics.drawable.shapes.RoundRectShape
25 import android.util.AttributeSet
26 import android.util.Log
27 import android.view.View
28 import android.view.ViewGroup
29 import androidx.core.view.updateLayoutParams
30 import com.android.launcher3.R
31 import com.android.launcher3.util.RunnableList
32 import com.android.launcher3.util.SplitConfigurationOptions
33 import com.android.launcher3.util.TransformingTouchDelegate
34 import com.android.launcher3.util.ViewPool
35 import com.android.launcher3.util.rects.set
36 import com.android.quickstep.BaseContainerInterface
37 import com.android.quickstep.TaskOverlayFactory
38 import com.android.quickstep.util.RecentsOrientedState
39 import com.android.systemui.shared.recents.model.Task
40 
41 /** TaskView that contains all tasks that are part of the desktop. */
42 class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
43     TaskView(context, attrs) {
44 
45     private val snapshotDrawParams =
46         object : FullscreenDrawParams(context) {
47             // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
48             override fun computeTaskCornerRadius(context: Context) =
49                 computeWindowCornerRadius(context)
50         }
51     private val taskThumbnailViewPool =
52         ViewPool<TaskThumbnailViewDeprecated>(
53             context,
54             this,
55             R.layout.task_thumbnail,
56             VIEW_POOL_MAX_SIZE,
57             VIEW_POOL_INITIAL_SIZE
58         )
59     private val tempPointF = PointF()
60     private val tempRect = Rect()
61     private lateinit var backgroundView: View
62     private lateinit var iconView: TaskViewIcon
63     private var childCountAtInflation = 0
64 
65     override fun onFinishInflate() {
66         super.onFinishInflate()
67         backgroundView =
68             findViewById<View>(R.id.background)!!.apply {
69                 updateLayoutParams<LayoutParams> {
70                     topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
71                 }
72                 background =
73                     ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null))
74                         .apply {
75                             setTint(
76                                 resources.getColor(
77                                     android.R.color.system_neutral2_300,
78                                     context.theme
79                                 )
80                             )
81                         }
82             }
83         iconView =
84             getOrInflateIconView(R.id.icon).apply {
85                 val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
86                 val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
87                 setIcon(this, LayerDrawable(arrayOf(iconBackground, icon)))
88             }
89         childCountAtInflation = childCount
90     }
91 
92     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
93         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
94         val containerWidth = MeasureSpec.getSize(widthMeasureSpec)
95         var containerHeight = MeasureSpec.getSize(heightMeasureSpec)
96         setMeasuredDimension(containerWidth, containerHeight)
97 
98         if (taskContainers.isEmpty()) {
99             return
100         }
101 
102         val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
103         containerHeight -= thumbnailTopMarginPx
104 
105         BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
106         val windowWidth = tempPointF.x.toInt()
107         val windowHeight = tempPointF.y.toInt()
108         val scaleWidth = containerWidth / windowWidth.toFloat()
109         val scaleHeight = containerHeight / windowHeight.toFloat()
110         if (DEBUG) {
111             Log.d(
112                 TAG,
113                 "onMeasure: container=[$containerWidth,$containerHeight] " +
114                     "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]"
115             )
116         }
117 
118         // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
119         taskContainers.forEach {
120             // Default to quarter of the desktop if we did not get app bounds.
121             val taskSize =
122                 it.task.appBounds
123                     ?: tempRect.apply {
124                         left = 0
125                         top = 0
126                         right = windowWidth / 4
127                         bottom = windowHeight / 4
128                     }
129             val thumbWidth = (taskSize.width() * scaleWidth).toInt()
130             val thumbHeight = (taskSize.height() * scaleHeight).toInt()
131             it.thumbnailViewDeprecated.measure(
132                 MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
133                 MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
134             )
135 
136             // Position the task to the same position as it would be on the desktop
137             val positionInParent = it.task.positionInParent ?: ORIGIN
138             val taskX = (positionInParent.x * scaleWidth).toInt()
139             var taskY = (positionInParent.y * scaleHeight).toInt()
140             // move task down by margin size
141             taskY += thumbnailTopMarginPx
142             it.thumbnailViewDeprecated.x = taskX.toFloat()
143             it.thumbnailViewDeprecated.y = taskY.toFloat()
144             if (DEBUG) {
145                 Log.d(
146                     TAG,
147                     "onMeasure: task=${it.task.key} thumb=[$thumbWidth,$thumbHeight]" +
148                         " pos=[$taskX,$taskY]"
149                 )
150             }
151         }
152     }
153 
154     override fun onRecycle() {
155         super.onRecycle()
156         visibility = VISIBLE
157     }
158 
159     /** Updates this desktop task to the gives task list defined in `tasks` */
160     fun bind(
161         tasks: List<Task>,
162         orientedState: RecentsOrientedState,
163         taskOverlayFactory: TaskOverlayFactory
164     ) {
165         if (DEBUG) {
166             val sb = StringBuilder()
167             sb.append("bind tasks=").append(tasks.size).append("\n")
168             tasks.forEach { sb.append(" key=${it.key}\n") }
169             Log.d(TAG, sb.toString())
170         }
171         cancelPendingLoadTasks()
172 
173         if (!isTaskContainersInitialized()) {
174             taskContainers = arrayListOf()
175         }
176         val taskContainers = taskContainers as ArrayList
177         taskContainers.ensureCapacity(tasks.size)
178         tasks.forEachIndexed { index, task ->
179             val thumbnailViewDeprecated: TaskThumbnailViewDeprecated
180             if (index >= taskContainers.size) {
181                 thumbnailViewDeprecated = taskThumbnailViewPool.view
182                 // Add thumbnailView from to position after the initial child views.
183                 addView(
184                     thumbnailViewDeprecated,
185                     childCountAtInflation,
186                     LayoutParams(
187                         ViewGroup.LayoutParams.WRAP_CONTENT,
188                         ViewGroup.LayoutParams.WRAP_CONTENT
189                     )
190                 )
191             } else {
192                 thumbnailViewDeprecated = taskContainers[index].thumbnailViewDeprecated
193             }
194             val taskContainer =
195                 TaskContainer(
196                         task,
197                         // TODO(b/338360089): Support new TTV for DesktopTaskView
198                         thumbnailView = null,
199                         thumbnailViewDeprecated,
200                         iconView,
201                         TransformingTouchDelegate(iconView.asView()),
202                         SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
203                         digitalWellBeingToast = null,
204                         showWindowsView = null,
205                         taskOverlayFactory
206                     )
207                     .apply { thumbnailViewDeprecated.bind(task, overlay) }
208             if (index >= taskContainers.size) {
209                 taskContainers.add(taskContainer)
210             } else {
211                 taskContainers[index] = taskContainer
212             }
213         }
214         repeat(taskContainers.size - tasks.size) {
215             with(taskContainers.removeLast()) {
216                 removeView(thumbnailViewDeprecated)
217                 taskThumbnailViewPool.recycle(thumbnailViewDeprecated)
218             }
219         }
220 
221         setOrientationState(orientedState)
222     }
223 
224     override fun needsUpdate(dataChange: Int, flag: Int) =
225         if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false
226 
227     // thumbnailView is laid out differently and is handled in onMeasure
228     override fun updateThumbnailSize() {}
229 
230     override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
231         if (relativeToDragLayer) {
232             container.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
233         } else {
234             bounds.set(backgroundView)
235         }
236     }
237 
238     override fun launchTaskAnimated(): RunnableList? {
239         val recentsView = recentsView ?: return null
240         val endCallback = RunnableList()
241         val desktopController = recentsView.desktopRecentsController
242         checkNotNull(desktopController) { "recentsController is null" }
243         desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
244         Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}")
245 
246         // Callbacks get run from recentsView for case when recents animation already running
247         recentsView.addSideTaskLaunchCallback(endCallback)
248         return endCallback
249     }
250 
251     override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
252         launchTasks()
253         callback(true)
254     }
255 
256     // Desktop tile can't be in split screen
257     override fun confirmSecondSplitSelectApp(): Boolean = false
258 
259     // TODO(b/330685808) support overlay for Screenshot action
260     override fun setOverlayEnabled(overlayEnabled: Boolean) {}
261 
262     override fun onFullscreenProgressChanged(fullscreenProgress: Float) {
263         // Don't show background while we are transitioning to/from fullscreen
264         backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE
265     }
266 
267     override fun updateCurrentFullscreenParams() {
268         super.updateCurrentFullscreenParams()
269         updateFullscreenParams(snapshotDrawParams)
270     }
271 
272     override fun getThumbnailFullscreenParams() = snapshotDrawParams
273 
274     companion object {
275         private const val TAG = "DesktopTaskView"
276         private const val DEBUG = false
277         private const val VIEW_POOL_MAX_SIZE = 10
278         // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
279         private const val VIEW_POOL_INITIAL_SIZE = 0
280         private val ORIGIN = Point(0, 0)
281     }
282 }
283