1 /*
2  * 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 
17 package com.android.systemui.mediaprojection.appselector.view
18 
19 import android.content.Context
20 import android.graphics.BitmapShader
21 import android.graphics.Canvas
22 import android.graphics.Color
23 import android.graphics.Paint
24 import android.graphics.Rect
25 import android.graphics.Shader
26 import android.util.AttributeSet
27 import android.view.View
28 import android.view.WindowManager
29 import androidx.core.content.getSystemService
30 import androidx.core.content.res.use
31 import com.android.systemui.res.R
32 import com.android.systemui.mediaprojection.appselector.data.RecentTask
33 import com.android.systemui.shared.recents.model.ThumbnailData
34 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
35 import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
36 
37 /**
38  * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
39  * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper].
40  */
41 class MediaProjectionTaskView
42 @JvmOverloads
43 constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
44     View(context, attrs, defStyleAttr) {
45 
46     private val defaultBackgroundColor: Int
47 
48     init {
49         val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating)
50         defaultBackgroundColor =
<lambda>null51             context.obtainStyledAttributes(backgroundColorAttribute).use {
52                 it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK)
53             }
54     }
55 
56     private val windowManager: WindowManager = context.getSystemService()!!
57     private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
58     private val backgroundPaint =
<lambda>null59         Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
60     private val cornerRadius =
61         context.resources.getDimensionPixelSize(
62             R.dimen.media_projection_app_selector_task_rounded_corners
63         )
64     private val previewPositionHelper = PreviewPositionHelper()
65     private val previewRect = Rect()
66 
67     private var task: RecentTask? = null
68     private var thumbnailData: ThumbnailData? = null
69 
70     private var bitmapShader: BitmapShader? = null
71 
bindTasknull72     fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) {
73         this.task = task
74         this.thumbnailData = thumbnailData
75 
76         // Strip alpha channel to make sure that the color is not semi-transparent
77         val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt()
78 
79         paint.color = color
80         backgroundPaint.color = color
81 
82         refresh()
83     }
84 
onSizeChangednull85     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
86         updateThumbnailMatrix()
87         invalidate()
88     }
89 
onDrawnull90     override fun onDraw(canvas: Canvas) {
91         // Always draw the background since the snapshots might be translucent or partially empty
92         // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
93         // split screen).
94         canvas.drawRoundRect(
95             0f,
96             1f,
97             width.toFloat(),
98             (height - 1).toFloat(),
99             cornerRadius.toFloat(),
100             cornerRadius.toFloat(),
101             backgroundPaint
102         )
103 
104         val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null
105         if (drawBackgroundOnly) {
106             return
107         }
108 
109         // Draw the task thumbnail using bitmap shader in the paint
110         canvas.drawRoundRect(
111             0f,
112             0f,
113             width.toFloat(),
114             height.toFloat(),
115             cornerRadius.toFloat(),
116             cornerRadius.toFloat(),
117             paint
118         )
119     }
120 
refreshnull121     private fun refresh() {
122         val thumbnailBitmap = thumbnailData?.thumbnail
123 
124         if (thumbnailBitmap != null) {
125             thumbnailBitmap.prepareToDraw()
126             bitmapShader =
127                 BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
128             paint.shader = bitmapShader
129             updateThumbnailMatrix()
130         } else {
131             bitmapShader = null
132             paint.shader = null
133         }
134 
135         invalidate()
136     }
137 
updateThumbnailMatrixnull138     private fun updateThumbnailMatrix() {
139         previewPositionHelper.isOrientationChanged = false
140 
141         val bitmapShader = bitmapShader ?: return
142         val thumbnailData = thumbnailData ?: return
143         val thumbnail = thumbnailData.thumbnail ?: return
144         val display = context.display ?: return
145         val windowMetrics = windowManager.maximumWindowMetrics
146 
147         previewRect.set(0, 0, thumbnail.width, thumbnail.height)
148 
149         val currentRotation: Int = display.rotation
150         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
151         val isLargeScreen = isLargeScreen(context)
152 
153         previewPositionHelper.updateThumbnailMatrix(
154             previewRect,
155             thumbnailData,
156             measuredWidth,
157             measuredHeight,
158             isLargeScreen,
159             currentRotation,
160             isRtl
161         )
162 
163         bitmapShader.setLocalMatrix(previewPositionHelper.matrix)
164         paint.shader = bitmapShader
165     }
166 }
167