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