diff options
Diffstat (limited to 'quickstep')
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>(), |