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