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