Merge "Refactor: Extract splash alpha logic from TaskThumbnailViewModel" into main
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 1f428f3..d2f10b6 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -28,13 +28,11 @@
 import com.android.quickstep.recents.data.TasksRepository
 import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
 import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
 import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.GetThumbnailUseCase
 import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
@@ -175,14 +173,8 @@
         val instance: Any =
             when (modelClass) {
                 RecentsViewData::class.java -> RecentsViewData()
-                TaskContainerData::class.java -> TaskContainerData()
-                TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
                 TaskThumbnailViewModel::class.java ->
-                    TaskThumbnailViewModelImpl(
-                        dispatcherProvider = inject(),
-                        getThumbnailPositionUseCase = inject(),
-                        splashAlphaUseCase = inject(scopeId),
-                    )
+                    TaskThumbnailViewModelImpl(getThumbnailPositionUseCase = inject())
                 TaskOverlayViewModel::class.java -> {
                     val task = extras["Task"] as Task
                     TaskOverlayViewModel(
@@ -193,6 +185,8 @@
                         dispatcherProvider = inject(),
                     )
                 }
+                IsThumbnailValidUseCase::class.java ->
+                    IsThumbnailValidUseCase(rotationStateRepository = inject())
                 GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject())
                 GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
                 GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
@@ -203,14 +197,6 @@
                         tasksRepository = inject(),
                     )
                 OrganizeDesktopTasksUseCase::class.java -> OrganizeDesktopTasksUseCase()
-                SplashAlphaUseCase::class.java ->
-                    SplashAlphaUseCase(
-                        recentsViewData = inject(),
-                        taskContainerData = inject(scopeId),
-                        taskThumbnailViewData = inject(scopeId),
-                        tasksRepository = inject(),
-                        rotationStateRepository = inject(),
-                    )
                 else -> {
                     log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
                     error("Factory for ${modelClass.simpleName} not defined!")
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
new file mode 100644
index 0000000..02f8329
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities
+
+/**
+ * Use case responsible for validating the aspect ratio and rotation of a thumbnail against the
+ * expected values based on the view's dimensions and the current rotation state.
+ *
+ * This class checks if the thumbnail's aspect ratio significantly differs from the aspect ratio of
+ * the view it is intended to be displayed in, and if the thumbnail's rotation is consistent with
+ * the device's current rotation state.
+ *
+ * @property rotationStateRepository Repository providing the current rotation state of the device.
+ */
+class IsThumbnailValidUseCase(private val rotationStateRepository: RecentsRotationStateRepository) {
+    operator fun invoke(thumbnailData: ThumbnailData?, viewWidth: Int, viewHeight: Int): Boolean {
+        val thumbnail = thumbnailData?.thumbnail ?: return false
+        return !isInaccurateThumbnail(thumbnail, viewWidth, viewHeight, thumbnailData.rotation)
+    }
+
+    private fun isInaccurateThumbnail(
+        thumbnail: Bitmap,
+        viewWidth: Int,
+        viewHeight: Int,
+        rotation: Int,
+    ): Boolean =
+        isAspectRatioDifferentFromViewAspectRatio(
+            thumbnail = thumbnail,
+            width = viewWidth.toFloat(),
+            height = viewHeight.toFloat(),
+        ) || isRotationDifferentFromTask(rotation)
+
+    private fun isAspectRatioDifferentFromViewAspectRatio(
+        thumbnail: Bitmap,
+        width: Float,
+        height: Float,
+    ): Boolean {
+        return Utilities.isRelativePercentDifferenceGreaterThan(
+            /* first = */ width / height,
+            /* second = */ thumbnail.width / thumbnail.height.toFloat(),
+            /* bound = */ PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT,
+        )
+    }
+
+    private fun isRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
+        val rotationState = rotationStateRepository.getRecentsRotationState()
+        return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
+            (rotationState.activityRotation - thumbnailRotation) % 2 != 0
+        } else {
+            rotationState.orientationHandlerRotation != thumbnailRotation
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
index 961446f..162d14d 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -24,8 +24,10 @@
 import com.android.quickstep.recents.domain.model.TaskModel
 import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
 import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.ThumbnailData
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +47,7 @@
     recentsViewData: RecentsViewData,
     private val getTaskUseCase: GetTaskUseCase,
     private val getSysUiStatusNavFlagsUseCase: GetSysUiStatusNavFlagsUseCase,
+    private val isThumbnailValidUseCase: IsThumbnailValidUseCase,
     dispatcherProvider: DispatcherProvider,
 ) {
     private var taskIds = MutableStateFlow(emptySet<Int>())
@@ -78,6 +81,9 @@
         taskIds.value = taskId.toSet()
     }
 
+    fun isThumbnailValid(thumbnail: ThumbnailData?, width: Int, height: Int): Boolean =
+        isThumbnailValidUseCase(thumbnail, width, height)
+
     private fun mapToTaskTile(tasks: List<TaskData>, isLiveTile: Boolean): TaskTileUiState {
         val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
         return TaskTileUiState(
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index a1f8454..2465a46 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -27,8 +27,6 @@
     // The settled set of visible taskIds that is updated after RecentsView scroll settles.
     val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
 
-    val thumbnailSplashProgress = MutableStateFlow(0f)
-
     // A list of taskIds that are associated with a RecentsAnimationController. */
     val runningTaskIds = MutableStateFlow(emptySet<Int>())
 
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index 73332fc..5ff8aaa 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -42,10 +42,6 @@
         recentsViewData.overlayEnabled.value = isOverlayEnabled
     }
 
-    fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
-        recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
-    }
-
     suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>?) {
         if (updatedThumbnails.isNullOrEmpty()) return
         combine(
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
deleted file mode 100644
index 723df55..0000000
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ /dev/null
@@ -1,26 +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.recents.viewmodel
-
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
-
-class TaskContainerViewModel(private val splashAlphaUseCase: SplashAlphaUseCase) {
-    fun shouldShowThumbnailSplash(taskId: Int): Boolean =
-        (runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
deleted file mode 100644
index 7673c71..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
+++ /dev/null
@@ -1,90 +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.graphics.Bitmap
-import android.view.Surface
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.data.RecentsRotationStateRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import com.android.systemui.shared.recents.utilities.Utilities
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-
-class SplashAlphaUseCase(
-    private val recentsViewData: RecentsViewData,
-    private val taskContainerData: TaskContainerData,
-    private val taskThumbnailViewData: TaskThumbnailViewData,
-    private val tasksRepository: RecentTasksRepository,
-    private val rotationStateRepository: RecentsRotationStateRepository,
-) {
-    fun execute(taskId: Int): Flow<Float> =
-        combine(
-                taskThumbnailViewData.width,
-                taskThumbnailViewData.height,
-                tasksRepository.getThumbnailById(taskId),
-                taskContainerData.thumbnailSplashProgress,
-                recentsViewData.thumbnailSplashProgress
-            ) { width, height, thumbnailData, taskSplashProgress, globalSplashProgress ->
-                val thumbnail = thumbnailData?.thumbnail
-                when {
-                    thumbnail == null -> 0f
-                    taskSplashProgress > 0f -> taskSplashProgress
-                    globalSplashProgress > 0f &&
-                        isInaccurateThumbnail(thumbnail, thumbnailData.rotation, width, height) ->
-                        globalSplashProgress
-                    else -> 0f
-                }
-            }
-            .distinctUntilChanged()
-
-    private fun isInaccurateThumbnail(
-        thumbnail: Bitmap,
-        thumbnailRotation: Int,
-        width: Int,
-        height: Int
-    ): Boolean {
-        return isThumbnailAspectRatioDifferentFromThumbnailData(thumbnail, width, height) ||
-            isThumbnailRotationDifferentFromTask(thumbnailRotation)
-    }
-
-    private fun isThumbnailAspectRatioDifferentFromThumbnailData(
-        thumbnail: Bitmap,
-        viewWidth: Int,
-        viewHeight: Int
-    ): Boolean {
-        val viewAspect: Float = viewWidth / viewHeight.toFloat()
-        val thumbnailAspect: Float = thumbnail.width / thumbnail.height.toFloat()
-        return Utilities.isRelativePercentDifferenceGreaterThan(
-            viewAspect,
-            thumbnailAspect,
-            PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT
-        )
-    }
-
-    private fun isThumbnailRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
-        val rotationState = rotationStateRepository.getRecentsRotationState()
-        return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
-            (rotationState.activityRotation - thumbnailRotation) % 2 != 0
-        } else {
-            rotationState.orientationHandlerRotation != thumbnailRotation
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 28152ec..63e93ba 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -50,10 +50,6 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.dropWhile
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
@@ -62,8 +58,6 @@
 
     // This is initialised here and set in onAttachedToWindow because onLayout can be called before
     // onAttachedToWindow so this property needs to be initialised as it is used below.
-    private var viewData: TaskThumbnailViewData = RecentsDependencies.get(this)
-
     private lateinit var viewModel: TaskThumbnailViewModel
 
     private lateinit var viewAttachedScope: CoroutineScope
@@ -110,18 +104,7 @@
             CoroutineScope(
                 SupervisorJob() + Dispatchers.Main.immediate + CoroutineName("TaskThumbnailView")
             )
-        viewData = RecentsDependencies.get(this)
-        updateViewDataValues()
         viewModel = RecentsDependencies.get(this)
-        viewModel.splashAlpha
-            .dropWhile { it == 0f }
-            .flowOn(dispatcherProvider.background)
-            .onEach { splashAlpha ->
-                splashBackground.alpha = splashAlpha
-                splashIcon.alpha = splashAlpha
-            }
-            .launchIn(viewAttachedScope)
-
         clipToOutline = true
         outlineProvider =
             object : ViewOutlineProvider() {
@@ -144,16 +127,9 @@
         resetViews()
     }
 
-    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        super.onLayout(changed, left, top, right, bottom)
-        if (changed) {
-            updateViewDataValues()
-        }
-    }
-
     fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
-        logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
         if (uiState == state) return
+        logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
         uiState = state
         resetViews()
         when (state) {
@@ -164,6 +140,12 @@
         }
     }
 
+    /**
+     * Updates the alpha of the dim layer on top of this view. If dimAlpha is 0, no dimming is
+     * applied; if dimAlpha is 1, the thumbnail will be the extracted background color.
+     *
+     * @param tintAmount The amount of alpha that will be applied to the dim layer.
+     */
     fun updateTintAmount(tintAmount: Float) {
         dimAlpha[ScrimViewAlpha.TintAmount.ordinal].value = tintAmount
     }
@@ -172,9 +154,9 @@
         dimAlpha[ScrimViewAlpha.MenuProgress.ordinal].value = progress * MAX_SCRIM_ALPHA
     }
 
-    private fun updateViewDataValues() {
-        viewData.width.value = width
-        viewData.height.value = height
+    fun updateSplashAlpha(value: Float) {
+        splashBackground.alpha = value
+        splashIcon.alpha = value
     }
 
     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
deleted file mode 100644
index 3502029..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
+++ /dev/null
@@ -1,24 +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 kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskThumbnailViewData {
-    val width = MutableStateFlow(0)
-    val height = MutableStateFlow(0)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
deleted file mode 100644
index 279ce39..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ /dev/null
@@ -1,23 +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.viewmodel
-
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskContainerData {
-    val thumbnailSplashProgress = MutableStateFlow(0f)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index c89bf01..e641737 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -17,13 +17,9 @@
 package com.android.quickstep.task.viewmodel
 
 import android.graphics.Matrix
-import kotlinx.coroutines.flow.Flow
 
 /** ViewModel for representing TaskThumbnails */
 interface TaskThumbnailViewModel {
-    /** Provides the alpha of the splash icon */
-    val splashAlpha: Flow<Float>
-
     /** Attaches this ViewModel to a specific task id for it to provide data from. */
     fun bind(taskId: Int)
 
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index 635d08b..94c40d1 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -19,32 +19,17 @@
 import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.graphics.Matrix
 import android.util.Log
-import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
 
-@OptIn(ExperimentalCoroutinesApi::class)
 class TaskThumbnailViewModelImpl(
-    dispatcherProvider: DispatcherProvider,
-    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
-    private val splashAlphaUseCase: SplashAlphaUseCase,
+    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
 ) : TaskThumbnailViewModel {
-    private val splashProgress = MutableStateFlow(flowOf(0f))
     private var taskId: Int = INVALID_TASK_ID
 
-    override val splashAlpha =
-        splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
-
     override fun bind(taskId: Int) {
         Log.d(TAG, "bind taskId: $taskId")
         this.taskId = taskId
-        splashProgress.value = splashAlphaUseCase.execute(taskId)
     }
 
     override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix =
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 0182969..99255e8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -52,9 +52,9 @@
 import com.android.launcher3.QuickstepTransitionManager
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.apppairs.AppPairIcon
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.StatsLogManager.EventEnum
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.statehandlers.DepthController
@@ -94,7 +94,7 @@
             val fadeWithThumbnail: Boolean,
             val isStagedTask: Boolean,
             val iconView: View?,
-            val contentDescription: CharSequence?
+            val contentDescription: CharSequence?,
         )
     }
 
@@ -104,7 +104,7 @@
      */
     fun getFirstAnimInitViews(
         taskViewSupplier: Supplier<TaskView>,
-        splitSelectSourceSupplier: Supplier<SplitSelectSource?>
+        splitSelectSourceSupplier: Supplier<SplitSelectSource?>,
     ): SplitAnimInitProps {
         val splitSelectSource = splitSelectSourceSupplier.get()
         if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
@@ -116,7 +116,7 @@
                 fadeWithThumbnail = false,
                 isStagedTask = true,
                 iconView = null,
-                splitSelectSource.itemInfo.contentDescription
+                splitSelectSource.itemInfo.contentDescription,
             )
         } else if (splitSelectStateController.isDismissingFromSplitPair) {
             // Initiating split from overview, but on a split pair
@@ -131,7 +131,7 @@
                         fadeWithThumbnail = true,
                         isStagedTask = true,
                         iconView = container.iconView.asView(),
-                        container.task.titleDescription
+                        container.task.titleDescription,
                     )
                 }
             }
@@ -151,7 +151,7 @@
                     fadeWithThumbnail = true,
                     isStagedTask = true,
                     iconView = it.iconView.asView(),
-                    it.task.titleDescription
+                    it.task.titleDescription,
                 )
             }
         }
@@ -189,29 +189,25 @@
         deviceProfile: DeviceProfile,
         taskViewWidth: Int,
         taskViewHeight: Int,
-        isPrimaryTaskSplitting: Boolean
+        isPrimaryTaskSplitting: Boolean,
     ) {
         val snapshot = taskContainer.snapshotView
         val iconView: View = taskContainer.iconView.asView()
-        if (!enableRefactorTaskThumbnail()) {
+        if (enableRefactorTaskThumbnail()) {
+            builder.add(
+                AnimatedFloat { v -> taskContainer.taskView.splitSplashAlpha = v }
+                    .animateToValue(1f)
+            )
+        } else {
             val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
             builder.add(
                 ObjectAnimator.ofFloat(
                     thumbnailViewDeprecated,
                     TaskThumbnailViewDeprecated.SPLASH_ALPHA,
-                    1f
+                    1f,
                 )
             )
             thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
-        } else {
-            builder.add(
-                ValueAnimator.ofFloat(0f, 1f).apply {
-                    addUpdateListener {
-                        taskContainer.taskContainerData.thumbnailSplashProgress.value =
-                            it.animatedFraction
-                    }
-                }
-            )
         }
         // With the new `IconAppChipView`, we always want to keep the chip pinned to the
         // top left of the task / thumbnail.
@@ -220,7 +216,7 @@
                 ObjectAnimator.ofFloat(
                     (iconView as IconAppChipView).splitTranslationX,
                     MULTI_PROPERTY_VALUE,
-                    0f
+                    0f,
                 )
             )
             builder.add(
@@ -306,7 +302,7 @@
     fun addScrimBehindAnim(
         pendingAnimation: PendingAnimation,
         container: RecentsViewContainer,
-        context: Context
+        context: Context,
     ): View {
         val scrim = View(context)
         val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
@@ -334,8 +330,8 @@
             Interpolators.clampToProgress(
                 timings.backingScrimFadeInterpolator,
                 timings.backingScrimFadeInStartOffset,
-                timings.backingScrimFadeInEndOffset
-            )
+                timings.backingScrimFadeInEndOffset,
+            ),
         )
 
         return scrim
@@ -358,7 +354,7 @@
     fun createPlaceholderDismissAnim(
         container: RecentsViewContainer,
         splitDismissEvent: EventEnum,
-        duration: Long?
+        duration: Long?,
     ): AnimatorSet {
         val animatorSet = AnimatorSet()
         duration?.let { animatorSet.duration = it }
@@ -375,7 +371,7 @@
             Rect(0, 0, floatingTask.width, floatingTask.height),
             false,
             null,
-            onScreenRectF
+            onScreenRectF,
         )
         // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
         // on-screen portion)
@@ -383,7 +379,7 @@
             dragLayer.left.toFloat(),
             dragLayer.top.toFloat(),
             dragLayer.right.toFloat(),
-            dragLayer.bottom.toFloat()
+            dragLayer.bottom.toFloat(),
         )
         animatorSet.play(
             ObjectAnimator.ofFloat(
@@ -393,8 +389,8 @@
                     floatingTask,
                     onScreenRectF,
                     floatingTask.stagePosition,
-                    container.deviceProfile
-                )
+                    container.deviceProfile,
+                ),
             )
         )
         animatorSet.addListener(
@@ -403,7 +399,7 @@
                     splitSelectStateController.resetState()
                     safeRemoveViewFromDragLayer(
                         container,
-                        splitSelectStateController.splitInstructionsView
+                        splitSelectStateController.splitInstructionsView,
                     )
                 }
             }
@@ -429,8 +425,8 @@
             Interpolators.clampToProgress(
                 Interpolators.LINEAR,
                 timings.instructionsContainerFadeInStartOffset,
-                timings.instructionsContainerFadeInEndOffset
-            )
+                timings.instructionsContainerFadeInEndOffset,
+            ),
         )
         anim.addFloat(
             splitInstructionsView,
@@ -440,8 +436,8 @@
             Interpolators.clampToProgress(
                 Interpolators.EMPHASIZED_DECELERATE,
                 timings.instructionsUnfoldStartOffset,
-                timings.instructionsUnfoldEndOffset
-            )
+                timings.instructionsUnfoldEndOffset,
+            ),
         )
         return anim
     }
@@ -459,7 +455,7 @@
     fun playAnimPlaceholderToFullscreen(
         container: RecentsViewContainer,
         view: View,
-        resetCallback: Optional<Runnable>
+        resetCallback: Optional<Runnable>,
     ) {
         val stagedTaskView = view as FloatingTaskView
 
@@ -481,7 +477,7 @@
             RectF(firstTaskStartingBounds),
             firstTaskEndingBounds,
             false /* fadeWithThumbnail */,
-            true /* isStagedTask */
+            true, /* isStagedTask */
         )
 
         pendingAnimation.addEndListener {
@@ -511,7 +507,7 @@
         info: TransitionInfo?,
         t: Transaction?,
         finishCallback: Runnable,
-        cornerRadius: Float
+        cornerRadius: Float,
     ) {
         if (info == null && t == null) {
             // (Legacy animation) Tapping a split tile in Overview
@@ -530,7 +526,7 @@
                 nonApps,
                 stateManager,
                 depthController,
-                finishCallback
+                finishCallback,
             )
 
             return
@@ -548,7 +544,7 @@
                 depthController,
                 info,
                 t,
-                finishCallback
+                finishCallback,
             )
         } else if (launchingIconView != null) {
             // Tapping an app pair icon
@@ -563,7 +559,7 @@
                     info,
                     t,
                     finishCallback,
-                    cornerRadius
+                    cornerRadius,
                 )
             } else {
                 composeFullscreenIconSplitLaunchAnimator(
@@ -571,7 +567,7 @@
                     info,
                     t,
                     finishCallback,
-                    appPairLaunchingAppIndex
+                    appPairLaunchingAppIndex,
                 )
             }
         } else {
@@ -587,7 +583,7 @@
                 info,
                 t,
                 finishCallback,
-                cornerRadius
+                cornerRadius,
             )
         }
     }
@@ -603,7 +599,7 @@
         depthController: DepthController?,
         info: TransitionInfo,
         t: Transaction,
-        finishCallback: Runnable
+        finishCallback: Runnable,
     ) {
         TaskViewUtils.composeRecentsSplitLaunchAnimator(
             launchingTaskView,
@@ -611,7 +607,7 @@
             depthController,
             info,
             t,
-            finishCallback
+            finishCallback,
         )
     }
 
@@ -629,7 +625,7 @@
         nonApps: Array<RemoteAnimationTarget>,
         stateManager: StateManager<*, *>,
         depthController: DepthController?,
-        finishCallback: Runnable
+        finishCallback: Runnable,
     ) {
         TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
             launchingTaskView,
@@ -640,7 +636,7 @@
             nonApps,
             stateManager,
             depthController,
-            finishCallback
+            finishCallback,
         )
     }
 
@@ -651,7 +647,7 @@
      */
     fun hasChangesForBothAppPairs(
         launchingIconView: AppPairIcon,
-        transitionInfo: TransitionInfo
+        transitionInfo: TransitionInfo,
     ): Int {
         val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
         val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
@@ -712,7 +708,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        windowRadius: Float
+        windowRadius: Float,
     ) {
         // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
         // use the scale-up animation
@@ -721,7 +717,7 @@
                 transitionInfo,
                 t,
                 finishCallback,
-                WINDOWING_MODE_MULTI_WINDOW
+                WINDOWING_MODE_MULTI_WINDOW,
             )
             return
         }
@@ -753,8 +749,7 @@
                     (!isLeftRightSplit && change.endAbsBounds.top <= 0)
             }
         val dividerPos =
-            if (isLeftRightSplit) leftTopApp.endAbsBounds.right
-            else leftTopApp.endAbsBounds.bottom
+            if (isLeftRightSplit) leftTopApp.endAbsBounds.right else leftTopApp.endAbsBounds.bottom
 
         // Create a new floating view in Launcher, positioned above the launching icon
         val drawableArea = launchingIconView.iconDrawableArea
@@ -769,7 +764,7 @@
                 drawableArea,
                 appIcon1,
                 appIcon2,
-                dividerPos
+                dividerPos,
             )
         floatingView.bringToFront()
 
@@ -780,7 +775,7 @@
                 finishCallback,
                 launcher,
                 floatingView,
-                mainRootCandidate
+                mainRootCandidate,
             )
         iconLaunchValueAnimator.addListener(
             object : AnimatorListenerAdapter() {
@@ -806,7 +801,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        launchFullscreenIndex: Int
+        launchFullscreenIndex: Int,
     ) {
         // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
         // use the scale-up animation
@@ -815,7 +810,7 @@
                 transitionInfo,
                 t,
                 finishCallback,
-                WINDOWING_MODE_FULLSCREEN
+                WINDOWING_MODE_FULLSCREEN,
             )
             return
         }
@@ -867,7 +862,7 @@
                 drawableArea,
                 appIcon,
                 null /*appIcon2*/,
-                0 /*dividerPos*/
+                0, /*dividerPos*/
             )
         floatingView.bringToFront()
         launchAnimation.play(
@@ -882,7 +877,7 @@
         finishCallback: Runnable,
         launcher: QuickstepLauncher,
         floatingView: FloatingAppPairView,
-        rootCandidate: Change
+        rootCandidate: Change,
     ): ValueAnimator {
         val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
         val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
@@ -896,7 +891,7 @@
                     Interpolators.LINEAR,
                     valueAnimator.animatedFraction,
                     timings.appRevealStartOffset,
-                    timings.appRevealEndOffset
+                    timings.appRevealEndOffset,
                 )
 
             // Set the alpha of the shell layer (2 apps + divider)
@@ -913,8 +908,8 @@
                         Interpolators.clampToProgress(
                             timings.getStagedRectXInterpolator(),
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mDy =
                     FloatProp(
@@ -923,8 +918,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mScaleX =
                     FloatProp(
@@ -933,8 +928,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mScaleY =
                     FloatProp(
@@ -943,8 +938,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
 
                 override fun onUpdate(percent: Float, initOnly: Boolean) {
@@ -979,7 +974,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        windowingMode: Int
+        windowingMode: Int,
     ) {
         val launchAnimation = AnimatorSet()
         val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
@@ -1066,7 +1061,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        cornerRadius: Float
+        cornerRadius: Float,
     ) {
         var splitRoot1: Change? = null
         var splitRoot2: Change? = null
@@ -1131,7 +1126,7 @@
                     Interpolators.LINEAR,
                     valueAnimator.animatedFraction,
                     0.8f,
-                    1f
+                    1f,
                 )
             for (leash in openingTargets) {
                 animTransaction.setAlpha(leash, progress)
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 99bfa7e..a76ebdb 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3541,11 +3541,6 @@
     }
 
     private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
-            return;
-        }
-
         mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
         for (TaskView taskView : getTaskViews()) {
             taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index b6f6bed..bbe1af4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -25,16 +25,14 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.ViewUtils.addAccessibleChildToList
 import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
 import com.android.quickstep.recents.di.getScope
 import com.android.quickstep.recents.di.inject
 import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
 import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.recents.viewmodel.TaskContainerViewModel
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
 
 /** Holder for all Task dependent information. */
 class TaskContainer(
@@ -56,20 +54,14 @@
     taskOverlayFactory: TaskOverlayFactory,
 ) {
     val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
-    lateinit var taskContainerData: TaskContainerData
 
+    // TODO(b/390581380): Remove this after this bug is fixed
     private val taskThumbnailViewModel: TaskThumbnailViewModel by
         RecentsDependencies.inject(snapshotView)
 
-    // TODO(b/335649589): Ideally create and obtain this from DI.
-    private val taskContainerViewModel: TaskContainerViewModel by lazy {
-        TaskContainerViewModel(splashAlphaUseCase = RecentsDependencies.get())
-    }
-
     init {
         if (enableRefactorTaskThumbnail()) {
             require(snapshotView is TaskThumbnailView)
-            taskContainerData = RecentsDependencies.get(this)
             RecentsDependencies.getScope(snapshotView).apply {
                 val taskViewScope = RecentsDependencies.getScope(taskView)
                 linkTo(taskViewScope)
@@ -82,9 +74,11 @@
         }
     }
 
-    var splitAnimationThumbnail: Bitmap? = null
-        get() = if (enableRefactorTaskThumbnail()) field else thumbnailViewDeprecated.thumbnail
-        private set
+    internal var thumbnailData: ThumbnailData? = null
+    val splitAnimationThumbnail: Bitmap?
+        get() =
+            if (enableRefactorTaskThumbnail()) thumbnailData?.thumbnail
+            else thumbnailViewDeprecated.thumbnail
 
     val thumbnailView: TaskThumbnailView
         get() {
@@ -98,10 +92,12 @@
             return snapshotView as TaskThumbnailViewDeprecated
         }
 
+    var isThumbnailValid: Boolean = false
+        internal set
+
     val shouldShowSplashView: Boolean
         get() =
-            if (enableRefactorTaskThumbnail())
-                taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
+            if (enableRefactorTaskThumbnail()) taskView.shouldShowSplash()
             else thumbnailViewDeprecated.shouldShowSplashView()
 
     /** Builds proto for logging */
@@ -111,7 +107,7 @@
     fun bind() {
         digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
         if (enableRefactorTaskThumbnail()) {
-            bindThumbnailView()
+            taskThumbnailViewModel.bind(task.key.id)
         } else {
             thumbnailViewDeprecated.bind(task, overlay, taskView)
         }
@@ -126,6 +122,9 @@
         if (enableRefactorTaskThumbnail()) {
             RecentsDependencies.getInstance().removeScope(snapshotView)
             RecentsDependencies.getInstance().removeScope(this)
+            isThumbnailValid = false
+        } else {
+            thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
         }
     }
 
@@ -134,10 +133,6 @@
         thumbnailView.destroyScopes()
     }
 
-    private fun bindThumbnailView() {
-        taskThumbnailViewModel.bind(task.key.id)
-    }
-
     fun setOverlayEnabled(enabled: Boolean) {
         if (!enableRefactorTaskThumbnail()) {
             thumbnailViewDeprecated.setOverlayEnabled(enabled)
@@ -157,15 +152,41 @@
             TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader),
             state?.taskId,
         )
-        splitAnimationThumbnail =
-            if (state is TaskData.Data) state.thumbnailData?.thumbnail else null
+        thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
     }
 
     fun updateTintAmount(tintAmount: Float) {
         thumbnailView.updateTintAmount(tintAmount)
     }
 
+    /**
+     * Updates the progress of the menu opening animation.
+     *
+     * This function propagates the given `progress` value to the `thumbnailView` allowing the
+     * thumbnail view to animate its visual state in sync with the menu's opening/closing
+     * transition.
+     *
+     * @param progress The progress of the menu opening animation (from closed=0 to fully open=1)
+     */
     fun updateMenuOpenProgress(progress: Float) {
         thumbnailView.updateMenuOpenProgress(progress)
     }
+
+    /**
+     * Updates the thumbnail splash progress for a given task.
+     *
+     * This function manages the visual feedback of a "splash" effect that can be displayed over a
+     * thumbnail image, typically during loading or updating. It calculates the alpha (transparency)
+     * of the splash based on the provided progress and then applies this alpha to the thumbnail
+     * view if it should be displayed.
+     *
+     * @param progress The progress of the operation, ranging from 0.0 to 1.0
+     */
+    fun updateThumbnailSplashProgress(progress: Float) {
+        if (enableRefactorTaskThumbnail()) {
+            thumbnailView.updateSplashAlpha(progress)
+        } else {
+            thumbnailViewDeprecated.setSplashAlpha(progress)
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 0465dbc..5093259 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -41,6 +41,7 @@
 import android.widget.Toast
 import androidx.annotation.IntDef
 import androidx.annotation.VisibleForTesting
+import androidx.core.view.doOnLayout
 import androidx.core.view.updateLayoutParams
 import com.android.app.animation.Interpolators
 import com.android.launcher3.Flags.enableCursorHoverStates
@@ -330,6 +331,12 @@
             onModalnessUpdated(field)
         }
 
+    var splitSplashAlpha = 0f
+        set(value) {
+            field = value
+            applyThumbnailSplashAlpha()
+        }
+
     protected var taskThumbnailSplashAlpha = 0f
         set(value) {
             field = value
@@ -647,6 +654,8 @@
         viewModel = null
         attachAlpha = 1f
         splitAlpha = 1f
+        splitSplashAlpha = 0f
+        taskThumbnailSplashAlpha = 0f
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
         if (!enableRefactorTaskThumbnail()) {
@@ -769,9 +778,20 @@
                 liveTile = state.isLiveTile,
                 hasHeader = type == TaskViewType.DESKTOP,
             )
+            updateThumbnailValidity(container)
         }
     }
 
+    private fun updateThumbnailValidity(container: TaskContainer) {
+        container.isThumbnailValid =
+            viewModel!!.isThumbnailValid(
+                thumbnail = container.thumbnailData,
+                width = container.thumbnailView.width,
+                height = container.thumbnailView.height,
+            )
+        applyThumbnailSplashAlpha()
+    }
+
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
         if (enableRefactorTaskThumbnail()) {
@@ -808,7 +828,7 @@
         onBind(orientedState)
     }
 
-    open fun onBind(orientedState: RecentsOrientedState) {
+    protected open fun onBind(orientedState: RecentsOrientedState) {
         if (enableRefactorTaskThumbnail()) {
             viewModel =
                 TaskViewModel(
@@ -816,20 +836,37 @@
                         recentsViewData = RecentsDependencies.get(),
                         getTaskUseCase = RecentsDependencies.get(),
                         getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
+                        isThumbnailValidUseCase = RecentsDependencies.get(),
                         dispatcherProvider = RecentsDependencies.get(),
                     )
                     .apply { bind(*taskIds) }
         }
 
-        taskContainers.forEach {
-            it.bind()
+        taskContainers.forEach { container ->
+            container.bind()
             if (enableRefactorTaskThumbnail()) {
-                it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                container.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                container.thumbnailView.doOnLayout { updateThumbnailValidity(container) }
             }
         }
         setOrientationState(orientedState)
     }
 
+    private fun applyThumbnailSplashAlpha() {
+        val alpha = getSplashAlphaProgress()
+        taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) }
+    }
+
+    private fun getSplashAlphaProgress(): Float =
+        when {
+            !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha
+            splitSplashAlpha > 0f -> splitSplashAlpha
+            shouldShowSplash() -> taskThumbnailSplashAlpha
+            else -> 0f
+        }
+
+    internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid }
+
     protected fun createTaskContainer(
         task: Task,
         @IdRes thumbnailViewId: Int,
@@ -1295,6 +1332,7 @@
                     if (isQuickSwitch) {
                         setFreezeRecentTasksReordering()
                     }
+                    // TODO(b/331754864): Update this to use TV.shouldShowSplash
                     disableStartingWindow = firstTaskContainer.shouldShowSplashView
                 }
         Executors.UI_HELPER_EXECUTOR.execute {
@@ -1585,14 +1623,6 @@
         updateFullscreenParams()
     }
 
-    protected open fun applyThumbnailSplashAlpha() {
-        if (!enableRefactorTaskThumbnail()) {
-            taskContainers.forEach {
-                it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
-            }
-        }
-    }
-
     private fun applyTranslationX() {
         translationX =
             dismissTranslationX +
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
index 3e0c186..1a2b1c3 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -18,11 +18,8 @@
 
 import android.graphics.Matrix
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
-    override val splashAlpha = MutableStateFlow(0f)
-
     override fun bind(taskId: Int) {
         // no-op
     }
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 356080a..232a08a 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
@@ -125,7 +125,6 @@
                 as TaskThumbnailView
         taskThumbnailView.cornerRadius = CORNER_RADIUS
         val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
-        di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
         di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
 
         return taskThumbnailView
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt
new file mode 100644
index 0000000..e8bca93
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class IsThumbnailValidUseCaseTest {
+    private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
+    private val systemUnderTest = IsThumbnailValidUseCase(recentsRotationStateRepository)
+
+    @Test
+    fun withNullThumbnail_returnsInvalid() = runTest {
+        val isThumbnailValid = systemUnderTest(thumbnailData = null, viewWidth = 0, viewHeight = 0)
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun sameAspectRatio_sameRotation_returnsValid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(),
+                viewWidth = THUMBNAIL_WIDTH * 2,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(true)
+    }
+
+    @Test
+    fun differentAspectRatio_sameRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(),
+                viewWidth = THUMBNAIL_WIDTH,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun sameAspectRatio_differentRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(rotation = ROTATION_90),
+                viewWidth = THUMBNAIL_WIDTH * 2,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun differentAspectRatio_differentRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(rotation = ROTATION_90),
+                viewWidth = THUMBNAIL_WIDTH,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    private fun createThumbnailData(
+        rotation: Int = Surface.ROTATION_0,
+        width: Int = THUMBNAIL_WIDTH,
+        height: Int = THUMBNAIL_HEIGHT,
+    ): ThumbnailData {
+        val bitmap = mock<Bitmap>()
+        whenever(bitmap.width).thenReturn(width)
+        whenever(bitmap.height).thenReturn(height)
+        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+    }
+
+    companion object {
+        const val THUMBNAIL_WIDTH = 100
+        const val THUMBNAIL_HEIGHT = 200
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
index c031150..08e459b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -25,9 +25,11 @@
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
 import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
 import com.android.quickstep.recents.domain.model.TaskModel
 import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
 import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -41,6 +43,10 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
@@ -52,6 +58,8 @@
 
     private val recentsViewData = RecentsViewData()
     private val getTaskUseCase = mock<GetTaskUseCase>()
+    private val isThumbnailValidUseCase =
+        spy(IsThumbnailValidUseCase(FakeRecentsRotationStateRepository()))
     private lateinit var sut: TaskViewModel
 
     @Before
@@ -62,6 +70,7 @@
                 recentsViewData = recentsViewData,
                 getTaskUseCase = getTaskUseCase,
                 getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+                isThumbnailValidUseCase = isThumbnailValidUseCase,
                 dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
             )
         whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
@@ -102,6 +111,7 @@
                         recentsViewData = recentsViewData,
                         getTaskUseCase = getTaskUseCase,
                         getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+                        isThumbnailValidUseCase = isThumbnailValidUseCase,
                         dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
                     )
                 sut.bind(TASK_MODEL_1.id)
@@ -225,6 +235,12 @@
             assertThat(sut.state.first()).isEqualTo(expectedResult)
         }
 
+    @Test
+    fun shouldShowSplash_calls_useCase() {
+        sut.isThumbnailValid(null, 0, 0)
+        verify(isThumbnailValidUseCase).invoke(anyOrNull(), anyInt(), anyInt())
+    }
+
     private fun TaskModel.toUiState() =
         TaskData.Data(
             taskId = id,
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
deleted file mode 100644
index 0767fb9..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
+++ /dev/null
@@ -1,149 +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.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.view.Surface
-import android.view.Surface.ROTATION_90
-import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-class SplashAlphaUseCaseTest {
-    private val recentsViewData = RecentsViewData()
-    private val taskContainerData = TaskContainerData()
-    private val taskThumbnailViewData = TaskThumbnailViewData()
-    private val recentTasksRepository = FakeTasksRepository()
-    private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
-    private val systemUnderTest =
-        SplashAlphaUseCase(
-            recentsViewData,
-            taskContainerData,
-            taskThumbnailViewData,
-            recentTasksRepository,
-            recentsRotationStateRepository,
-        )
-
-    @Test
-    fun execute_withNullThumbnail_showsSplash() = runTest {
-        assertThat(systemUnderTest.execute(0).first()).isEqualTo(SPLASH_HIDDEN)
-    }
-
-    @Test
-    fun execute_withTaskSpecificSplashAlpha_showsSplash() = runTest {
-        setupTask(2)
-        taskContainerData.thumbnailSplashProgress.value = 0.7f
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.7f)
-    }
-
-    @Test
-    fun execute_withNoGlobalSplashEnabled_doesntShowSplash() = runTest {
-        setupTask(2)
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
-    }
-
-    @Test
-    fun execute_withSameAspectRatioAndRotation_withGlobalSplashEnabled_doesntShowSplash() =
-        runTest {
-            setupTask(2)
-            recentsViewData.thumbnailSplashProgress.value = 0.5f
-            taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
-            taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-            assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
-        }
-
-    @Test
-    fun execute_withDifferentAspectRatioAndSameRotation_showsSplash() = runTest {
-        setupTask(2)
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    @Test
-    fun execute_withSameAspectRatioAndDifferentRotation_showsSplash() = runTest {
-        setupTask(2, createThumbnailData(rotation = ROTATION_90))
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    @Test
-    fun execute_withDifferentAspectRatioAndRotation_showsSplash() = runTest {
-        setupTask(2, createThumbnailData(rotation = ROTATION_90))
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    private val tasks = (0..5).map(::createTaskWithId)
-
-    private fun setupTask(taskId: Int, thumbnailData: ThumbnailData = createThumbnailData()) {
-        recentTasksRepository.seedThumbnailData(mapOf(taskId to thumbnailData))
-        val expectedIconData = mock<Drawable>()
-        recentTasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-        recentTasksRepository.seedTasks(tasks)
-        recentTasksRepository.setVisibleTasks(setOf(taskId))
-    }
-
-    private fun createThumbnailData(
-        rotation: Int = Surface.ROTATION_0,
-        width: Int = THUMBNAIL_WIDTH,
-        height: Int = THUMBNAIL_HEIGHT,
-    ): ThumbnailData {
-        val bitmap = mock<Bitmap>()
-        whenever(bitmap.width).thenReturn(width)
-        whenever(bitmap.height).thenReturn(height)
-
-        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
-    }
-
-    private fun createTaskWithId(taskId: Int) =
-        Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.argb(taskId, taskId, taskId, taskId)
-        }
-
-    companion object {
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-
-        const val SPLASH_HIDDEN = 0f
-        const val SPLASH_SHOWN = 1f
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index aec586d..4b4e2eb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -19,7 +19,6 @@
 import android.graphics.Matrix
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.util.TestDispatcherProvider
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
@@ -42,17 +41,9 @@
     private val dispatcher = StandardTestDispatcher()
     private val testScope = TestScope(dispatcher)
 
-    private val dispatcherProvider = TestDispatcherProvider(dispatcher)
     private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
-    private val splashAlphaUseCase: SplashAlphaUseCase = mock()
 
-    private val systemUnderTest by lazy {
-        TaskThumbnailViewModelImpl(
-            dispatcherProvider,
-            mGetThumbnailPositionUseCase,
-            splashAlphaUseCase,
-        )
-    }
+    private val systemUnderTest by lazy { TaskThumbnailViewModelImpl(mGetThumbnailPositionUseCase) }
 
     @Test
     fun getSnapshotMatrix_MissingThumbnail() =