| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.mediaprojection.appselector.view |
| |
| import android.content.Context |
| import android.graphics.BitmapShader |
| import android.graphics.Canvas |
| import android.graphics.Color |
| import android.graphics.Paint |
| import android.graphics.Rect |
| import android.graphics.Shader |
| import android.util.AttributeSet |
| import android.view.View |
| import android.view.WindowManager |
| import androidx.core.content.getSystemService |
| import androidx.core.content.res.use |
| import com.android.internal.R as AndroidR |
| import com.android.systemui.R |
| import com.android.systemui.mediaprojection.appselector.data.RecentTask |
| import com.android.systemui.shared.recents.model.ThumbnailData |
| import com.android.systemui.shared.recents.utilities.PreviewPositionHelper |
| import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen |
| |
| /** |
| * Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData]. |
| * It handles proper cropping and positioning of the thumbnail using [PreviewPositionHelper]. |
| */ |
| class MediaProjectionTaskView |
| @JvmOverloads |
| constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : |
| View(context, attrs, defStyleAttr) { |
| |
| private val defaultBackgroundColor: Int |
| |
| init { |
| val backgroundColorAttribute = intArrayOf(android.R.attr.colorBackgroundFloating) |
| defaultBackgroundColor = |
| context.obtainStyledAttributes(backgroundColorAttribute).use { |
| it.getColor(/* index= */ 0, /* defValue= */ Color.BLACK) |
| } |
| } |
| |
| private val windowManager: WindowManager = context.getSystemService()!! |
| private val paint = Paint(Paint.ANTI_ALIAS_FLAG) |
| private val backgroundPaint = |
| Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor } |
| private val cornerRadius = |
| context.resources.getDimensionPixelSize( |
| R.dimen.media_projection_app_selector_task_rounded_corners |
| ) |
| private val previewPositionHelper = PreviewPositionHelper() |
| private val previewRect = Rect() |
| |
| private var task: RecentTask? = null |
| private var thumbnailData: ThumbnailData? = null |
| |
| private var bitmapShader: BitmapShader? = null |
| |
| fun bindTask(task: RecentTask?, thumbnailData: ThumbnailData?) { |
| this.task = task |
| this.thumbnailData = thumbnailData |
| |
| // Strip alpha channel to make sure that the color is not semi-transparent |
| val color = (task?.colorBackground ?: Color.BLACK) or 0xFF000000.toInt() |
| |
| paint.color = color |
| backgroundPaint.color = color |
| |
| refresh() |
| } |
| |
| override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
| updateThumbnailMatrix() |
| invalidate() |
| } |
| |
| override fun onDraw(canvas: Canvas) { |
| // Always draw the background since the snapshots might be translucent or partially empty |
| // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss |
| // split screen). |
| canvas.drawRoundRect( |
| 0f, |
| 1f, |
| width.toFloat(), |
| (height - 1).toFloat(), |
| cornerRadius.toFloat(), |
| cornerRadius.toFloat(), |
| backgroundPaint |
| ) |
| |
| val drawBackgroundOnly = task == null || bitmapShader == null || thumbnailData == null |
| if (drawBackgroundOnly) { |
| return |
| } |
| |
| // Draw the task thumbnail using bitmap shader in the paint |
| canvas.drawRoundRect( |
| 0f, |
| 0f, |
| width.toFloat(), |
| height.toFloat(), |
| cornerRadius.toFloat(), |
| cornerRadius.toFloat(), |
| paint |
| ) |
| } |
| |
| private fun refresh() { |
| val thumbnailBitmap = thumbnailData?.thumbnail |
| |
| if (thumbnailBitmap != null) { |
| thumbnailBitmap.prepareToDraw() |
| bitmapShader = |
| BitmapShader(thumbnailBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) |
| paint.shader = bitmapShader |
| updateThumbnailMatrix() |
| } else { |
| bitmapShader = null |
| paint.shader = null |
| } |
| |
| invalidate() |
| } |
| |
| private fun updateThumbnailMatrix() { |
| previewPositionHelper.isOrientationChanged = false |
| |
| val bitmapShader = bitmapShader ?: return |
| val thumbnailData = thumbnailData ?: return |
| val display = context.display ?: return |
| val windowMetrics = windowManager.maximumWindowMetrics |
| |
| previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height) |
| |
| val currentRotation: Int = display.rotation |
| val displayWidthPx = windowMetrics.bounds.width() |
| val displayHeightPx = windowMetrics.bounds.height() |
| val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL |
| val isLargeScreen = isLargeScreen(context) |
| val taskbarSize = |
| if (isLargeScreen) { |
| resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) |
| } else { |
| 0 |
| } |
| |
| previewPositionHelper.updateThumbnailMatrix( |
| previewRect, |
| thumbnailData, |
| measuredWidth, |
| measuredHeight, |
| displayWidthPx, |
| displayHeightPx, |
| taskbarSize, |
| isLargeScreen, |
| currentRotation, |
| isRtl |
| ) |
| |
| bitmapShader.setLocalMatrix(previewPositionHelper.matrix) |
| paint.shader = bitmapShader |
| } |
| } |