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.task.thumbnail 18 19 import android.content.Context 20 import android.content.res.Configuration 21 import android.graphics.Canvas 22 import android.graphics.Color 23 import android.graphics.Outline 24 import android.graphics.Paint 25 import android.graphics.PorterDuff 26 import android.graphics.PorterDuffXfermode 27 import android.graphics.Rect 28 import android.util.AttributeSet 29 import android.view.View 30 import android.view.ViewOutlineProvider 31 import androidx.annotation.ColorInt 32 import com.android.launcher3.Utilities 33 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly 34 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile 35 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot 36 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized 37 import com.android.quickstep.util.TaskCornerRadius 38 import com.android.quickstep.views.RecentsView 39 import com.android.quickstep.views.RecentsViewContainer 40 import com.android.quickstep.views.TaskView 41 import com.android.systemui.shared.system.QuickStepContract 42 import kotlinx.coroutines.MainScope 43 import kotlinx.coroutines.launch 44 45 class TaskThumbnailView : View { 46 // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped 47 // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] 48 // This is using a lazy for now because the dependencies cannot be obtained without DI. 49 val viewModel by lazy { 50 val recentsView = 51 RecentsViewContainer.containerFromContext<RecentsViewContainer>(context) 52 .getOverviewPanel<RecentsView<*, *>>() 53 TaskThumbnailViewModel( 54 recentsView.mRecentsViewData, 55 (parent as TaskView).taskViewData, 56 recentsView.mTasksRepository, 57 ) 58 } 59 60 private var uiState: TaskThumbnailUiState = Uninitialized 61 private var inheritedScale: Float = 1f 62 63 private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) 64 private val _measuredBounds = Rect() 65 private val measuredBounds: Rect 66 get() { 67 _measuredBounds.set(0, 0, measuredWidth, measuredHeight) 68 return _measuredBounds 69 } 70 private var cornerRadius: Float = TaskCornerRadius.get(context) 71 private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context) 72 73 constructor(context: Context?) : super(context) 74 constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) 75 constructor( 76 context: Context?, 77 attrs: AttributeSet?, 78 defStyleAttr: Int, 79 ) : super(context, attrs, defStyleAttr) 80 81 override fun onAttachedToWindow() { 82 super.onAttachedToWindow() 83 // TODO(b/335396935) replace MainScope with shorter lifecycle. 84 MainScope().launch { 85 viewModel.uiState.collect { viewModelUiState -> 86 uiState = viewModelUiState 87 invalidate() 88 } 89 } 90 MainScope().launch { viewModel.recentsFullscreenProgress.collect { invalidateOutline() } } 91 MainScope().launch { 92 viewModel.inheritedScale.collect { viewModelInheritedScale -> 93 inheritedScale = viewModelInheritedScale 94 invalidateOutline() 95 } 96 } 97 98 clipToOutline = true 99 outlineProvider = 100 object : ViewOutlineProvider() { 101 override fun getOutline(view: View, outline: Outline) { 102 outline.setRoundRect(measuredBounds, getCurrentCornerRadius()) 103 } 104 } 105 } 106 107 override fun onDraw(canvas: Canvas) { 108 when (val uiStateVal = uiState) { 109 is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK) 110 is LiveTile -> drawTransparentUiState(canvas) 111 is Snapshot -> drawSnapshotState(canvas, uiStateVal) 112 is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor) 113 } 114 } 115 116 private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) { 117 backgroundPaint.color = backgroundColor 118 canvas.drawRect(measuredBounds, backgroundPaint) 119 } 120 121 override fun onConfigurationChanged(newConfig: Configuration?) { 122 super.onConfigurationChanged(newConfig) 123 124 cornerRadius = TaskCornerRadius.get(context) 125 fullscreenCornerRadius = QuickStepContract.getWindowCornerRadius(context) 126 invalidateOutline() 127 } 128 129 private fun drawTransparentUiState(canvas: Canvas) { 130 canvas.drawRect(measuredBounds, CLEAR_PAINT) 131 } 132 133 private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) { 134 drawBackgroundOnly(canvas, snapshot.backgroundColor) 135 canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null) 136 } 137 138 private fun getCurrentCornerRadius() = 139 Utilities.mapRange( 140 viewModel.recentsFullscreenProgress.value, 141 cornerRadius, 142 fullscreenCornerRadius 143 ) / inheritedScale 144 145 companion object { 146 private val CLEAR_PAINT = 147 Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } 148 } 149 } 150