Change drawing in TTV to use Views rather than Canvas
Fix: 352332386
Test: Presubmit, Manual
Flag: com.android.launcher3.enable_refactor_task_thumbnail
Change-Id: Ia1b7b6dc3d093d431ce022e793f40e61cec38ac6
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index cc3b30e..46c1332 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,7 +28,7 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <include layout="@layout/task_thumbnail" />
+ <include layout="@layout/task_thumbnail_deprecated" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 87a0f70..708aa3c 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,9 +33,9 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
- <include layout="@layout/task_thumbnail"/>
+ <include layout="@layout/task_thumbnail_deprecated"/>
- <include layout="@layout/task_thumbnail"
+ <include layout="@layout/task_thumbnail_deprecated"
android:id="@+id/bottomright_snapshot" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index f1a3d62..34640e6 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -13,8 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.quickstep.views.TaskThumbnailViewDeprecated
+<com.android.quickstep.task.thumbnail.TaskThumbnailView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
\ No newline at end of file
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/task_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ android:visibility="gone"/>
+
+ <com.android.quickstep.task.thumbnail.LiveTileView
+ android:id="@+id/task_thumbnail_live_tile"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+
+ <View
+ android:id="@+id/task_thumbnail_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/overview_foreground_scrim_color"
+ android:alpha="0" />
+
+</com.android.quickstep.task.thumbnail.TaskThumbnailView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_thumbnail_deprecated.xml b/quickstep/res/layout/task_thumbnail_deprecated.xml
new file mode 100644
index 0000000..f1a3d62
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_deprecated.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2024 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.
+-->
+<com.android.quickstep.views.TaskThumbnailViewDeprecated
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/snapshot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt
new file mode 100644
index 0000000..45b3687
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.util.AttributeSet
+import android.view.View
+
+class LiveTileView : View {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ override fun onDraw(canvas: Canvas) {
+ canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT)
+ }
+
+ companion object {
+ private val CLEAR_PAINT =
+ Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 22d49c1..c71b9e7 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -18,17 +18,17 @@
import android.content.Context
import android.content.res.Configuration
-import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Outline
-import android.graphics.Paint
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
+import android.widget.ImageView
import androidx.annotation.ColorInt
+import androidx.core.view.isVisible
+import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
@@ -43,7 +43,7 @@
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
-class TaskThumbnailView : View, ViewPool.Reusable {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
// to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
// This is using a lazy for now because the dependencies cannot be obtained without DI.
@@ -59,12 +59,12 @@
)
}
- private var uiState: TaskThumbnailUiState = Uninitialized
- private var inheritedScale: Float = 1f
- private var dimProgress: Float = 0f
+ private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
+ private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
+ private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) }
- private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
- private val scrimPaint = Paint().apply { color = Color.BLACK }
+ private var inheritedScale: Float = 1f
+
private val _measuredBounds = Rect()
private val measuredBounds: Rect
get() {
@@ -75,12 +75,12 @@
private var overviewCornerRadius: Float = TaskCornerRadius.get(context)
private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
- constructor(context: Context?) : super(context)
+ constructor(context: Context) : super(context)
- constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(
- context: Context?,
+ context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
@@ -90,15 +90,19 @@
// TODO(b/335396935) replace MainScope with shorter lifecycle.
MainScope().launch {
viewModel.uiState.collect { viewModelUiState ->
- uiState = viewModelUiState
- invalidate()
+ resetViews()
+ when (viewModelUiState) {
+ is Uninitialized -> {}
+ is LiveTile -> drawLiveWindow()
+ is Snapshot -> drawSnapshot(viewModelUiState)
+ is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
+ }
}
}
MainScope().launch {
viewModel.dimProgress.collect { dimProgress ->
// TODO(b/348195366) Add fade in/out for scrim
- this@TaskThumbnailView.dimProgress = dimProgress
- invalidate()
+ scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA
}
}
MainScope().launch { viewModel.cornerRadiusProgress.collect { invalidateOutline() } }
@@ -120,25 +124,6 @@
override fun onRecycle() {
// Do nothing
- uiState = Uninitialized
- }
-
- override fun onDraw(canvas: Canvas) {
- when (val uiStateVal = uiState) {
- is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK)
- is LiveTile -> drawTransparentUiState(canvas)
- is Snapshot -> drawSnapshotState(canvas, uiStateVal)
- is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor)
- }
-
- if (dimProgress > 0) {
- drawScrim(canvas)
- }
- }
-
- private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) {
- backgroundPaint.color = backgroundColor
- canvas.drawRect(measuredBounds, backgroundPaint)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -149,18 +134,25 @@
invalidateOutline()
}
- private fun drawTransparentUiState(canvas: Canvas) {
- canvas.drawRect(measuredBounds, CLEAR_PAINT)
+ private fun resetViews() {
+ liveTileView.isVisible = false
+ thumbnail.isVisible = false
+ scrimView.alpha = 0f
+ setBackgroundColor(Color.BLACK)
}
- private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) {
- drawBackgroundOnly(canvas, snapshot.backgroundColor)
- canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null)
+ private fun drawBackground(@ColorInt background: Int) {
+ setBackgroundColor(background)
}
- private fun drawScrim(canvas: Canvas) {
- scrimPaint.alpha = (dimProgress * MAX_SCRIM_ALPHA).toInt()
- canvas.drawRect(measuredBounds, scrimPaint)
+ private fun drawLiveWindow() {
+ liveTileView.isVisible = true
+ }
+
+ private fun drawSnapshot(snapshot: Snapshot) {
+ drawBackground(snapshot.backgroundColor)
+ thumbnail.setImageBitmap(snapshot.bitmap)
+ thumbnail.isVisible = true
}
private fun getCurrentCornerRadius() =
@@ -170,9 +162,7 @@
fullscreenCornerRadius
) / inheritedScale
- companion object {
- private val CLEAR_PAINT =
- Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
- private const val MAX_SCRIM_ALPHA = (0.4f * 255).toInt()
+ private companion object {
+ const val MAX_SCRIM_ALPHA = 0.4f
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 4333c8b..9ce2277 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -24,6 +24,7 @@
import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
@@ -36,7 +37,6 @@
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
@@ -54,7 +54,7 @@
ViewPool<TaskThumbnailViewDeprecated>(
context,
this,
- R.layout.task_thumbnail,
+ R.layout.task_thumbnail_deprecated,
VIEW_POOL_MAX_SIZE,
VIEW_POOL_INITIAL_SIZE
)
@@ -108,22 +108,21 @@
tasks.map { task ->
val snapshotView =
if (enableRefactorTaskThumbnail()) {
- TaskThumbnailView(context)
- } else {
- taskThumbnailViewDeprecatedPool.view
- }
- .also { snapshotView ->
- addView(
- snapshotView,
- // Add snapshotView to the front after initial views e.g. icon and
- // background.
- childCountAtInflation,
- LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- )
- )
- }
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
+ } else {
+ taskThumbnailViewDeprecatedPool.view
+ }
+
+ addView(
+ snapshotView,
+ // Add snapshotView to the front after initial views e.g. icon and
+ // background.
+ childCountAtInflation,
+ LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
TaskContainer(
this,
task,
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 5c95aaa..3209fab 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -31,6 +31,7 @@
import android.util.FloatProperty
import android.util.Log
import android.view.Display
+import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.OnClickListener
@@ -665,9 +666,8 @@
if (enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.visibility = GONE
val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
- TaskThumbnailView(context).apply {
- layoutParams = thumbnailViewDeprecated.layoutParams
- addView(this, indexOfSnapshotView)
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false).also {
+ addView(it, indexOfSnapshotView, thumbnailViewDeprecated.layoutParams)
}
} else {
thumbnailViewDeprecated