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