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