• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.graphics.Color
21 import android.graphics.Matrix
22 import android.graphics.Outline
23 import android.graphics.Path
24 import android.graphics.Rect
25 import android.graphics.drawable.ShapeDrawable
26 import android.util.AttributeSet
27 import android.util.Log
28 import android.view.LayoutInflater
29 import android.view.View
30 import android.view.ViewOutlineProvider
31 import android.widget.FrameLayout
32 import androidx.annotation.ColorInt
33 import androidx.core.view.isInvisible
34 import com.android.launcher3.Flags.enableDesktopExplodedView
35 import com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA
36 import com.android.launcher3.R
37 import com.android.launcher3.util.MultiPropertyFactory
38 import com.android.launcher3.util.ViewPool
39 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
40 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
41 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
42 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
43 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
44 import com.android.quickstep.views.FixedSizeImageView
45 import com.android.quickstep.views.TaskThumbnailViewHeader
46 
47 class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
<lambda>null48     private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
<lambda>null49     private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
<lambda>null50     private val thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
<lambda>null51     private val splashBackground: View by lazy { findViewById(R.id.splash_background) }
<lambda>null52     private val splashIcon: FixedSizeImageView by lazy { findViewById(R.id.splash_icon) }
<lambda>null53     private val dimAlpha: MultiPropertyFactory<View> by lazy {
54         MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
55     }
56     private val outlinePath = Path()
57     private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
58 
59     private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
60 
61     private var uiState: TaskThumbnailUiState = Uninitialized
62 
63     /**
64      * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
65      */
66     var outlineBounds: Rect? = null
67         set(value) {
68             field = value
69             invalidateOutline()
70         }
71 
72     private val bounds = Rect()
73 
74     var cornerRadius: Float = 0f
75         set(value) {
76             field = value
77             invalidateOutline()
78         }
79 
80     constructor(context: Context) : super(context)
81 
82     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
83 
84     constructor(
85         context: Context,
86         attrs: AttributeSet?,
87         defStyleAttr: Int,
88     ) : super(context, attrs, defStyleAttr)
89 
onFinishInflatenull90     override fun onFinishInflate() {
91         super.onFinishInflate()
92         maybeCreateHeader()
93     }
94 
onAttachedToWindownull95     override fun onAttachedToWindow() {
96         super.onAttachedToWindow()
97         clipToOutline = true
98         outlineProvider =
99             object : ViewOutlineProvider() {
100                 override fun getOutline(view: View, outline: Outline) {
101                     val outlineRect = outlineBounds ?: bounds
102                     outlinePath.apply {
103                         rewind()
104                         addRoundRect(
105                             outlineRect.left.toFloat(),
106                             outlineRect.top.toFloat(),
107                             outlineRect.right.toFloat(),
108                             outlineRect.bottom.toFloat(),
109                             cornerRadius / scaleX,
110                             cornerRadius / scaleY,
111                             Path.Direction.CW,
112                         )
113                     }
114                     outline.setPath(outlinePath)
115                 }
116             }
117     }
118 
onRecyclenull119     override fun onRecycle() {
120         uiState = Uninitialized
121         onSizeChanged = null
122         outlineBounds = null
123         resetViews()
124     }
125 
setStatenull126     fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
127         if (uiState == state) return
128         logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
129         uiState = state
130         resetViews()
131         when (state) {
132             is Uninitialized -> {}
133             is LiveTile -> drawLiveWindow(state)
134             is SnapshotSplash -> drawSnapshotSplash(state)
135             is BackgroundOnly -> drawBackground(state.backgroundColor)
136         }
137     }
138 
139     /**
140      * Updates the alpha of the dim layer on top of this view. If dimAlpha is 0, no dimming is
141      * applied; if dimAlpha is 1, the thumbnail will be the extracted background color.
142      *
143      * @param tintAmount The amount of alpha that will be applied to the dim layer.
144      */
updateTintAmountnull145     fun updateTintAmount(tintAmount: Float) {
146         dimAlpha[ScrimViewAlpha.TintAmount.ordinal].value = tintAmount
147     }
148 
updateMenuOpenProgressnull149     fun updateMenuOpenProgress(progress: Float) {
150         dimAlpha[ScrimViewAlpha.MenuProgress.ordinal].value = progress * MAX_SCRIM_ALPHA
151     }
152 
updateSplashAlphanull153     fun updateSplashAlpha(value: Float) {
154         splashBackground.alpha = value
155         splashIcon.alpha = value
156     }
157 
doOnSizeChangenull158     fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
159         onSizeChanged = action
160     }
161 
onSizeChangednull162     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
163         super.onSizeChanged(w, h, oldw, oldh)
164         onSizeChanged?.invoke(width, height)
165         bounds.set(0, 0, w, h)
166         invalidateOutline()
167     }
168 
setScaleXnull169     override fun setScaleX(scaleX: Float) {
170         super.setScaleX(scaleX)
171         // Splash icon should ignore scale on TTV
172         splashIcon.scaleX = 1 / scaleX
173     }
174 
setScaleYnull175     override fun setScaleY(scaleY: Float) {
176         super.setScaleY(scaleY)
177         // Splash icon should ignore scale on TTV
178         splashIcon.scaleY = 1 / scaleY
179     }
180 
resetViewsnull181     private fun resetViews() {
182         liveTileView.isInvisible = true
183         thumbnailView.isInvisible = true
184         thumbnailView.setImageBitmap(null)
185         splashBackground.alpha = 0f
186         splashIcon.alpha = 0f
187         splashIcon.setImageDrawable(null)
188         scrimView.alpha = 0f
189         setBackgroundColor(Color.BLACK)
190         taskThumbnailViewHeader?.isInvisible = true
191     }
192 
drawBackgroundnull193     private fun drawBackground(@ColorInt background: Int) {
194         setBackgroundColor(background)
195     }
196 
drawLiveWindownull197     private fun drawLiveWindow(liveTile: LiveTile) {
198         liveTileView.isInvisible = false
199 
200         if (liveTile is LiveTile.WithHeader) {
201             taskThumbnailViewHeader?.isInvisible = false
202             taskThumbnailViewHeader?.setHeader(liveTile.header)
203         }
204     }
205 
drawSnapshotSplashnull206     private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
207         drawSnapshot(snapshotSplash.snapshot)
208 
209         splashBackground.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
210         val icon = snapshotSplash.splash?.constantState?.newDrawable()?.mutate() ?: ShapeDrawable()
211         splashIcon.setImageDrawable(icon)
212     }
213 
drawSnapshotnull214     private fun drawSnapshot(snapshot: Snapshot) {
215         if (snapshot is Snapshot.WithHeader) {
216             taskThumbnailViewHeader?.isInvisible = false
217             taskThumbnailViewHeader?.setHeader(snapshot.header)
218         }
219 
220         drawBackground(snapshot.backgroundColor)
221         thumbnailView.setImageBitmap(snapshot.bitmap)
222         thumbnailView.isInvisible = false
223     }
224 
setImageMatrixnull225     fun setImageMatrix(matrix: Matrix) {
226         if (uiState is SnapshotSplash) {
227             thumbnailView.imageMatrix = matrix
228         }
229     }
230 
logDebugnull231     private fun logDebug(message: String) {
232         Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
233     }
234 
maybeCreateHeadernull235     private fun maybeCreateHeader() {
236         if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
237             taskThumbnailViewHeader =
238                 LayoutInflater.from(context)
239                     .inflate(R.layout.task_thumbnail_view_header, this, false)
240                     as TaskThumbnailViewHeader
241             addView(taskThumbnailViewHeader)
242         }
243     }
244 
245     private companion object {
246         const val TAG = "TaskThumbnailView"
247         private const val MAX_SCRIM_ALPHA = 0.4f
248 
249         enum class ScrimViewAlpha {
250             MenuProgress,
251             TintAmount,
252         }
253     }
254 }
255