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