summaryrefslogtreecommitdiff
path: root/quickstep
diff options
context:
space:
mode:
Diffstat (limited to 'quickstep')
-rw-r--r--quickstep/res/layout/task.xml4
-rw-r--r--quickstep/res/layout/task_content_view.xml20
-rw-r--r--quickstep/res/layout/task_grouped.xml8
-rw-r--r--quickstep/res/layout/task_header_view.xml62
-rw-r--r--quickstep/res/layout/task_thumbnail.xml3
-rw-r--r--quickstep/res/layout/task_thumbnail_view_header.xml72
-rw-r--r--quickstep/res/values/dimens.xml6
-rw-r--r--quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt2
-rw-r--r--quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt2
-rw-r--r--quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt3
-rw-r--r--quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt85
-rw-r--r--quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt158
-rw-r--r--quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt32
-rw-r--r--quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt36
-rw-r--r--quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt106
-rw-r--r--quickstep/src/com/android/quickstep/util/SplitAnimationController.kt28
-rw-r--r--quickstep/src/com/android/quickstep/views/DesktopTaskView.kt54
-rw-r--r--quickstep/src/com/android/quickstep/views/GroupedTaskView.kt21
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskContainer.kt16
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt (renamed from quickstep/src/com/android/quickstep/views/TaskHeaderView.kt)24
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskView.kt28
-rw-r--r--quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt43
-rw-r--r--quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt124
-rw-r--r--quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt80
-rw-r--r--quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt60
-rw-r--r--quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt2
-rw-r--r--quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt150
-rw-r--r--quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt2
-rw-r--r--quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt2
29 files changed, 474 insertions, 759 deletions
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 4abfbbe152..3aac1b63f9 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -29,8 +29,8 @@
launcher:hoverBorderColor="@color/materialColorPrimary">
<ViewStub
- android:id="@+id/task_content_view"
- android:inflatedId="@id/task_content_view"
+ android:id="@+id/snapshot"
+ android:inflatedId="@id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/quickstep/res/layout/task_content_view.xml b/quickstep/res/layout/task_content_view.xml
deleted file mode 100644
index 9055ccd745..0000000000
--- a/quickstep/res/layout/task_content_view.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2025 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.task.thumbnail.TaskContentView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/task_content_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" /> \ No newline at end of file
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index a7c48561c8..3e6f5eda69 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -34,14 +34,14 @@
launcher:hoverBorderColor="@color/materialColorPrimary">
<ViewStub
- android:id="@+id/task_content_view"
- android:inflatedId="@id/task_content_view"
+ android:id="@+id/snapshot"
+ android:inflatedId="@id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewStub
- android:id="@+id/bottomright_task_content_view"
- android:inflatedId="@id/bottomright_task_content_view"
+ android:id="@+id/bottomright_snapshot"
+ android:inflatedId="@id/bottomright_snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/quickstep/res/layout/task_header_view.xml b/quickstep/res/layout/task_header_view.xml
deleted file mode 100644
index 849153fd9e..0000000000
--- a/quickstep/res/layout/task_header_view.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- Copyright (C) 2025 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.TaskHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/task_header_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/task_thumbnail_header_padding_top_bottom"
- android:paddingBottom="@dimen/task_thumbnail_header_padding_top_bottom"
- android:paddingStart="@dimen/task_thumbnail_header_padding_start_end"
- android:paddingEnd="@dimen/task_thumbnail_header_padding_start_end"
- android:background="@drawable/task_thumbnail_header_bg">
- <ImageView
- android:id="@+id/header_app_icon"
- android:layout_width="@dimen/task_thumbnail_header_icon_size"
- android:layout_height="@dimen/task_thumbnail_header_icon_size"
- android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
- android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
- android:contentDescription="@string/header_app_icon_description"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- <TextView
- android:id="@+id/header_app_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
- android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
- android:maxLines="1"
- android:text="@string/header_default_app_title"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/header_app_icon"
- app:layout_constraintTop_toTopOf="parent" />
- <ImageButton
- android:id="@+id/header_close_button"
- android:layout_width="@dimen/task_thumbnail_header_icon_size"
- android:layout_height="@dimen/task_thumbnail_header_icon_size"
- android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
- android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
- android:background="@null"
- android:contentDescription="@string/header_close_icon_description"
- android:src="@drawable/task_header_close_button"
- android:tint="@android:color/darker_gray"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintStart_toEndOf="@id/header_app_title"
- app:layout_constraintTop_toTopOf="parent" />
-</com.android.quickstep.views.TaskHeaderView>
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 8280e13f5b..3b966159d1 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -17,8 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1" >
+ android:layout_height="match_parent" >
<com.android.quickstep.views.FixedSizeImageView
android:id="@+id/task_thumbnail"
diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_thumbnail_view_header.xml
new file mode 100644
index 0000000000..70e4a42b41
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_view_header.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2025 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.TaskThumbnailViewHeader
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/task_thumbnail_view_header"
+ android:background="@drawable/task_thumbnail_header_bg">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/task_thumbnail_header_height"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_edge"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_edge"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+ <ImageView
+ android:id="@+id/header_app_icon"
+ android:contentDescription="@string/header_app_icon_description"
+ android:layout_width="@dimen/task_thumbnail_header_icon_size"
+ android:layout_height="@dimen/task_thumbnail_header_icon_size"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/header_app_title"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintVertical_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread_inside" />
+ <TextView
+ android:id="@+id/header_app_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+ android:text="@string/header_default_app_title"
+ app:layout_constraintStart_toEndOf="@id/header_app_icon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintVertical_bias="0.5" />
+ <ImageButton
+ android:id="@+id/header_close_button"
+ android:contentDescription="@string/header_close_icon_description"
+ android:layout_width="@dimen/task_thumbnail_header_icon_size"
+ android:layout_height="@dimen/task_thumbnail_header_icon_size"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+ android:src="@drawable/task_header_close_button"
+ android:tint="@android:color/darker_gray"
+ android:background="@null"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_bias="0.5" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.quickstep.views.TaskThumbnailViewHeader>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cb3c446dd9..86d44c978a 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -82,9 +82,9 @@
<dimen name="task_thumbnail_icon_menu_drawable_touch_size">44dp</dimen>
<dimen name="task_thumbnail_icon_menu_elevation">4dp</dimen>
<!-- The size of the task thumbnail header -->
- <dimen name="task_thumbnail_header_padding_top_bottom">6dp</dimen>
- <dimen name="task_thumbnail_header_padding_start_end">12dp</dimen>
- <dimen name="task_thumbnail_header_margin_between_views">8dp</dimen>
+ <dimen name="task_thumbnail_header_height">30dp</dimen>
+ <dimen name="task_thumbnail_header_margin_edge">18dp</dimen>
+ <dimen name="task_thumbnail_header_margin_between_views">9dp</dimen>
<dimen name="task_thumbnail_header_icon_size">18dp</dimen>
<dimen name="task_thumbnail_header_round_corner_radius">16dp</dimen>
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index b8f43a4c36..4b9eb9e77f 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -275,7 +275,7 @@ open class LandscapePagedViewHandler : RecentsPagedOrientationHandler {
desiredTaskId: Int,
banner: View,
): Pair<Float, Float> {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX = banner.height.toFloat()
val translationY: Float
if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
index 15eb69e47a..74ae688bd7 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -255,7 +255,7 @@ class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientat
}
} else {
if (desiredTaskId == splitBounds.leftTopTaskId) {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val bottomRightTaskPlusDividerPercent =
(splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
translationY =
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 80b50cbd83..456115fdca 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -26,7 +26,6 @@ import android.view.Surface
import android.view.View
import android.view.View.MeasureSpec
import android.widget.FrameLayout
-import android.widget.LinearLayout
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.core.view.updateLayoutParams
@@ -152,7 +151,7 @@ class SeascapePagedViewHandler : LandscapePagedViewHandler() {
desiredTaskId: Int,
banner: View,
): Pair<Float, Float> {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX: Float = (taskViewWidth - banner.height).toFloat()
val translationY: Float
if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
index 679daf8ef6..619075ff83 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -17,78 +17,67 @@
package com.android.quickstep.recents.ui.mapper
import android.view.View.OnClickListener
-import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
object TaskUiStateMapper {
/**
- * Converts a [TaskData] object into a [TaskHeaderUiState] for display in the UI.
+ * Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
*
* This function handles different types of [TaskData] and determines the appropriate UI state
* based on the data and provided flags.
*
* @param taskData The [TaskData] to convert. Can be null or a specific subclass.
+ * @param isLiveTile A flag indicating whether the task data represents live tile.
* @param hasHeader A flag indicating whether the UI should display a header.
* @param clickCloseListener A callback when the close button in the UI is clicked.
- * @return A [TaskHeaderUiState] representing the UI state for the given task data.
+ * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
*/
- fun toTaskHeaderState(
+ fun toTaskThumbnailUiState(
taskData: TaskData?,
+ isLiveTile: Boolean,
hasHeader: Boolean,
clickCloseListener: OnClickListener?,
- ): TaskHeaderUiState =
- when {
- taskData !is TaskData.Data -> TaskHeaderUiState.HideHeader
- canHeaderBeCreated(taskData, hasHeader, clickCloseListener) -> {
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- // TODO(http://b/353965691): figure out what to do when `icon` or
- // `titleDescription` is null.
- taskData.icon!!,
- taskData.titleDescription!!,
- clickCloseListener!!,
- )
- )
- }
- else -> TaskHeaderUiState.HideHeader
- }
-
- /**
- * Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
- *
- * This function handles different types of [TaskData] and determines the appropriate UI state
- * based on the data and provided flags.
- *
- * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
- * @param isLiveTile A flag indicating whether the task data represents live tile.
- * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
- */
- fun toTaskThumbnailUiState(taskData: TaskData?, isLiveTile: Boolean): TaskThumbnailUiState =
+ ): TaskThumbnailUiState =
when {
taskData !is TaskData.Data -> Uninitialized
- isLiveTile -> LiveTile
+ isLiveTile -> createLiveTileState(taskData, hasHeader, clickCloseListener)
isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
isSnapshotSplash(taskData) ->
SnapshotSplash(
- Snapshot(
- taskData.thumbnailData?.thumbnail!!,
- taskData.thumbnailData.rotation,
- taskData.backgroundColor,
- ),
+ createSnapshotState(taskData, hasHeader, clickCloseListener),
taskData.icon,
)
-
else -> Uninitialized
}
+ private fun createSnapshotState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ): Snapshot =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+ Snapshot.WithHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!),
+ )
+ } else {
+ Snapshot.WithoutHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ )
+ }
+
private fun isBackgroundOnly(taskData: TaskData.Data) =
taskData.isLocked || taskData.thumbnailData == null
@@ -100,9 +89,21 @@ object TaskUiStateMapper {
hasHeader: Boolean,
clickCloseListener: OnClickListener?,
) =
- enableDesktopExplodedView() &&
- hasHeader &&
+ hasHeader &&
taskData.icon != null &&
taskData.titleDescription != null &&
clickCloseListener != null
+
+ private fun createLiveTileState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ) =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+ // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+ // null.
+ LiveTile.WithHeader(
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!)
+ )
+ } else LiveTile.WithoutHeader
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
deleted file mode 100644
index a40929c5f0..0000000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2025 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.Outline
-import android.graphics.Path
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewOutlineProvider
-import android.widget.LinearLayout
-import androidx.core.view.isInvisible
-import com.android.launcher3.Flags.enableRefactorTaskThumbnail
-import com.android.launcher3.R
-import com.android.launcher3.util.ViewPool
-import com.android.quickstep.views.TaskHeaderView
-import com.android.quickstep.views.TaskThumbnailViewDeprecated
-
-/**
- * TaskContentView is a wrapper around the TaskHeaderView and TaskThumbnailView. It is a sibling to
- * DWB, AiAi (TaskOverlay).
- */
-class TaskContentView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- LinearLayout(context, attrs), ViewPool.Reusable {
-
- private var taskHeaderView: TaskHeaderView? = null
- private var taskThumbnailView: TaskThumbnailView? = null
- private var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated? = null
- private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
- private val outlinePath = Path()
-
- /**
- * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
- */
- var outlineBounds: Rect? = null
- set(value) {
- field = value
- invalidateOutline()
- }
-
- private val bounds = Rect()
-
- var cornerRadius: Float = 0f
- set(value) {
- field = value
- invalidateOutline()
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- createTaskThumbnailView()
- }
-
- override fun setScaleX(scaleX: Float) {
- super.setScaleX(scaleX)
- taskThumbnailView?.parentScaleXUpdated(scaleX)
- }
-
- override fun setScaleY(scaleY: Float) {
- super.setScaleY(scaleY)
- taskThumbnailView?.parentScaleYUpdated(scaleY)
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- clipToOutline = true
- outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- val outlineRect = outlineBounds ?: bounds
- outlinePath.apply {
- rewind()
- addRoundRect(
- outlineRect.left.toFloat(),
- outlineRect.top.toFloat(),
- outlineRect.right.toFloat(),
- outlineRect.bottom.toFloat(),
- cornerRadius / scaleX,
- cornerRadius / scaleY,
- Path.Direction.CW,
- )
- }
- outline.setPath(outlinePath)
- }
- }
- }
-
- override fun onRecycle() {
- taskHeaderView?.isInvisible = true
- onSizeChanged = null
- outlineBounds = null
- taskThumbnailView?.onRecycle()
- taskThumbnailViewDeprecated?.onRecycle()
- }
-
- fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
- onSizeChanged = action
- }
-
- override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
- super.onSizeChanged(w, h, oldw, oldh)
- onSizeChanged?.invoke(width, height)
- bounds.set(0, 0, w, h)
- invalidateOutline()
- }
-
- private fun createHeaderView(taskHeaderState: TaskHeaderUiState) {
- if (taskHeaderView == null && taskHeaderState is TaskHeaderUiState.ShowHeader) {
- taskHeaderView =
- LayoutInflater.from(context).inflate(R.layout.task_header_view, this, false)
- as TaskHeaderView
- addView(taskHeaderView, 0)
- }
- }
-
- private fun createTaskThumbnailView() {
- if (taskThumbnailView == null) {
- if (enableRefactorTaskThumbnail()) {
- taskThumbnailView =
- LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
- as TaskThumbnailView
- addView(taskThumbnailView)
- } else {
- taskThumbnailViewDeprecated =
- LayoutInflater.from(context)
- .inflate(R.layout.task_thumbnail_deprecated, this, false)
- as TaskThumbnailViewDeprecated
- addView(taskThumbnailViewDeprecated)
- }
- }
- }
-
- fun setState(
- taskHeaderState: TaskHeaderUiState,
- taskThumbnailUiState: TaskThumbnailUiState,
- taskId: Int?,
- ) {
- createHeaderView(taskHeaderState)
- taskHeaderView?.setState(taskHeaderState)
- taskThumbnailView?.setState(taskThumbnailUiState, taskId)
- }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
deleted file mode 100644
index 09fb5409b5..0000000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2025 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.graphics.drawable.Drawable
-import android.view.View
-
-sealed class TaskHeaderUiState {
- data class ShowHeader(val header: ThumbnailHeader) : TaskHeaderUiState()
-
- data object HideHeader : TaskHeaderUiState()
-
- data class ThumbnailHeader(
- val icon: Drawable,
- val title: String,
- val clickCloseListener: View.OnClickListener,
- )
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index a5c9ac032f..db593d34d3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -19,6 +19,7 @@ package com.android.quickstep.task.thumbnail
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.Surface
+import android.view.View.OnClickListener
import androidx.annotation.ColorInt
sealed class TaskThumbnailUiState {
@@ -26,14 +27,37 @@ sealed class TaskThumbnailUiState {
data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
- data object LiveTile : TaskThumbnailUiState()
-
data class SnapshotSplash(val snapshot: Snapshot, val splash: Drawable?) :
TaskThumbnailUiState()
- data class Snapshot(
- val bitmap: Bitmap,
- @Surface.Rotation val thumbnailRotation: Int,
- @ColorInt val backgroundColor: Int,
+ sealed class LiveTile : TaskThumbnailUiState() {
+ data class WithHeader(val header: ThumbnailHeader) : LiveTile()
+
+ data object WithoutHeader : LiveTile()
+ }
+
+ sealed class Snapshot {
+ abstract val bitmap: Bitmap
+ abstract val thumbnailRotation: Int
+ abstract val backgroundColor: Int
+
+ data class WithHeader(
+ override val bitmap: Bitmap,
+ @Surface.Rotation override val thumbnailRotation: Int,
+ @ColorInt override val backgroundColor: Int,
+ val header: ThumbnailHeader,
+ ) : Snapshot()
+
+ data class WithoutHeader(
+ override val bitmap: Bitmap,
+ @Surface.Rotation override val thumbnailRotation: Int,
+ @ColorInt override val backgroundColor: Int,
+ ) : Snapshot()
+ }
+
+ data class ThumbnailHeader(
+ val icon: Drawable,
+ val title: String,
+ val clickCloseListener: OnClickListener,
)
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 78a16f1f39..0edbacc710 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -19,24 +19,32 @@ package com.android.quickstep.task.thumbnail
import android.content.Context
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Outline
+import android.graphics.Path
+import android.graphics.Rect
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewOutlineProvider
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.view.isInvisible
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA
import com.android.launcher3.R
import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.ViewPool
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.views.FixedSizeImageView
+import com.android.quickstep.views.TaskThumbnailViewHeader
-class TaskThumbnailView : FrameLayout {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
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 thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
@@ -45,9 +53,30 @@ class TaskThumbnailView : FrameLayout {
private val dimAlpha: MultiPropertyFactory<View> by lazy {
MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
}
+ private val outlinePath = Path()
+ private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
+
+ private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
private var uiState: TaskThumbnailUiState = Uninitialized
+ /**
+ * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
+ */
+ var outlineBounds: Rect? = null
+ set(value) {
+ field = value
+ invalidateOutline()
+ }
+
+ private val bounds = Rect()
+
+ var cornerRadius: Float = 0f
+ set(value) {
+ field = value
+ invalidateOutline()
+ }
+
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -58,8 +87,39 @@ class TaskThumbnailView : FrameLayout {
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
- fun onRecycle() {
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ maybeCreateHeader()
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ clipToOutline = true
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ val outlineRect = outlineBounds ?: bounds
+ outlinePath.apply {
+ rewind()
+ addRoundRect(
+ outlineRect.left.toFloat(),
+ outlineRect.top.toFloat(),
+ outlineRect.right.toFloat(),
+ outlineRect.bottom.toFloat(),
+ cornerRadius / scaleX,
+ cornerRadius / scaleY,
+ Path.Direction.CW,
+ )
+ }
+ outline.setPath(outlinePath)
+ }
+ }
+ }
+
+ override fun onRecycle() {
uiState = Uninitialized
+ onSizeChanged = null
+ outlineBounds = null
resetViews()
}
@@ -70,7 +130,7 @@ class TaskThumbnailView : FrameLayout {
resetViews()
when (state) {
is Uninitialized -> {}
- is LiveTile -> drawLiveWindow()
+ is LiveTile -> drawLiveWindow(state)
is SnapshotSplash -> drawSnapshotSplash(state)
is BackgroundOnly -> drawBackground(state.backgroundColor)
}
@@ -95,12 +155,25 @@ class TaskThumbnailView : FrameLayout {
splashIcon.alpha = value
}
- fun parentScaleXUpdated(scaleX: Float) {
+ fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
+ onSizeChanged = action
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ onSizeChanged?.invoke(width, height)
+ bounds.set(0, 0, w, h)
+ invalidateOutline()
+ }
+
+ override fun setScaleX(scaleX: Float) {
+ super.setScaleX(scaleX)
// Splash icon should ignore scale on TTV
splashIcon.scaleX = 1 / scaleX
}
- fun parentScaleYUpdated(scaleY: Float) {
+ override fun setScaleY(scaleY: Float) {
+ super.setScaleY(scaleY)
// Splash icon should ignore scale on TTV
splashIcon.scaleY = 1 / scaleY
}
@@ -114,14 +187,20 @@ class TaskThumbnailView : FrameLayout {
splashIcon.setImageDrawable(null)
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
+ taskThumbnailViewHeader?.isInvisible = true
}
private fun drawBackground(@ColorInt background: Int) {
setBackgroundColor(background)
}
- private fun drawLiveWindow() {
+ private fun drawLiveWindow(liveTile: LiveTile) {
liveTileView.isInvisible = false
+
+ if (liveTile is LiveTile.WithHeader) {
+ taskThumbnailViewHeader?.isInvisible = false
+ taskThumbnailViewHeader?.setHeader(liveTile.header)
+ }
}
private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
@@ -133,6 +212,11 @@ class TaskThumbnailView : FrameLayout {
}
private fun drawSnapshot(snapshot: Snapshot) {
+ if (snapshot is Snapshot.WithHeader) {
+ taskThumbnailViewHeader?.isInvisible = false
+ taskThumbnailViewHeader?.setHeader(snapshot.header)
+ }
+
drawBackground(snapshot.backgroundColor)
thumbnailView.setImageBitmap(snapshot.bitmap)
thumbnailView.isInvisible = false
@@ -148,6 +232,16 @@ class TaskThumbnailView : FrameLayout {
Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
}
+ private fun maybeCreateHeader() {
+ if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
+ taskThumbnailViewHeader =
+ LayoutInflater.from(context)
+ .inflate(R.layout.task_thumbnail_view_header, this, false)
+ as TaskThumbnailViewHeader
+ addView(taskThumbnailViewHeader)
+ }
+ }
+
private companion object {
const val TAG = "TaskThumbnailView"
private const val MAX_SCRIM_ALPHA = 0.4f
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index d6e553d1b0..96a5733a46 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -192,7 +192,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
taskViewHeight: Int,
isPrimaryTaskSplitting: Boolean,
) {
- val taskContentView = taskContainer.taskContentView
+ val snapshot = taskContainer.snapshotView
val iconView: View = taskContainer.iconView.asView()
if (enableRefactorTaskThumbnail()) {
builder.add(
@@ -241,11 +241,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
builder.add(
- ObjectAnimator.ofFloat(
- taskContentView,
- View.TRANSLATION_X,
- centerThumbnailTranslationX,
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
@@ -254,17 +250,15 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
)
}
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_X, finalScaleX))
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
// Reset other dimensions
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
- taskContentView.scaleY = 1f
+ snapshot.scaleY = 1f
val translateYResetVal: Float =
if (!isPrimaryTaskSplitting) 0f
else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
- builder.add(
- ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_Y, translateYResetVal)
- )
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -287,22 +281,18 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
}
val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
builder.add(
- ObjectAnimator.ofFloat(
- taskContentView,
- View.TRANSLATION_Y,
- centerThumbnailTranslationY,
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
}
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_Y, finalScaleY))
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
// Reset other dimensions
- taskContentView.scaleX = 1f
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_X, 0f))
+ snapshot.scaleX = 1f
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 8876633bd3..8b124555f1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -56,7 +56,6 @@ import com.android.quickstep.recents.di.get
import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.RecentsOrientedState
@@ -78,14 +77,27 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
private val contentViewFullscreenParams = FullscreenDrawParams(context)
- private val taskContentViewPool =
- ViewPool<TaskContentView>(
- context,
- this,
- R.layout.task_content_view,
- VIEW_POOL_MAX_SIZE,
- VIEW_POOL_INITIAL_SIZE,
- )
+ private val taskThumbnailViewDeprecatedPool =
+ if (!enableRefactorTaskThumbnail()) {
+ ViewPool<TaskThumbnailViewDeprecated>(
+ context,
+ this,
+ R.layout.task_thumbnail_deprecated,
+ VIEW_POOL_MAX_SIZE,
+ VIEW_POOL_INITIAL_SIZE,
+ )
+ } else null
+
+ private val taskThumbnailViewPool =
+ if (enableRefactorTaskThumbnail()) {
+ ViewPool<TaskThumbnailView>(
+ context,
+ this,
+ R.layout.task_thumbnail,
+ VIEW_POOL_MAX_SIZE,
+ VIEW_POOL_INITIAL_SIZE,
+ )
+ } else null
private val tempPointF = PointF()
private val lastComputedTaskSize = Rect()
@@ -243,7 +255,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
// for all cases where the progress is non-zero.
if (explodeProgress == 0.0f || explodeProgress == 1.0f) {
// Reset scaling and translation that may have been applied during animation.
- it.taskContentView.apply {
+ it.snapshotView.apply {
scaleX = 1.0f
scaleY = 1.0f
translationX = 0.0f
@@ -251,7 +263,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
}
// Position the task to the same position as it would be on the desktop
- it.taskContentView?.updateLayoutParams<LayoutParams> {
+ it.snapshotView.updateLayoutParams<LayoutParams> {
gravity = Gravity.LEFT or Gravity.TOP
width = taskWidth.toInt()
height = taskHeight.toInt()
@@ -262,7 +274,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
if (
enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail()
) {
- it.taskContentView?.outlineBounds =
+ it.thumbnailView.outlineBounds =
if (intersects(overviewTaskPosition, screenRect))
Rect(overviewTaskPosition).apply {
intersectUnchecked(screenRect)
@@ -279,7 +291,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
} else {
// During the animation, apply translation and scale such that the view is
// transformed to where we want, without triggering layout.
- it.taskContentView.apply {
+ it.snapshotView.apply {
pivotX = 0.0f
pivotY = 0.0f
translationX = taskLeft - left
@@ -313,19 +325,17 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
val backgroundViewIndex = contentView.indexOfChild(backgroundView)
taskContainers =
tasks.map { task ->
- val taskContentView = taskContentViewPool.view
- contentView.addView(taskContentView, backgroundViewIndex + 1)
val snapshotView =
if (enableRefactorTaskThumbnail()) {
- taskContentView.findViewById<TaskThumbnailView>(R.id.snapshot)
+ taskThumbnailViewPool!!.view
} else {
- taskContentView.findViewById<TaskThumbnailViewDeprecated>(R.id.snapshot)
+ taskThumbnailViewDeprecatedPool!!.view
}
+ contentView.addView(snapshotView, backgroundViewIndex + 1)
TaskContainer(
this,
task,
- taskContentView,
snapshotView,
iconView,
TransformingTouchDelegate(iconView.asView()),
@@ -478,8 +488,12 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
}
private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
- contentView.removeView(taskContainer.taskContentView)
- taskContentViewPool.recycle(taskContainer.taskContentView)
+ contentView.removeView(taskContainer.snapshotView)
+ if (enableRefactorTaskThumbnail()) {
+ taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView)
+ } else {
+ taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated)
+ }
}
private fun updateTaskPositions() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 10a2e902cb..faa9e2893b 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -24,6 +24,7 @@ import android.view.View
import android.view.ViewStub
import com.android.internal.jank.Cuj
import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.RunnableList
@@ -77,8 +78,8 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
val splitBoundsConfig = splitBoundsConfig ?: return
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- leftTopTaskContainer.taskContentView,
- rightBottomTaskContainer.taskContentView,
+ leftTopTaskContainer.snapshotView,
+ rightBottomTaskContainer.snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
@@ -94,8 +95,12 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
override fun inflateViewStubs() {
super.inflateViewStubs()
- findViewById<ViewStub>(R.id.bottomright_task_content_view)
- ?.apply { layoutResource = R.layout.task_content_view }
+ findViewById<ViewStub>(R.id.bottomright_snapshot)
+ ?.apply {
+ layoutResource =
+ if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+ else R.layout.task_thumbnail_deprecated
+ }
?.inflate()
findViewById<ViewStub>(R.id.bottomRight_icon)
?.apply {
@@ -123,7 +128,6 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
listOf(
createTaskContainer(
primaryTask,
- R.id.task_content_view,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -133,8 +137,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
),
createTaskContainer(
secondaryTask,
- R.id.bottomright_task_content_view,
- R.id.snapshot,
+ R.id.bottomright_snapshot,
R.id.bottomRight_icon,
R.id.show_windows_right,
R.id.bottomRight_digital_wellbeing_toast,
@@ -237,8 +240,8 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
leftTopTaskContainer.iconView.asView(),
rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
- leftTopTaskContainer.taskContentView.measuredWidth,
- leftTopTaskContainer.taskContentView.measuredHeight,
+ leftTopTaskContainer.snapshotView.measuredWidth,
+ leftTopTaskContainer.snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isLayoutRtl,
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 31ed465349..0e769d0d37 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -32,7 +32,6 @@ import com.android.quickstep.ViewUtils.addAccessibleChildToList
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -41,7 +40,6 @@ import com.android.systemui.shared.recents.model.ThumbnailData
class TaskContainer(
val taskView: TaskView,
val task: Task,
- val taskContentView: TaskContentView,
val snapshotView: View,
val iconView: TaskViewIcon,
/**
@@ -113,8 +111,8 @@ class TaskContainer(
fun destroy() =
traceSection("TaskContainer.destroy") {
digitalWellBeingToast?.destroy()
- taskContentView.scaleX = 1f
- taskContentView.scaleY = 1f
+ snapshotView.scaleX = 1f
+ snapshotView.scaleY = 1f
overlay.reset()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
@@ -179,9 +177,13 @@ class TaskContainer(
clickCloseListener: OnClickListener?,
) =
traceSection("TaskContainer.setState") {
- taskContentView.setState(
- TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
- TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
+ thumbnailView.setState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ state,
+ liveTile,
+ hasHeader,
+ clickCloseListener,
+ ),
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
diff --git a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
index 1fda5a3ace..9a8805bf0f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
@@ -18,33 +18,23 @@ package com.android.quickstep.views
import android.content.Context
import android.util.AttributeSet
+import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isGone
import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-class TaskHeaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- ConstraintLayout(context, attrs) {
+class TaskThumbnailViewHeader
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) }
private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) }
private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) }
- fun setState(taskHeaderState: TaskHeaderUiState) {
- when (taskHeaderState) {
- is TaskHeaderUiState.ShowHeader -> {
- setHeader(taskHeaderState.header)
- isGone = false
- }
- TaskHeaderUiState.HideHeader -> isGone = true
- }
- }
-
- private fun setHeader(header: TaskHeaderUiState.ThumbnailHeader) {
- headerTitleView.text = header.title
+ fun setHeader(header: ThumbnailHeader) {
+ headerTitleView.setText(header.title)
headerIconView.setImageDrawable(header.icon)
headerCloseButton.setOnClickListener(header.clickCloseListener)
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index a108afc34b..fa3fd91615 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -89,7 +89,6 @@ import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
@@ -767,10 +766,13 @@ constructor(
}
protected open fun inflateViewStubs() {
- findViewById<ViewStub>(R.id.task_content_view)
- ?.apply { layoutResource = R.layout.task_content_view }
+ findViewById<ViewStub>(R.id.snapshot)
+ ?.apply {
+ layoutResource =
+ if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+ else R.layout.task_thumbnail_deprecated
+ }
?.inflate()
-
findViewById<ViewStub>(R.id.icon)
?.apply {
layoutResource =
@@ -917,7 +919,6 @@ constructor(
listOf(
createTaskContainer(
task,
- R.id.task_content_view,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -952,9 +953,9 @@ constructor(
taskContainers.forEach { container ->
container.bind()
if (enableRefactorTaskThumbnail()) {
- container.taskContentView.cornerRadius =
+ container.thumbnailView.cornerRadius =
thumbnailFullscreenParams.currentCornerRadius
- container.taskContentView.doOnSizeChange { width, height ->
+ container.thumbnailView.doOnSizeChange { width, height ->
updateThumbnailValidity(container)
val thumbnailPosition = updateThumbnailMatrix(container, width, height)
container.refreshOverlay(thumbnailPosition)
@@ -981,7 +982,6 @@ constructor(
protected fun createTaskContainer(
task: Task,
- @IdRes taskContentViewId: Int,
@IdRes thumbnailViewId: Int,
@IdRes iconViewId: Int,
@IdRes showWindowViewId: Int,
@@ -991,12 +991,10 @@ constructor(
): TaskContainer =
traceSection("TaskView.createTaskContainer") {
val iconView = findViewById<View>(iconViewId) as TaskViewIcon
- val taskContentView = findViewById<TaskContentView>(taskContentViewId)
return TaskContainer(
this,
task,
- taskContentView,
- taskContentView.findViewById(thumbnailViewId),
+ findViewById(thumbnailViewId),
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
@@ -1085,7 +1083,7 @@ constructor(
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- firstTaskContainer?.taskContentView?.updateLayoutParams<LayoutParams> {
+ firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1099,11 +1097,11 @@ constructor(
val thumbnailBounds = Rect()
if (relativeToDragLayer) {
container.dragLayer.getDescendantRectRelativeToSelf(
- it.taskContentView,
+ it.snapshotView,
thumbnailBounds,
)
} else {
- thumbnailBounds.set(it.taskContentView)
+ thumbnailBounds.set(it.snapshotView)
}
bounds.union(thumbnailBounds)
}
@@ -1817,7 +1815,7 @@ constructor(
updateFullscreenParams(thumbnailFullscreenParams)
taskContainers.forEach {
if (enableRefactorTaskThumbnail()) {
- it.taskContentView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
} else {
it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
deleted file mode 100644
index 8cc09d470b..0000000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2025 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.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-
-object SplashHelper {
- private val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
-
- fun createSplash(): Bitmap = createBitmap(width = 20, height = 20, rectColorRotation = 1)
-
- fun createBitmap(width: Int, height: Int, rectColorRotation: Int = 0): Bitmap =
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
- Canvas(this).apply {
- val paint = Paint()
- paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
- drawRect(0f, 0f, width / 2f, height / 2f, paint)
- paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
- drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
- paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
- drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
- paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
- drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
- }
- }
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
deleted file mode 100644
index 7b1e445e7a..0000000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.Color
-import android.graphics.drawable.BitmapDrawable
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.LayoutInflater
-import com.android.launcher3.Flags
-import com.android.launcher3.R
-import com.android.launcher3.util.rule.setFlags
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.Displays
-import platform.test.screenshot.ViewScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** Screenshot tests for [TaskContentView]. */
-@RunWith(ParameterizedAndroidJunit4::class)
-class TaskContentViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
-
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
-
- @get:Rule(order = 1)
- val screenshotRule =
- ViewScreenshotTestRule(
- emulationSpec,
- ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
- )
-
- @Before
- fun setUp() {
- setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
- }
-
- @Test
- fun taskContentView_recyclesToUninitialized() {
- screenshotRule.screenshotTest("taskContentView_uninitialized") { activity ->
- activity.actionBar?.hide()
- val taskContentView = createTaskContentView(activity)
- taskContentView.setState(
- TaskHeaderUiState.HideHeader,
- BackgroundOnly(Color.YELLOW),
- null,
- )
- taskContentView.onRecycle()
- taskContentView
- }
- }
-
- @Test
- fun taskContentView_shows_thumbnail_and_header() {
- screenshotRule.screenshotTest("taskContentView_shows_thumbnail_and_header") { activity ->
- activity.actionBar?.hide()
- createTaskContentView(activity).apply {
- setState(
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- BitmapDrawable(activity.resources, createSplash()),
- "test",
- ) {}
- ),
- BackgroundOnly(Color.YELLOW),
- null,
- )
- }
- }
- }
-
- @Test
- fun taskContentView_scaled_roundRoundedCorners() {
- screenshotRule.screenshotTest("taskContentView_scaledRoundedCorners") { activity ->
- activity.actionBar?.hide()
- createTaskContentView(activity).apply {
- scaleX = 0.75f
- scaleY = 0.3f
- setState(TaskHeaderUiState.HideHeader, BackgroundOnly(Color.YELLOW), null)
- }
- }
- }
-
- private fun createTaskContentView(context: Context): TaskContentView {
- val taskContentView =
- LayoutInflater.from(context).inflate(R.layout.task_content_view, null, false)
- as TaskContentView
- taskContentView.cornerRadius = CORNER_RADIUS
- return taskContentView
- }
-
- companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun getTestSpecs() =
- DeviceEmulationSpec.forDisplays(
- Displays.Phone,
- isDarkTheme = false,
- isLandscape = false,
- )
-
- const val CORNER_RADIUS = 56f
- }
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
deleted file mode 100644
index e30554e647..0000000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2025 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.drawable.BitmapDrawable
-import android.view.LayoutInflater
-import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
-import com.android.quickstep.views.TaskHeaderView
-import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.Displays
-import platform.test.screenshot.ViewScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** Screenshot tests for [TaskHeaderView]. */
-@RunWith(ParameterizedAndroidJunit4::class)
-class TaskHeaderViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
- @get:Rule
- val screenshotRule =
- ViewScreenshotTestRule(
- emulationSpec,
- ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
- )
-
- @Test
- fun taskHeaderView_showHeader() {
- screenshotRule.screenshotTest("taskHeaderView_showHeader") { activity ->
- activity.actionBar?.hide()
- createTaskHeaderView(activity).apply {
- setState(
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- BitmapDrawable(activity.resources, createSplash()),
- "Example",
- ) {}
- )
- )
- }
- }
- }
-
- private fun createTaskHeaderView(context: Context): TaskHeaderView {
- val taskHeaderView =
- LayoutInflater.from(context).inflate(R.layout.task_header_view, null, false)
- as TaskHeaderView
- return taskHeaderView
- }
-
- companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun getTestSpecs() =
- DeviceEmulationSpec.forDisplays(
- Displays.Tablet,
- isDarkTheme = false,
- isLandscape = true,
- )
- }
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 45df73572d..80b2c16e10 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -16,14 +16,16 @@
package com.android.quickstep.task.thumbnail
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Paint
import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
import android.view.Surface.ROTATION_0
+import androidx.core.graphics.set
import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.SplashHelper.createBitmap
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
@@ -88,25 +90,23 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
}
@Test
- fun taskThumbnailView_liveTile() {
+ fun taskThumbnailView_liveTile_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
activity.actionBar?.hide()
- createTaskThumbnailView(activity).apply { setState(TaskThumbnailUiState.LiveTile) }
+ createTaskThumbnailView(activity).apply {
+ setState(TaskThumbnailUiState.LiveTile.WithoutHeader)
+ }
}
}
@Test
- fun taskThumbnailView_image() {
+ fun taskThumbnailView_image_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_image") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
setState(
SnapshotSplash(
- Snapshot(
- createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
- ROTATION_0,
- Color.DKGRAY,
- ),
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
null,
)
)
@@ -115,14 +115,14 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
}
@Test
- fun taskThumbnailView_image_withImageMatrix() {
+ fun taskThumbnailView_image_withoutHeader_withImageMatrix() {
screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
setState(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
createBitmap(
width = VIEW_ENV_WIDTH / 2,
height = lessThanHeightMatchingAspectRatio,
@@ -139,17 +139,13 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
}
@Test
- fun taskThumbnailView_splash() {
+ fun taskThumbnailView_splash_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
setState(
SnapshotSplash(
- Snapshot(
- createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
- ROTATION_0,
- Color.DKGRAY,
- ),
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
BitmapDrawable(activity.resources, createSplash()),
)
)
@@ -159,14 +155,14 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
}
@Test
- fun taskThumbnailView_splash_withImageMatrix() {
+ fun taskThumbnailView_splash_withoutHeader_withImageMatrix() {
screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
setState(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
createBitmap(
width = VIEW_ENV_WIDTH / 2,
height = lessThanHeightMatchingAspectRatio,
@@ -233,9 +229,31 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
val taskThumbnailView =
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
as TaskThumbnailView
+ taskThumbnailView.cornerRadius = CORNER_RADIUS
return taskThumbnailView
}
+ private fun createSplash() = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+ private fun createBitmap(
+ width: Int = VIEW_ENV_WIDTH,
+ height: Int = VIEW_ENV_HEIGHT,
+ rectColorRotation: Int = 0,
+ ) =
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
+ Canvas(this).apply {
+ val paint = Paint()
+ paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
+ drawRect(0f, 0f, width / 2f, height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
+ drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
+ drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
+ paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
+ drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
+ }
+ }
+
companion object {
@Parameters(name = "{0}")
@JvmStatic
@@ -246,6 +264,8 @@ class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
isLandscape = false,
)
+ const val CORNER_RADIUS = 56f
+ val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
const val VIEW_ENV_WIDTH = 1440
const val VIEW_ENV_HEIGHT = 3120
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index d2abed8501..42adfec82c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -35,7 +35,6 @@ import com.android.launcher3.util.UserIconInfo
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.TaskContainer
@@ -199,7 +198,6 @@ class TaskViewItemInfoTest {
return TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
index 6c0d0ed9ea..7ca194afcf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -19,67 +19,55 @@ package com.android.quickstep.recents.ui.mapper
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.view.Surface
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.Flags
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TaskUiStateMapperTest {
- @get:Rule val mSetFlagsRule = SetFlagsRule()
-
- /** TaskHeaderUiState */
@Test
- fun taskData_isNull_returns_HideHeader() {
+ fun taskData_isNull_returns_Uninitialized() {
val result =
- TaskUiStateMapper.toTaskHeaderState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
taskData = null,
+ isLiveTile = false,
hasHeader = false,
clickCloseListener = null,
)
- assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
+ assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
}
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
@Test
- fun explodedFlagDisabled_returnsHideHeader() {
+ fun taskData_isLiveTile_returns_LiveTile() {
val inputs =
- listOf(
- TASK_DATA,
- TASK_DATA.copy(thumbnailData = null),
- TASK_DATA.copy(isLocked = true),
- TASK_DATA.copy(title = null),
- )
- val closeCallback = View.OnClickListener {}
- val expected = TaskHeaderUiState.HideHeader
- inputs.forEach { taskData ->
+ listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
+ inputs.forEach { input ->
val result =
- TaskUiStateMapper.toTaskHeaderState(
- taskData = taskData,
- hasHeader = true,
- clickCloseListener = closeCallback,
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = input,
+ isLiveTile = true,
+ hasHeader = false,
+ clickCloseListener = null,
)
- assertThat(result).isEqualTo(expected)
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
}
}
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
@Test
- fun taskData_hasHeader_and_taskData_returnsShowHeader() {
+ fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
val inputs =
listOf(
TASK_DATA,
@@ -89,18 +77,14 @@ class TaskUiStateMapperTest {
)
val closeCallback = View.OnClickListener {}
val expected =
- TaskHeaderUiState.ShowHeader(
- header =
- TaskHeaderUiState.ThumbnailHeader(
- TASK_ICON,
- TASK_TITLE_DESCRIPTION,
- closeCallback,
- )
+ LiveTile.WithHeader(
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback)
)
inputs.forEach { taskData ->
val result =
- TaskUiStateMapper.toTaskHeaderState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
taskData = taskData,
+ isLiveTile = true,
hasHeader = true,
clickCloseListener = closeCallback,
)
@@ -110,7 +94,7 @@ class TaskUiStateMapperTest {
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
@Test
- fun taskData_hasHeader_emptyTaskData_returns_HideHeader() {
+ fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
val inputs =
listOf(
TASK_DATA.copy(icon = null),
@@ -120,42 +104,30 @@ class TaskUiStateMapperTest {
inputs.forEach { taskData ->
val result =
- TaskUiStateMapper.toTaskHeaderState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
taskData = taskData,
+ isLiveTile = true,
hasHeader = true,
clickCloseListener = {},
)
- assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
- }
- }
-
- /** TaskThumbnailUiState */
- @Test
- fun taskData_isNull_returns_Uninitialized() {
- val result = TaskUiStateMapper.toTaskThumbnailUiState(taskData = null, isLiveTile = false)
- assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
- }
-
- @Test
- fun taskData_isLiveTile_returns_LiveTile() {
- val inputs =
- listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
- inputs.forEach { input ->
- val result =
- TaskUiStateMapper.toTaskThumbnailUiState(taskData = input, isLiveTile = true)
- assertThat(result).isEqualTo(LiveTile)
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
}
}
@Test
fun taskData_isStaticTile_returns_SnapshotSplash() {
val result =
- TaskUiStateMapper.toTaskThumbnailUiState(taskData = TASK_DATA, isLiveTile = false)
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = TASK_DATA,
+ isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
+ )
val expected =
TaskThumbnailUiState.SnapshotSplash(
snapshot =
- Snapshot(
+ Snapshot.WithoutHeader(
backgroundColor = TASK_BACKGROUND_COLOR,
bitmap = TASK_THUMBNAIL,
thumbnailRotation = Surface.ROTATION_0,
@@ -166,12 +138,72 @@ class TaskUiStateMapperTest {
assertThat(result).isEqualTo(expected)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
+ val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+ val closeCallback = View.OnClickListener {}
+ val expected =
+ TaskThumbnailUiState.SnapshotSplash(
+ snapshot =
+ Snapshot.WithHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ bitmap = TASK_THUMBNAIL,
+ thumbnailRotation = Surface.ROTATION_0,
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback),
+ ),
+ splash = TASK_ICON,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ clickCloseListener = closeCallback,
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA.copy(titleDescription = null, icon = null),
+ TASK_DATA.copy(titleDescription = null),
+ TASK_DATA.copy(icon = null),
+ )
+ val expected =
+ Snapshot.WithoutHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ thumbnailRotation = Surface.ROTATION_0,
+ bitmap = TASK_THUMBNAIL,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ clickCloseListener = {},
+ )
+
+ assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
+ result as TaskThumbnailUiState.SnapshotSplash
+ assertThat(result.snapshot).isEqualTo(expected)
+ }
+ }
+
@Test
fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
val result =
TaskUiStateMapper.toTaskThumbnailUiState(
taskData = TASK_DATA.copy(thumbnailData = null),
isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
@@ -184,6 +216,8 @@ class TaskUiStateMapperTest {
TaskUiStateMapper.toTaskThumbnailUiState(
taskData = TASK_DATA.copy(isLocked = true),
isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 746f8bb98e..5f61ba2e07 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -37,7 +37,6 @@ import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -254,7 +253,6 @@ class DesktopSystemShortcutTest {
TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 5aaed7dd25..2db94f6f1a 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -37,7 +37,6 @@ import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -247,7 +246,6 @@ class ExternalDisplaySystemShortcutTest {
TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),