diff options
author | 2025-03-21 04:14:36 -0700 | |
---|---|---|
committer | 2025-03-21 04:14:36 -0700 | |
commit | df6dc455a7cd5c4ec39b71b2552631b09e6e5a35 (patch) | |
tree | 570f71505d4b52712f256de9d0056ff3e8902663 /quickstep/src | |
parent | cb724aeb2b034643fad1f94f6e6773c0d336e650 (diff) | |
parent | 77ddc254cc43d7037fb3c690da5bb5873f87e3e3 (diff) |
Merge changes from topic "revert_task_content_view" into main
* changes:
Revert "Add TaskContentView parent to TaskThumbnailViewHeader and TTV"
Revert "Remove redundant FrameLayout from task_header_view.xml"
Diffstat (limited to 'quickstep/src')
14 files changed, 260 insertions, 335 deletions
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) } |