Merge "Allow trackpad to tune RAPID_DECELERATION_FACTOR for gesture nav" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 21b9863..a779641 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -316,3 +316,16 @@
     description: "Archived apps will use new icon in app title"
     bug: "350758155"
 }
+
+flag {
+    name: "enable_multi_instance_menu_taskbar"
+    namespace: "launcher"
+    description: "Menu in Taskbar with options to launch and manage multiple instances of the same app"
+    bug: "355237285"
+}
+flag {
+    name: "navigate_to_child_preference"
+    namespace: "launcher"
+    description: "Settings screen supports navigating to child preference if the key is not on the screen"
+    bug: "293390881"
+}
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index b1fe89e..784a094 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -39,4 +39,16 @@
         android:background="@color/overview_foreground_scrim_color"
         android:alpha="0" />
 
+    <FrameLayout
+        android:id="@+id/splash_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone">
+        <ImageView
+            android:id="@+id/splash_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:importantForAccessibility="no" />
+    </FrameLayout>
 </com.android.quickstep.task.thumbnail.TaskThumbnailView>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 63412e9..8bcbb33 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -299,10 +299,16 @@
     <string name="taskbar_button_quick_settings">Quick Settings</string>
     <!-- Accessibility title for the Taskbar window. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_a11y_title">Taskbar</string>
-    <!-- Accessibility title for the Taskbar window appeared. [CHAR_LIMIT=NONE] -->
+    <!-- Accessibility title for the Taskbar window appeared. [CHAR_LIMIT=30] -->
     <string name="taskbar_a11y_shown_title">Taskbar shown</string>
-    <!-- Accessibility title for the Taskbar window being close. [CHAR_LIMIT=NONE] -->
+    <!-- Accessibility title for the Taskbar window appearing together with bubble bar on left. [CHAR_LIMIT=30] -->
+    <string name="taskbar_a11y_shown_with_bubbles_left_title">Taskbar &#38; bubbles left shown</string>
+    <!-- Accessibility title for the Taskbar window appearing together with bubble bar on right. [CHAR_LIMIT=30] -->
+    <string name="taskbar_a11y_shown_with_bubbles_right_title">Taskbar &#38; bubbles right shown</string>
+    <!-- Accessibility title for the Taskbar window being closed. [CHAR_LIMIT=30] -->
     <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
+    <!-- Accessibility title for the Taskbar window being closed together with bubble bar. [CHAR_LIMIT=30] -->
+    <string name="taskbar_a11y_hidden_with_bubbles_title">Taskbar &#38; bubbles hidden</string>
     <!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_phone_a11y_title">Navigation bar</string>
     <!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 44601b7..fe1b403 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1252,15 +1252,25 @@
         TransitionFilter homeCheck = new TransitionFilter();
         // No need to handle the transition that also dismisses keyguard.
         homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+
         homeCheck.mRequirements =
                 new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
+                        new TransitionFilter.Requirement(),
                         new TransitionFilter.Requirement()};
+
         homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
         homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
         homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
         homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
+
         homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
         homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+
+        homeCheck.mRequirements[2].mNot = true;
+        homeCheck.mRequirements[2].mCustomAnimation = true;
+        homeCheck.mRequirements[2].mMustBeTask = true;
+        homeCheck.mRequirements[2].mMustBeIndependent = true;
+
         SystemUiProxy.INSTANCE.get(mLauncher)
                 .registerRemoteTransition(mLauncherOpenTransition, homeCheck);
         if (mBackAnimationController != null) {
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index a16031d..92d9516 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -290,6 +290,9 @@
         writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
         writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
         writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
-        writer.println(prefix + "\tmPredictedApps: " + mPredictedApps);
+        writer.println(prefix + "\tmPredictedApps: " + mPredictedApps.size());
+        for (WorkspaceItemInfo info : mPredictedApps) {
+            writer.println(prefix + "\t\t" + info);
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index de974ec..c50e82d 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -536,6 +536,9 @@
         writer.println(prefix + "HotseatPredictionController");
         writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
         writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
-        writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
+        writer.println(prefix + "\tmPredictedItems: " + mPredictedItems.size());
+        for (ItemInfo info : mPredictedItems) {
+            writer.println(prefix + "\t\t" + info);
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 93a023d..252ebf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -272,6 +272,10 @@
      * app launch animation.
      */
     public void setIgnoreInAppFlagForSync(boolean enabled) {
+        if (mControllers == null) {
+            // This method can be called before init() is called.
+            return;
+        }
         mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c42d6c6..e58069a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -72,6 +72,7 @@
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 import java.util.List;
 import java.util.function.Predicate;
@@ -246,12 +247,34 @@
     @Override
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
         if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
-            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
+            announceTaskbarShown();
         } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
-            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
+            announceTaskbarHidden();
         }
         return super.performAccessibilityActionInternal(action, arguments);
+    }
 
+    private void announceTaskbarShown() {
+        BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
+        if (bubbleBarLocation == null) {
+            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_shown_title));
+        } else if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
+            announceForAccessibility(
+                    mContext.getString(R.string.taskbar_a11y_shown_with_bubbles_left_title));
+        } else {
+            announceForAccessibility(
+                    mContext.getString(R.string.taskbar_a11y_shown_with_bubbles_right_title));
+        }
+    }
+
+    private void announceTaskbarHidden() {
+        BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
+        if (bubbleBarLocation == null) {
+            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
+        } else {
+            announceForAccessibility(
+                    mContext.getString(R.string.taskbar_a11y_hidden_with_bubbles_title));
+        }
     }
 
     protected void announceAccessibilityChanges() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 3c646cb..e6cac2f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -23,8 +23,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.jank.Cuj;
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 /**
  * Callbacks for {@link TaskbarView} to interact with its controller.
@@ -104,4 +108,18 @@
         mControllers.taskbarScrimViewController.onTaskbarVisibilityChanged(
                 mTaskbarView.getVisibility());
     }
+
+    /**
+     * Get current location of bubble bar, if it is visible.
+     * Returns {@code null} if bubble bar is not shown.
+     */
+    @Nullable
+    public BubbleBarLocation getBubbleBarLocationIfVisible() {
+        BubbleBarViewController bubbleBarViewController =
+                mControllers.bubbleControllers.map(c -> c.bubbleBarViewController).orElse(null);
+        if (bubbleBarViewController != null && bubbleBarViewController.isBubbleBarVisible()) {
+            return bubbleBarViewController.getBubbleBarLocation();
+        }
+        return null;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 5d550ae..af371f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -307,6 +307,17 @@
         }
     }
 
+    @Override
+    public void setAlpha(float alpha) {
+        super.setAlpha(alpha);
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View childView = getChildAt(i);
+            if (!(childView instanceof BubbleView)) continue;
+            ((BubbleView) childView).setProvideShadowOutline(alpha == 1f);
+        }
+    }
+
     /**
      * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
      *
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 3bcaa16..6db42a4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -70,6 +70,8 @@
     // The current scale value of the dot
     private float mDotScale;
 
+    private boolean mProvideShadowOutline = true;
+
     // TODO: (b/273310265) handle RTL
     // Whether the bubbles are positioned on the left or right side of the screen
     private boolean mOnLeft = false;
@@ -113,17 +115,28 @@
         });
     }
 
+    //TODO(b/345490679) remove once proper shadow is applied
+    /** Set whether provide an outline. */
+    public void setProvideShadowOutline(boolean provideOutline) {
+        if (mProvideShadowOutline == provideOutline) return;
+        mProvideShadowOutline = provideOutline;
+        invalidateOutline();
+    }
+
     private void getOutline(Outline outline) {
         updateBubbleSizeAndDotRender();
         final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
         final int inset = (mBubbleSize - normalizedSize) / 2;
-        outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
+        if (mProvideShadowOutline) {
+            outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
+        }
     }
 
     private void updateBubbleSizeAndDotRender() {
         int updatedBubbleSize = Math.min(getWidth(), getHeight());
         if (updatedBubbleSize == mBubbleSize) return;
         mBubbleSize = updatedBubbleSize;
+        invalidateOutline();
         if (mBubble == null || mBubble instanceof BubbleBarOverflow) return;
         Path dotPath = ((BubbleBarBubble) mBubble).getDotPath();
         mDotRenderer = new DotRenderer(mBubbleSize, dotPath, DEFAULT_PATH_SIZE);
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
new file mode 100644
index 0000000..feed2fd
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.data
+
+/**
+ * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
+ *
+ * @property isLargeScreen whether the current device posture has a large screen
+ */
+data class RecentsDeviceProfile(
+    val isLargeScreen: Boolean,
+    val widthPx: Int,
+    val heightPx: Int,
+)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
index adf904c..13cf56d 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepository.kt
@@ -16,21 +16,6 @@
 
 package com.android.quickstep.recents.data
 
-import com.android.quickstep.views.RecentsViewContainer
-
-/**
- * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
- * data related to Recents.
- */
-class RecentsDeviceProfileRepository(private val container: RecentsViewContainer) {
-
-    fun getRecentsDeviceProfile() =
-        with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
-
-    /**
-     * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
-     *
-     * @property isLargeScreen whether the current device posture has a large screen
-     */
-    data class RecentsDeviceProfile(val isLargeScreen: Boolean)
+interface RecentsDeviceProfileRepository {
+    fun getRecentsDeviceProfile(): RecentsDeviceProfile
 }
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
new file mode 100644
index 0000000..ce39ff1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.data
+
+import com.android.quickstep.views.RecentsViewContainer
+
+/**
+ * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
+ * data related to Recents.
+ */
+class RecentsDeviceProfileRepositoryImpl(private val container: RecentsViewContainer) :
+    RecentsDeviceProfileRepository {
+
+    override fun getRecentsDeviceProfile() =
+        with(container.deviceProfile) {
+            RecentsDeviceProfile(isLargeScreen = isTablet, widthPx = widthPx, heightPx = heightPx)
+        }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt
new file mode 100644
index 0000000..2c2a744
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationState.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.data
+
+import android.view.Surface
+
+/**
+ * Container to hold orientation/rotation related information related to Recents.
+ *
+ * @property activityRotation rotation of the activity hosting RecentsView
+ */
+data class RecentsRotationState(
+    @Surface.Rotation val activityRotation: Int,
+    @Surface.Rotation val orientationHandlerRotation: Int,
+)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
index 6ead704..ed074d2 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepository.kt
@@ -16,20 +16,6 @@
 
 package com.android.quickstep.recents.data
 
-import com.android.quickstep.util.RecentsOrientedState
-
-/**
- * Repository for [RecentsRotationState] which holds orientation/rotation related information
- * related to Recents
- */
-class RecentsRotationStateRepository(private val state: RecentsOrientedState) {
-    fun getRecentsRotationState() =
-        with(state) { RecentsRotationState(activityRotation = recentsActivityRotation) }
-
-    /**
-     * Container to hold orientation/rotation related information related to Recents.
-     *
-     * @property activityRotation rotation of the activity hosting RecentsView
-     */
-    data class RecentsRotationState(val activityRotation: Int)
+interface RecentsRotationStateRepository {
+    fun getRecentsRotationState(): RecentsRotationState
 }
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt
new file mode 100644
index 0000000..8417b06
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.data
+
+import com.android.quickstep.util.RecentsOrientedState
+
+/**
+ * Repository for [RecentsRotationState] which holds orientation/rotation related information
+ * related to Recents
+ */
+class RecentsRotationStateRepositoryImpl(private val state: RecentsOrientedState) :
+    RecentsRotationStateRepository {
+    override fun getRecentsRotationState() =
+        with(state) {
+            RecentsRotationState(
+                activityRotation = recentsActivityRotation,
+                orientationHandlerRotation = orientationHandler.rotation
+            )
+        }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index f73db5a..71be75b 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -130,9 +130,15 @@
                                 icon,
                                 contentDescription,
                                 title ->
-                                continuation.resume(
-                                    TaskIconQueryResponse(icon, contentDescription, title)
-                                )
+                                icon.constantState?.let {
+                                    continuation.resume(
+                                        TaskIconQueryResponse(
+                                            it.newDrawable().mutate(),
+                                            contentDescription,
+                                            title
+                                        )
+                                    )
+                                }
                             }
                         continuation.invokeOnCancellation { cancellableTask?.cancel() }
                     }
@@ -157,7 +163,7 @@
     }
 }
 
-private data class TaskIconQueryResponse(
+data class TaskIconQueryResponse(
     val icon: Drawable,
     val contentDescription: String,
     val title: String
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 3a6d5c0..eba7688 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -26,6 +26,9 @@
 import com.android.quickstep.recents.usecase.GetThumbnailUseCase
 import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
 import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.GetSplashSizeUseCase
+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
@@ -33,7 +36,6 @@
 import com.android.quickstep.task.viewmodel.TaskViewModel
 import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.Task
-import java.util.logging.Level
 
 internal typealias RecentsScopeId = String
 
@@ -145,13 +147,16 @@
                     TaskViewData(taskViewType)
                 }
                 TaskContainerData::class.java -> TaskContainerData()
+                TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
                 TaskThumbnailViewModel::class.java ->
                     TaskThumbnailViewModel(
                         recentsViewData = inject(),
                         taskViewData = inject(scopeId, extras),
-                        taskContainerData = inject(),
+                        taskContainerData = inject(scopeId),
                         getThumbnailPositionUseCase = inject(),
-                        tasksRepository = inject()
+                        tasksRepository = inject(),
+                        splashAlphaUseCase = inject(scopeId),
+                        getSplashSizeUseCase = inject(scopeId),
                     )
                 TaskOverlayViewModel::class.java -> {
                     val task = extras["Task"] as Task
@@ -171,6 +176,20 @@
                         rotationStateRepository = inject(),
                         tasksRepository = inject()
                     )
+                SplashAlphaUseCase::class.java ->
+                    SplashAlphaUseCase(
+                        recentsViewData = inject(),
+                        taskContainerData = inject(scopeId),
+                        taskThumbnailViewData = inject(scopeId),
+                        tasksRepository = inject(),
+                        rotationStateRepository = inject(),
+                    )
+                GetSplashSizeUseCase::class.java ->
+                    GetSplashSizeUseCase(
+                        taskThumbnailViewData = inject(scopeId),
+                        taskViewData = inject(scopeId, extras),
+                        deviceProfileRepository = 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/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index fdb62df..f5e0243 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -31,4 +31,9 @@
 
     // The settled set of visible taskIds that is updated after RecentsView scroll settles.
     val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
+
+    // Color tint on foreground scrim
+    val tintAmount = MutableStateFlow(0f)
+
+    val thumbnailSplashProgress = MutableStateFlow(0f)
 }
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index 8b03a84..6148d4b 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -45,4 +45,12 @@
     fun setOverlayEnabled(isOverlayEnabled: Boolean) {
         recentsViewData.overlayEnabled.value = isOverlayEnabled
     }
+
+    fun setTintAmount(tintAmount: Float) {
+        recentsViewData.tintAmount.value = tintAmount
+    }
+
+    fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
+        recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
index 8b8bc3e..168c1e0 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
@@ -19,13 +19,20 @@
 import android.graphics.Bitmap
 import com.android.quickstep.recents.usecase.GetThumbnailUseCase
 import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
 
 class TaskContainerViewModel(
     private val sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase,
-    private val getThumbnailUseCase: GetThumbnailUseCase
+    private val getThumbnailUseCase: GetThumbnailUseCase,
+    private val splashAlphaUseCase: SplashAlphaUseCase,
 ) {
     fun getThumbnail(taskId: Int): Bitmap? = getThumbnailUseCase.run(taskId)
 
     fun getSysUiStatusNavFlags(taskId: Int) =
         sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(taskId)
+
+    fun shouldShowThumbnailSplash(taskId: Int): Boolean =
+        (runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt
new file mode 100644
index 0000000..145957a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCase.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
+import com.android.quickstep.task.viewmodel.TaskViewData
+import kotlin.math.min
+
+class GetSplashSizeUseCase(
+    private val taskThumbnailViewData: TaskThumbnailViewData,
+    private val taskViewData: TaskViewData,
+    private val deviceProfileRepository: RecentsDeviceProfileRepository,
+) {
+    fun execute(splashImage: Drawable): Point {
+        val recentsDeviceProfile = deviceProfileRepository.getRecentsDeviceProfile()
+        val screenWidth = recentsDeviceProfile.widthPx
+        val screenHeight = recentsDeviceProfile.heightPx
+        val scaleAtFullscreen =
+            min(
+                screenWidth / taskThumbnailViewData.width.value,
+                screenHeight / taskThumbnailViewData.height.value,
+            )
+        val scaleFactor: Float = 1f / taskViewData.nonGridScale.value / scaleAtFullscreen
+        return Point(
+            (splashImage.intrinsicWidth * scaleFactor / taskThumbnailViewData.scaleX.value).toInt(),
+            (splashImage.intrinsicHeight * scaleFactor / taskThumbnailViewData.scaleY.value)
+                .toInt(),
+        )
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
new file mode 100644
index 0000000..e5618fc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
@@ -0,0 +1,90 @@
+/*
+ * 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 -> 1f
+                    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/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 3b3a811..aa7d26c 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -17,6 +17,9 @@
 package com.android.quickstep.task.thumbnail
 
 import android.graphics.Bitmap
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import android.view.Surface
 import androidx.annotation.ColorInt
 
 sealed class TaskThumbnailUiState {
@@ -26,8 +29,21 @@
 
     data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
 
-    data class Snapshot(val bitmap: Bitmap, @ColorInt val backgroundColor: Int) :
-        TaskThumbnailUiState()
+    data class SnapshotSplash(
+        val snapshot: Snapshot,
+        val splash: Splash,
+    ) : TaskThumbnailUiState()
+
+    data class Snapshot(
+        val bitmap: Bitmap,
+        @Surface.Rotation val thumbnailRotation: Int,
+        @ColorInt val backgroundColor: Int
+    )
+
+    data class Splash(
+        val icon: Drawable?,
+        val size: Point,
+    )
 }
 
 data class TaskThumbnail(val taskId: Int, val isRunning: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index fcc2af3..41aee52 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -28,6 +28,7 @@
 import android.widget.ImageView
 import androidx.annotation.ColorInt
 import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.util.ViewPool
@@ -36,10 +37,12 @@
 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.task.viewmodel.TaskThumbnailViewModel
 import com.android.quickstep.util.TaskCornerRadius
 import com.android.systemui.shared.system.QuickStepContract
+import kotlin.math.abs
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -50,6 +53,7 @@
 
 class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
 
+    private val viewData: TaskThumbnailViewData by RecentsDependencies.inject(this)
     private val viewModel: TaskThumbnailViewModel by RecentsDependencies.inject(this)
 
     private lateinit var viewAttachedScope: CoroutineScope
@@ -57,6 +61,8 @@
     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: ImageView by lazy { findViewById(R.id.task_thumbnail) }
+    private val splashContainer: FrameLayout by lazy { findViewById(R.id.splash_container) }
+    private val splashIcon: ImageView by lazy { findViewById(R.id.splash_icon) }
 
     private var uiState: TaskThumbnailUiState = Uninitialized
     private var inheritedScale: Float = 1f
@@ -92,16 +98,16 @@
                 when (viewModelUiState) {
                     is Uninitialized -> {}
                     is LiveTile -> drawLiveWindow()
-                    is Snapshot -> drawSnapshot(viewModelUiState)
+                    is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
                     is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
                 }
             }
             .launchIn(viewAttachedScope)
         viewModel.dimProgress
-            .onEach { dimProgress ->
-                // TODO(b/348195366) Add fade in/out for scrim
-                scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA
-            }
+            .onEach { dimProgress -> scrimView.alpha = dimProgress }
+            .launchIn(viewAttachedScope)
+        viewModel.splashAlpha
+            .onEach { splashAlpha -> splashContainer.alpha = splashAlpha }
             .launchIn(viewAttachedScope)
         viewModel.cornerRadiusProgress.onEach { invalidateOutline() }.launchIn(viewAttachedScope)
         viewModel.inheritedScale
@@ -129,13 +135,35 @@
         uiState = Uninitialized
     }
 
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        if (changed) {
+            viewData.width.value = abs(right - left)
+            viewData.height.value = abs(bottom - top)
+        }
+    }
+
     override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
         super.onSizeChanged(w, h, oldw, oldh)
-        if (uiState is Snapshot) {
+        if (uiState is SnapshotSplash) {
             setImageMatrix()
         }
     }
 
+    override fun setScaleX(scaleX: Float) {
+        super.setScaleX(scaleX)
+        viewData.scaleX.value = scaleX
+        // Splash icon should ignore scale
+        splashIcon.scaleX = 1 / scaleX
+    }
+
+    override fun setScaleY(scaleY: Float) {
+        super.setScaleY(scaleY)
+        viewData.scaleY.value = scaleY
+        // Splash icon should ignore scale
+        splashIcon.scaleY = 1 / scaleY
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration?) {
         super.onConfigurationChanged(newConfig)
 
@@ -147,6 +175,7 @@
     private fun resetViews() {
         liveTileView.isVisible = false
         thumbnailView.isVisible = false
+        splashContainer.alpha = 0f
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
     }
@@ -159,6 +188,18 @@
         liveTileView.isVisible = true
     }
 
+    private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
+        drawSnapshot(snapshotSplash.snapshot)
+
+        splashContainer.isVisible = true
+        splashContainer.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
+        splashIcon.setImageDrawable(snapshotSplash.splash.icon)
+        splashIcon.updateLayoutParams<LayoutParams> {
+            width = snapshotSplash.splash.size.x
+            height = snapshotSplash.splash.size.y
+        }
+    }
+
     private fun drawSnapshot(snapshot: Snapshot) {
         drawBackground(snapshot.backgroundColor)
         thumbnailView.setImageBitmap(snapshot.bitmap)
@@ -176,8 +217,4 @@
             overviewCornerRadius,
             fullscreenCornerRadius
         ) / inheritedScale
-
-    private companion object {
-        const val MAX_SCRIM_ALPHA = 0.4f
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
new file mode 100644
index 0000000..1f8c0bc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
@@ -0,0 +1,26 @@
+/*
+ * 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)
+    val scaleX = MutableStateFlow(1f)
+    val scaleY = MutableStateFlow(1f)
+}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
index 769424c..5f2de94 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
@@ -20,4 +20,6 @@
 
 class TaskContainerData {
     val taskMenuOpenProgress = MutableStateFlow(0f)
+
+    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 6465645..de33919 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -18,18 +18,24 @@
 
 import android.annotation.ColorInt
 import android.graphics.Matrix
+import android.graphics.Point
 import androidx.core.graphics.ColorUtils
 import com.android.quickstep.recents.data.RecentTasksRepository
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState
 import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.GetSplashSizeUseCase
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
 import com.android.quickstep.task.thumbnail.TaskThumbnail
 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.Splash
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.systemui.shared.recents.model.Task
+import kotlin.math.max
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -47,9 +53,12 @@
     taskViewData: TaskViewData,
     taskContainerData: TaskContainerData,
     private val tasksRepository: RecentTasksRepository,
-    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase
+    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+    private val splashAlphaUseCase: SplashAlphaUseCase,
+    private val getSplashSizeUseCase: GetSplashSizeUseCase,
 ) {
     private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
+    private val splashProgress = MutableStateFlow(flowOf(0f))
     private lateinit var taskThumbnail: TaskThumbnail
 
     /**
@@ -63,7 +72,13 @@
         combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
             recentsScale * taskScale
         }
-    val dimProgress: Flow<Float> = taskContainerData.taskMenuOpenProgress
+    val dimProgress: Flow<Float> =
+        combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
+            taskMenuOpenProgress,
+            tintAmount ->
+            max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
+        }
+    val splashAlpha = splashProgress.flatMapLatest { it }
     val uiState: Flow<TaskThumbnailUiState> =
         task
             .flatMapLatest { taskFlow ->
@@ -73,10 +88,8 @@
                         taskThumbnail.isRunning -> LiveTile
                         isBackgroundOnly(taskVal) ->
                             BackgroundOnly(taskVal.colorBackground.removeAlpha())
-                        isSnapshotState(taskVal) -> {
-                            val bitmap = taskVal.thumbnail?.thumbnail!!
-                            Snapshot(bitmap, taskVal.colorBackground.removeAlpha())
-                        }
+                        isSnapshotSplashState(taskVal) ->
+                            SnapshotSplash(createSnapshotState(taskVal), createSplashState(taskVal))
                         else -> Uninitialized
                     }
                 }
@@ -86,6 +99,7 @@
     fun bind(taskThumbnail: TaskThumbnail) {
         this.taskThumbnail = taskThumbnail
         task.value = tasksRepository.getTaskDataById(taskThumbnail.taskId)
+        splashProgress.value = splashAlphaUseCase.execute(taskThumbnail.taskId)
     }
 
     fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
@@ -102,12 +116,28 @@
 
     private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
 
-    private fun isSnapshotState(task: Task): Boolean {
+    private fun isSnapshotSplashState(task: Task): Boolean {
         val thumbnailPresent = task.thumbnail?.thumbnail != null
         val taskLocked = task.isLocked
 
         return thumbnailPresent && !taskLocked
     }
 
+    private fun createSnapshotState(task: Task): Snapshot {
+        val thumbnailData = task.thumbnail
+        val bitmap = thumbnailData?.thumbnail!!
+        return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
+    }
+
+    private fun createSplashState(task: Task): Splash {
+        val taskIcon = task.icon
+        val size = if (taskIcon == null) Point() else getSplashSizeUseCase.execute(taskIcon)
+        return Splash(taskIcon, size)
+    }
+
     @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
+    private companion object {
+        const val MAX_SCRIM_ALPHA = 0.4f
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
index 7a9ecf2..07dfc29 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
@@ -23,6 +23,8 @@
     // This is typically a View concern but it is used to invalidate rendering in other Views
     val scale = MutableStateFlow(1f)
 
+    val nonGridScale = MutableStateFlow(1f)
+
     // TODO(b/331753115): This property should not be in TaskViewData once TaskView is MVVM.
     /** Whether outline of TaskView is formed by outline thumbnail view(s). */
     val isOutlineFormedByThumbnailView: Boolean = taskViewType != TaskViewType.DESKTOP
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
index ec75d59..30ee360 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt
@@ -22,4 +22,8 @@
     fun updateScale(scale: Float) {
         taskViewData.scale.value = scale
     }
+
+    fun updateNonGridScale(nonGridScale: Float) {
+        taskViewData.nonGridScale.value = nonGridScale
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index e31a828..0335fa1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -187,7 +187,6 @@
     ) {
         val snapshot = taskContainer.snapshotView
         val iconView: View = taskContainer.iconView.asView()
-        // TODO(334826842): Switch to splash state in TaskThumbnailView
         if (!enableRefactorTaskThumbnail()) {
             val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
             builder.add(
@@ -198,6 +197,15 @@
                 )
             )
             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.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d63ac56..8e232ee 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -193,7 +193,9 @@
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.recents.data.RecentTasksRepository;
 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl;
 import com.android.quickstep.recents.data.RecentsRotationStateRepository;
+import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl;
 import com.android.quickstep.recents.di.RecentsDependencies;
 import com.android.quickstep.recents.viewmodel.RecentsViewData;
 import com.android.quickstep.recents.viewmodel.RecentsViewModel;
@@ -828,10 +830,10 @@
             );
 
             recentsDependencies.provide(RecentsRotationStateRepository.class,
-                    () -> new RecentsRotationStateRepository(mOrientationState));
+                    () -> new RecentsRotationStateRepositoryImpl(mOrientationState));
 
             recentsDependencies.provide(RecentsDeviceProfileRepository.class,
-                    () -> new RecentsDeviceProfileRepository(mContainer));
+                    () -> new RecentsDeviceProfileRepositoryImpl(mContainer));
         } else {
             mRecentsViewModel = null;
         }
@@ -3297,6 +3299,10 @@
     }
 
     private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
+        if (enableRefactorTaskThumbnail()) {
+            mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
+            return;
+        }
         int taskCount = getTaskViewCount();
         if (taskCount == 0) {
             return;
@@ -4894,7 +4900,6 @@
                             mSplitHiddenTaskView.getWidth(), mSplitHiddenTaskView.getHeight(),
                             primaryTaskSelected);
             builder.addOnFrameCallback(() -> {
-                // TODO(b/334826842): Handle splash icon for new TTV.
                 if (!enableRefactorTaskThumbnail()) {
                     taskContainer.getThumbnailViewDeprecated().refreshSplashView();
                 }
@@ -6116,7 +6121,6 @@
      * tasks to be dimmed while other elements in the recents view are left alone.
      */
     public void showForegroundScrim(boolean show) {
-        // TODO(b/349601769) Add scrim response into new TTV - this is called from overlay
         if (!show && mColorTint == 0) {
             if (mTintingAnimator != null) {
                 mTintingAnimator.cancel();
@@ -6135,6 +6139,10 @@
     private void setColorTint(float tintAmount) {
         mColorTint = tintAmount;
 
+        if (enableRefactorTaskThumbnail()) {
+            mRecentsViewModel.setTintAmount(tintAmount);
+        }
+
         for (int i = 0; i < getTaskViewCount(); i++) {
             requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index e7a8720..b1a25b5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -69,7 +69,8 @@
     private val taskContainerViewModel: TaskContainerViewModel by lazy {
         TaskContainerViewModel(
             sysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
-            getThumbnailUseCase = RecentsDependencies.get()
+            getThumbnailUseCase = RecentsDependencies.get(),
+            splashAlphaUseCase = RecentsDependencies.get(),
         )
     }
 
@@ -81,7 +82,7 @@
                 val taskViewScope = RecentsDependencies.getScope(taskView)
                 linkTo(taskViewScope)
 
-                val taskContainerScope = RecentsDependencies.getScope(this)
+                val taskContainerScope = RecentsDependencies.getScope(this@TaskContainer)
                 linkTo(taskContainerScope)
             }
         } else {
@@ -112,7 +113,8 @@
     // TODO(b/334826842): Support shouldShowSplashView for new TTV.
     val shouldShowSplashView: Boolean
         get() =
-            if (enableRefactorTaskThumbnail()) false
+            if (enableRefactorTaskThumbnail())
+                taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
             else thumbnailViewDeprecated.shouldShowSplashView()
 
     // TODO(b/350743460) Support sysUiStatusNavFlags for new TTV.
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index f2f036a..b2abe69 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -285,6 +285,9 @@
         set(value) {
             field = value
             applyScale()
+            if (enableRefactorTaskThumbnail()) {
+                taskViewModel.updateNonGridScale(value)
+            }
         }
 
     private var dismissScale = 1f
@@ -1051,11 +1054,9 @@
                     if (isQuickSwitch) {
                         setFreezeRecentTasksReordering()
                     }
-                    // TODO(b/334826842) add splash functionality to new TTV
-                    if (!enableRefactorTaskThumbnail()) {
-                        disableStartingWindow =
-                            firstContainer.thumbnailViewDeprecated.shouldShowSplashView()
-                    }
+                    // TODO(b/334826842) no work required - add splash functionality to new TTV -
+                    // cold start e.g. restart device. Small splash moving to bigger splash
+                    disableStartingWindow = firstContainer.shouldShowSplashView
                 }
         Executors.UI_HELPER_EXECUTOR.execute {
             if (
@@ -1396,7 +1397,7 @@
 
     protected open fun refreshTaskThumbnailSplash() {
         if (!enableRefactorTaskThumbnail()) {
-            // TODO(b/334826842) add splash functionality to new TTV
+            // TODO(b/342560598) handle onTaskIconChanged
             taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() }
         }
     }
@@ -1420,7 +1421,6 @@
 
     protected open fun applyThumbnailSplashAlpha() {
         if (!enableRefactorTaskThumbnail()) {
-            // TODO(b/334826842) add splash functionality to new TTV
             taskContainers.forEach {
                 it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
             }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java
new file mode 100644
index 0000000..0ff142a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/MultiStateCallbackTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.launcher3.util.LauncherMultivalentJUnit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit.class)
+public class MultiStateCallbackTest {
+
+    private int mFlagCount = 0;
+    private int getNextStateFlag() {
+        int index = 1 << mFlagCount;
+        mFlagCount++;
+        return index;
+    }
+
+    private final MultiStateCallback mMultiStateCallback = new MultiStateCallback(new String[0]);
+    private final Runnable mCallback = spy(new Runnable() {
+        @Override
+        public void run() {}
+    });
+    private final Consumer<Boolean> mListener = spy(new Consumer<Boolean>() {
+        @Override
+        public void accept(Boolean isOn) {}
+    });
+
+    @Test
+    public void testSetState_trackedProperly() {
+        int watchedAnime = getNextStateFlag();
+
+        assertThat(mMultiStateCallback.getState()).isEqualTo(0);
+        assertThat(mMultiStateCallback.hasStates(watchedAnime)).isFalse();
+
+        mMultiStateCallback.setState(watchedAnime);
+
+        assertThat(mMultiStateCallback.getState()).isEqualTo(watchedAnime);
+        assertThat(mMultiStateCallback.hasStates(watchedAnime)).isTrue();
+    }
+
+    @Test
+    public void testSetState_withMultipleStates_trackedProperly() {
+        int watchedAnime = getNextStateFlag();
+        int sharedMemes = getNextStateFlag();
+
+        mMultiStateCallback.setState(watchedAnime);
+        mMultiStateCallback.setState(sharedMemes);
+
+        assertThat(mMultiStateCallback.getState()).isEqualTo(watchedAnime | sharedMemes);
+        assertThat(mMultiStateCallback.hasStates(watchedAnime)).isTrue();
+        assertThat(mMultiStateCallback.hasStates(sharedMemes)).isTrue();
+        assertThat(mMultiStateCallback.hasStates(watchedAnime | sharedMemes)).isTrue();
+    }
+
+    @Test
+    public void testClearState_trackedProperly() {
+        int lovedAnime = getNextStateFlag();
+
+        mMultiStateCallback.setState(lovedAnime);
+        mMultiStateCallback.clearState(lovedAnime);
+
+        assertThat(mMultiStateCallback.getState()).isEqualTo(0);
+        assertThat(mMultiStateCallback.hasStates(lovedAnime)).isFalse();
+    }
+
+    @Test
+    public void testClearState_withMultipleState_trackedProperly() {
+        int lovedAnime = getNextStateFlag();
+        int talkedAboutAnime = getNextStateFlag();
+
+        mMultiStateCallback.setState(lovedAnime);
+        mMultiStateCallback.setState(talkedAboutAnime);
+        mMultiStateCallback.clearState(talkedAboutAnime);
+
+        assertThat(mMultiStateCallback.getState()).isEqualTo(lovedAnime);
+        assertThat(mMultiStateCallback.hasStates(lovedAnime)).isTrue();
+        assertThat(mMultiStateCallback.hasStates(talkedAboutAnime)).isFalse();
+        assertThat(mMultiStateCallback.hasStates(lovedAnime | talkedAboutAnime)).isFalse();
+    }
+
+    @Test
+    public void testCallbackDoesNotRun_withoutState() {
+        int watchedOnePiece = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedOnePiece, mCallback);
+
+        verify(mCallback, never()).run();
+    }
+
+    @Test
+    public void testCallbackDoesNotRun_whenNotTracked() {
+        int watchedJujutsuKaisen = getNextStateFlag();
+
+        mMultiStateCallback.setState(watchedJujutsuKaisen);
+
+        verify(mCallback, never()).run();
+    }
+
+    @Test
+    public void testCallbackRuns_afterTrackedAndStateSet() {
+        int watchedHunterXHunter = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedHunterXHunter, mCallback);
+        mMultiStateCallback.setState(watchedHunterXHunter);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackRuns_onUiThread() {
+        int watchedHunterXHunter = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedHunterXHunter, mCallback);
+        mMultiStateCallback.setStateOnUiThread(watchedHunterXHunter);
+
+        runOnMainSync(() -> verify(mCallback, times(1)).run());
+    }
+
+    @Test
+    public void testCallbackRuns_agnosticallyToCallOrder() {
+        int watchedFullMetalAlchemist = getNextStateFlag();
+
+        mMultiStateCallback.setState(watchedFullMetalAlchemist);
+        mMultiStateCallback.runOnceAtState(watchedFullMetalAlchemist, mCallback);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackRuns_onlyOnceAfterStateSet() {
+        int watchedBleach = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedBleach, mCallback);
+        mMultiStateCallback.setState(watchedBleach);
+        mMultiStateCallback.setState(watchedBleach);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackRuns_onlyOnceAfterClearState() {
+        int rememberedGreatShow = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(rememberedGreatShow, mCallback);
+        mMultiStateCallback.setState(rememberedGreatShow);
+        mMultiStateCallback.clearState(rememberedGreatShow);
+        mMultiStateCallback.setState(rememberedGreatShow);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackDoesNotRun_withoutFullStateSet() {
+        int watchedMobPsycho = getNextStateFlag();
+        int watchedVinlandSaga = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedMobPsycho | watchedVinlandSaga, mCallback);
+        mMultiStateCallback.setState(watchedMobPsycho);
+
+        verify(mCallback, times(0)).run();
+    }
+
+    @Test
+    public void testCallbackRuns_withFullStateSet_agnosticallyToCallOrder() {
+        int watchedReZero = getNextStateFlag();
+        int watchedJojosBizareAdventure = getNextStateFlag();
+
+        mMultiStateCallback.setState(watchedJojosBizareAdventure);
+        mMultiStateCallback.runOnceAtState(watchedReZero | watchedJojosBizareAdventure, mCallback);
+        mMultiStateCallback.setState(watchedReZero);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackRuns_withFullStateSet_asIntegerMask() {
+        int watchedPokemon = getNextStateFlag();
+        int watchedDigimon = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedPokemon | watchedDigimon, mCallback);
+        mMultiStateCallback.setState(watchedPokemon | watchedDigimon);
+
+        verify(mCallback, times(1)).run();
+    }
+
+    @Test
+    public void testCallbackDoesNotRun_afterClearState() {
+        int watchedMonster = getNextStateFlag();
+        int watchedPingPong = getNextStateFlag();
+
+        mMultiStateCallback.runOnceAtState(watchedMonster | watchedPingPong, mCallback);
+        mMultiStateCallback.setState(watchedMonster);
+        mMultiStateCallback.clearState(watchedMonster);
+        mMultiStateCallback.setState(watchedPingPong);
+
+        verify(mCallback, times(0)).run();
+    }
+
+    @Test
+    public void testlistenerRuns_multipleTimes() {
+        int watchedSteinsGate = getNextStateFlag();
+
+        mMultiStateCallback.addChangeListener(watchedSteinsGate, mListener);
+        mMultiStateCallback.setState(watchedSteinsGate);
+
+        // Called exactly one
+        verify(mListener, times(1)).accept(anyBoolean());
+        // Called exactly once with isOn = true
+        verify(mListener, times(1)).accept(eq(true));
+        // Never called with isOn = false
+        verify(mListener, times(0)).accept(eq(false));
+
+        mMultiStateCallback.clearState(watchedSteinsGate);
+
+        // Called exactly twice
+        verify(mListener, times(2)).accept(anyBoolean());
+        // Called exactly once with isOn = true
+        verify(mListener, times(1)).accept(eq(true));
+        // Called exactly once with isOn = false
+        verify(mListener, times(1)).accept(eq(false));
+    }
+
+    @Test
+    public void testlistenerDoesNotRun_forUnchangedState() {
+        int watchedSteinsGate = getNextStateFlag();
+
+        mMultiStateCallback.addChangeListener(watchedSteinsGate, mListener);
+        mMultiStateCallback.setState(watchedSteinsGate);
+        mMultiStateCallback.setState(watchedSteinsGate);
+
+        // State remained unchanged
+        verify(mListener, times(1)).accept(anyBoolean());
+        // Called exactly once with isOn = true
+        verify(mListener, times(1)).accept(eq(true));
+    }
+
+    private static void runOnMainSync(Runnable runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
new file mode 100644
index 0000000..cdfbd16
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.data
+
+class FakeRecentsDeviceProfileRepository : RecentsDeviceProfileRepository {
+    private var recentsDeviceProfile =
+        RecentsDeviceProfile(
+            isLargeScreen = false,
+            widthPx = 1080,
+            heightPx = 1920,
+        )
+
+    override fun getRecentsDeviceProfile() = recentsDeviceProfile
+
+    fun setRecentsDeviceProfile(newValue: RecentsDeviceProfile) {
+        recentsDeviceProfile = newValue
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt
new file mode 100644
index 0000000..c328672
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsRotationStateRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.data
+
+import android.view.Surface
+
+class FakeRecentsRotationStateRepository : RecentsRotationStateRepository {
+    private var recentsRotationState =
+        RecentsRotationState(
+            activityRotation = Surface.ROTATION_0,
+            orientationHandlerRotation = Surface.ROTATION_0
+        )
+
+    override fun getRecentsRotationState() = recentsRotationState
+
+    fun setRecentsRotationState(newValue: RecentsRotationState) {
+        recentsRotationState = newValue
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
index 242bc73..fee4979 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -23,10 +23,12 @@
 import com.android.systemui.shared.recents.model.Task
 import com.google.common.truth.Truth.assertThat
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 class FakeTaskIconDataSource : TaskIconDataSource {
 
-    val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mock() }
+    val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mockCopyableDrawable() }
+
     val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
     var shouldLoadSynchronously: Boolean = true
 
@@ -49,6 +51,17 @@
         }
         return null
     }
+
+    private fun mockCopyableDrawable(): Drawable {
+        val mutableDrawable = mock<Drawable>()
+        val immutableDrawable =
+            mock<Drawable>().apply { whenever(mutate()).thenReturn(mutableDrawable) }
+        val constantState =
+            mock<Drawable.ConstantState>().apply {
+                whenever(newDrawable()).thenReturn(immutableDrawable)
+            }
+        return mutableDrawable.apply { whenever(this.constantState).thenReturn(constantState) }
+    }
 }
 
 fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index 19990a8..ec1da5a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -24,6 +24,7 @@
 
 class FakeTasksRepository : RecentTasksRepository {
     private var thumbnailDataMap: Map<Int, ThumbnailData> = emptyMap()
+    private var taskIconDataMap: Map<Int, TaskIconQueryResponse> = emptyMap()
     private var tasks: MutableStateFlow<List<Task>> = MutableStateFlow(emptyList())
     private var visibleTasks: MutableStateFlow<List<Int>> = MutableStateFlow(emptyList())
 
@@ -37,7 +38,17 @@
 
     override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
         visibleTasks.value = visibleTaskIdList
-        tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } }
+        tasks.value =
+            tasks.value.map {
+                it.apply {
+                    thumbnail = thumbnailDataMap[it.key.id]
+                    taskIconDataMap[it.key.id].let { taskIconData ->
+                        icon = taskIconData?.icon
+                        titleDescription = taskIconData?.contentDescription
+                        title = taskIconData?.title
+                    }
+                }
+            }
     }
 
     fun seedTasks(tasks: List<Task>) {
@@ -47,4 +58,8 @@
     fun seedThumbnailData(thumbnailDataMap: Map<Int, ThumbnailData>) {
         this.thumbnailDataMap = thumbnailDataMap
     }
+
+    fun seedIconData(iconDataMap: Map<Int, TaskIconQueryResponse>) {
+        this.taskIconDataMap = iconDataMap
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
similarity index 84%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
index eff926d..e74fe4b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
@@ -25,12 +25,12 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
-/** Test for [RecentsDeviceProfileRepository] */
+/** Test for [RecentsDeviceProfileRepositoryImpl] */
 @RunWith(AndroidJUnit4::class)
-class RecentsDeviceProfileRepositoryTest : FakeInvariantDeviceProfileTest() {
+class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
     private val recentsViewContainer = mock<RecentsViewContainer>()
 
-    private val systemUnderTest = RecentsDeviceProfileRepository(recentsViewContainer)
+    private val systemUnderTest = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
 
     @Test
     fun deviceProfileMappedCorrectly() {
@@ -39,6 +39,6 @@
         whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
 
         assertThat(systemUnderTest.getRecentsDeviceProfile())
-            .isEqualTo(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen = true))
+            .isEqualTo(RecentsDeviceProfile(isLargeScreen = true, widthPx = 1600, heightPx = 2560))
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
similarity index 71%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
index 1f4da26..017f037 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsRotationStateRepositoryImplTest.kt
@@ -16,26 +16,32 @@
 
 package com.android.quickstep.recents.data
 
+import android.view.Surface.ROTATION_270
 import android.view.Surface.ROTATION_90
+import com.android.quickstep.orientation.SeascapePagedViewHandler
 import com.android.quickstep.util.RecentsOrientedState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
-/** Test for [RecentsRotationStateRepository] */
-class RecentsRotationStateRepositoryTest {
+/** Test for [RecentsRotationStateRepositoryImpl] */
+class RecentsRotationStateRepositoryImplTest {
     private val recentsOrientedState = mock<RecentsOrientedState>()
 
-    private val systemUnderTest = RecentsRotationStateRepository(recentsOrientedState)
+    private val systemUnderTest = RecentsRotationStateRepositoryImpl(recentsOrientedState)
 
     @Test
     fun orientedStateMappedCorrectly() {
         whenever(recentsOrientedState.recentsActivityRotation).thenReturn(ROTATION_90)
+        whenever(recentsOrientedState.orientationHandler).thenReturn(SeascapePagedViewHandler())
 
         assertThat(systemUnderTest.getRecentsRotationState())
             .isEqualTo(
-                RecentsRotationStateRepository.RecentsRotationState(activityRotation = ROTATION_90)
+                RecentsRotationState(
+                    activityRotation = ROTATION_90,
+                    orientationHandlerRotation = ROTATION_270
+                )
             )
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
index e657d59..02f1d11 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
@@ -24,9 +24,9 @@
 import android.graphics.Rect
 import android.view.Surface.ROTATION_90
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
 import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
-import com.android.quickstep.recents.data.RecentsRotationStateRepository
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.systemui.shared.recents.model.Task
@@ -56,8 +56,8 @@
                 }
         )
 
-    private val deviceProfileRepository = mock<RecentsDeviceProfileRepository>()
-    private val rotationStateRepository = mock<RecentsRotationStateRepository>()
+    private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
+    private val rotationStateRepository = FakeRecentsRotationStateRepository()
     private val tasksRepository = FakeTasksRepository()
     private val previewPositionHelper = mock<PreviewPositionHelper>()
 
@@ -93,15 +93,18 @@
         tasksRepository.setVisibleTasks(listOf(TASK_ID))
 
         val isLargeScreen = true
+        deviceProfileRepository.setRecentsDeviceProfile(
+            deviceProfileRepository.getRecentsDeviceProfile().copy(isLargeScreen = isLargeScreen)
+        )
         val activityRotation = ROTATION_90
+        rotationStateRepository.setRecentsRotationState(
+            rotationStateRepository
+                .getRecentsRotationState()
+                .copy(activityRotation = activityRotation)
+        )
         val isRtl = true
         val isRotated = true
 
-        whenever(deviceProfileRepository.getRecentsDeviceProfile())
-            .thenReturn(RecentsDeviceProfileRepository.RecentsDeviceProfile(isLargeScreen))
-        whenever(rotationStateRepository.getRecentsRotationState())
-            .thenReturn(RecentsRotationStateRepository.RecentsRotationState(activityRotation))
-
         whenever(previewPositionHelper.matrix).thenReturn(MATRIX)
         whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated)
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt
new file mode 100644
index 0000000..13e8b09
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/GetSplashSizeUseCaseTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.Point
+import android.graphics.drawable.Drawable
+import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.views.TaskViewType
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class GetSplashSizeUseCaseTest {
+    private val taskThumbnailViewData = TaskThumbnailViewData()
+    private val taskViewData = TaskViewData(TaskViewType.SINGLE)
+    private val recentsDeviceProfileRepository = FakeRecentsDeviceProfileRepository()
+    private val systemUnderTest =
+        GetSplashSizeUseCase(taskThumbnailViewData, taskViewData, recentsDeviceProfileRepository)
+
+    @Test
+    fun execute_whenNoScaleRequired_returnsIntrinsicSize() {
+        taskThumbnailViewData.width.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx
+        taskThumbnailViewData.height.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx
+
+        assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(100, 100))
+    }
+
+    @Test
+    fun execute_whenThumbnailViewIsSmallerThanScreen_returnsScaledSize() {
+        taskThumbnailViewData.width.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+        taskThumbnailViewData.height.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+
+        assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(50, 50))
+    }
+
+    @Test
+    fun execute_whenThumbnailViewIsSmallerThanScreen_withNonGridScale_returnsScaledSize() {
+        taskThumbnailViewData.width.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+        taskThumbnailViewData.height.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+        taskViewData.nonGridScale.value = 2f
+
+        assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(25, 25))
+    }
+
+    @Test
+    fun execute_whenThumbnailViewIsSmallerThanScreen_withThumbnailViewScale_returnsScaledSize() {
+        taskThumbnailViewData.width.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().widthPx / 2
+        taskThumbnailViewData.height.value =
+            recentsDeviceProfileRepository.getRecentsDeviceProfile().heightPx / 2
+        taskThumbnailViewData.scaleX.value = 2f
+        taskThumbnailViewData.scaleY.value = 2f
+
+        assertThat(systemUnderTest.execute(createIcon(100, 100))).isEqualTo(Point(25, 25))
+    }
+
+    private fun createIcon(width: Int, height: Int): Drawable =
+        mock<Drawable>().apply {
+            whenever(intrinsicWidth).thenReturn(width)
+            whenever(intrinsicHeight).thenReturn(height)
+        }
+}
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
new file mode 100644
index 0000000..e083046
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.data.TaskIconQueryResponse
+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_SHOWN)
+    }
+
+    @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 = createIconData("Task $taskId")
+        recentTasksRepository.seedIconData(mapOf(taskId to expectedIconData))
+        recentTasksRepository.seedTasks(tasks)
+        recentTasksRepository.setVisibleTasks(listOf(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 createIconData(title: String) = TaskIconQueryResponse(mock<Drawable>(), "", title)
+
+    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/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index 754c9d1..877528e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -21,8 +21,12 @@
 import android.graphics.Bitmap
 import android.graphics.Color
 import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import android.view.Surface
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.TaskIconQueryResponse
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
@@ -30,6 +34,8 @@
 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.Splash
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
@@ -40,8 +46,10 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
@@ -53,18 +61,27 @@
     private val taskContainerData = TaskContainerData()
     private val tasksRepository = FakeTasksRepository()
     private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+    private val splashAlphaUseCase: SplashAlphaUseCase = mock()
+    private val getSplashSizeUseCase: GetSplashSizeUseCase = mock()
     private val systemUnderTest by lazy {
         TaskThumbnailViewModel(
             recentsViewData,
             taskViewData,
             taskContainerData,
             tasksRepository,
-            mGetThumbnailPositionUseCase
+            mGetThumbnailPositionUseCase,
+            splashAlphaUseCase,
+            getSplashSizeUseCase,
         )
     }
 
     private val tasks = (0..5).map(::createTaskWithId)
 
+    @Before
+    fun setUp() {
+        whenever(getSplashSizeUseCase.execute(any())).thenReturn(Point())
+    }
+
     @Test
     fun initialStateIsUninitialized() = runTest {
         assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
@@ -147,9 +164,11 @@
     }
 
     @Test
-    fun bindStoppedTaskWithThumbnail_thenStateIs_Snapshot_withAlphaRemoved() = runTest {
-        val expectedThumbnailData = createThumbnailData()
+    fun bindStoppedTaskWithThumbnail_thenStateIs_SnapshotSplash_withAlphaRemoved() = runTest {
+        val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
         tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+        val expectedIconData = createIconData("Task 2")
+        tasksRepository.seedIconData(mapOf(2 to expectedIconData))
         tasksRepository.seedTasks(tasks)
         tasksRepository.setVisibleTasks(listOf(2))
         val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
@@ -157,17 +176,23 @@
         systemUnderTest.bind(recentTask)
         assertThat(systemUnderTest.uiState.first())
             .isEqualTo(
-                Snapshot(
-                    backgroundColor = Color.rgb(2, 2, 2),
-                    bitmap = expectedThumbnailData.thumbnail!!,
+                SnapshotSplash(
+                    Snapshot(
+                        backgroundColor = Color.rgb(2, 2, 2),
+                        bitmap = expectedThumbnailData.thumbnail!!,
+                        thumbnailRotation = Surface.ROTATION_270,
+                    ),
+                    Splash(expectedIconData.icon, Point())
                 )
             )
     }
 
     @Test
-    fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshot() = runTest {
+    fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshotSplash() = runTest {
         val expectedThumbnailData = createThumbnailData()
         tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+        val expectedIconData = createIconData("Task 2")
+        tasksRepository.seedIconData(mapOf(2 to expectedIconData))
         tasksRepository.seedTasks(tasks)
         val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
 
@@ -177,14 +202,35 @@
         tasksRepository.setVisibleTasks(listOf(2))
         assertThat(systemUnderTest.uiState.first())
             .isEqualTo(
-                Snapshot(
-                    backgroundColor = Color.rgb(2, 2, 2),
-                    bitmap = expectedThumbnailData.thumbnail!!,
+                SnapshotSplash(
+                    Snapshot(
+                        backgroundColor = Color.rgb(2, 2, 2),
+                        bitmap = expectedThumbnailData.thumbnail!!,
+                        thumbnailRotation = Surface.ROTATION_0,
+                    ),
+                    Splash(expectedIconData.icon, Point())
                 )
             )
     }
 
     @Test
+    fun bindStoppedTask_thenStateContainsSplashSizeFromUseCase() = runTest {
+        val expectedSplashSize = Point(100, 150)
+        whenever(getSplashSizeUseCase.execute(any())).thenReturn(expectedSplashSize)
+        val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
+        tasksRepository.seedThumbnailData(mapOf(2 to expectedThumbnailData))
+        val expectedIconData = createIconData("Task 2")
+        tasksRepository.seedIconData(mapOf(2 to expectedIconData))
+        tasksRepository.seedTasks(tasks)
+        tasksRepository.setVisibleTasks(listOf(2))
+        val recentTask = TaskThumbnail(taskId = 2, isRunning = false)
+
+        systemUnderTest.bind(recentTask)
+        val uiState = systemUnderTest.uiState.first() as SnapshotSplash
+        assertThat(uiState.splash.size).isEqualTo(expectedSplashSize)
+    }
+
+    @Test
     fun getSnapshotMatrix_MissingThumbnail() = runTest {
         val taskId = 2
         val recentTask = TaskThumbnail(taskId = taskId, isRunning = false)
@@ -212,19 +258,42 @@
             .isEqualTo(MATRIX)
     }
 
+    @Test
+    fun getForegroundScrimDimProgress_returnsForegroundMaxScrim() = runTest {
+        recentsViewData.tintAmount.value = 0.32f
+        taskContainerData.taskMenuOpenProgress.value = 0f
+        assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.32f)
+    }
+
+    @Test
+    fun getTaskMenuScrimDimProgress_returnsTaskMenuScrim() = runTest {
+        recentsViewData.tintAmount.value = 0f
+        taskContainerData.taskMenuOpenProgress.value = 1f
+        assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.4f)
+    }
+
+    @Test
+    fun getForegroundScrimDimProgress_returnsNoScrim() = runTest {
+        recentsViewData.tintAmount.value = 0f
+        taskContainerData.taskMenuOpenProgress.value = 0f
+        assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0f)
+    }
+
     private fun createTaskWithId(taskId: Int) =
         Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
             colorBackground = Color.argb(taskId, taskId, taskId, taskId)
         }
 
-    private fun createThumbnailData(): ThumbnailData {
+    private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
         val bitmap = mock<Bitmap>()
         whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
         whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
 
-        return ThumbnailData(thumbnail = bitmap)
+        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
     }
 
+    private fun createIconData(title: String) = TaskIconQueryResponse(mock<Drawable>(), "", title)
+
     companion object {
         const val THUMBNAIL_WIDTH = 100
         const val THUMBNAIL_HEIGHT = 200
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt
new file mode 100644
index 0000000..6c526a4
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TestExtensions.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.util
+
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.util.SafeCloseable
+import com.android.quickstep.DeviceConfigWrapper.Companion.configHelper
+import com.android.quickstep.util.DeviceConfigHelper.Companion.prefs
+import java.util.concurrent.CountDownLatch
+import java.util.function.BooleanSupplier
+import org.junit.Assert
+import org.junit.Assume
+
+/** Helper methods for testing */
+object TestExtensions {
+
+    @JvmStatic
+    fun overrideNavConfigFlag(
+        key: String,
+        value: Boolean,
+        targetValue: BooleanSupplier
+    ): AutoCloseable {
+        Assume.assumeTrue(BuildConfig.IS_DEBUG_DEVICE)
+        if (targetValue.asBoolean == value) {
+            return AutoCloseable {}
+        }
+
+        navConfigEditWatcher().let {
+            prefs.edit().putBoolean(key, value).commit()
+            it.close()
+        }
+        Assert.assertEquals(value, targetValue.asBoolean)
+
+        val watcher = navConfigEditWatcher()
+        return AutoCloseable {
+            prefs.edit().remove(key).commit()
+            watcher.close()
+        }
+    }
+
+    private fun navConfigEditWatcher(): SafeCloseable {
+        val wait = CountDownLatch(1)
+        val listener = Runnable { wait.countDown() }
+        configHelper.addChangeListener(listener)
+
+        return SafeCloseable {
+            wait.await()
+            configHelper.removeChangeListener(listener)
+        }
+    }
+}
diff --git a/src/com/android/launcher3/allapps/FloatingMaskView.java b/src/com/android/launcher3/allapps/FloatingMaskView.java
index 606eb03..cee5e18 100644
--- a/src/com/android/launcher3/allapps/FloatingMaskView.java
+++ b/src/com/android/launcher3/allapps/FloatingMaskView.java
@@ -21,6 +21,7 @@
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.launcher3.R;
@@ -53,13 +54,21 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
-        AllAppsRecyclerView allAppsContainerView =
-                mActivityContext.getAppsView().getActiveRecyclerView();
+        setParameters((ViewGroup.MarginLayoutParams) getLayoutParams(),
+                mActivityContext.getAppsView().getActiveRecyclerView());
+    }
+
+    @VisibleForTesting
+    void setParameters(ViewGroup.MarginLayoutParams lp, AllAppsRecyclerView recyclerView) {
         if (lp != null) {
-            lp.rightMargin = allAppsContainerView.getPaddingRight();
-            lp.leftMargin = allAppsContainerView.getPaddingLeft();
-            mBottomBox.setMinimumHeight(allAppsContainerView.getPaddingBottom());
+            lp.rightMargin = recyclerView.getPaddingRight();
+            lp.leftMargin = recyclerView.getPaddingLeft();
+            getBottomBox().setMinimumHeight(recyclerView.getPaddingBottom());
         }
     }
+
+    @VisibleForTesting
+    ImageView getBottomBox() {
+        return mBottomBox;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index c1264d6..e215cab 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -403,6 +403,7 @@
                     mLockText.setHorizontallyScrolling(false);
                     mPrivateSpaceSettingsButton.setVisibility(
                             isPrivateSpaceSettingsAvailable() ? VISIBLE : GONE);
+                    mPrivateSpaceSettingsButton.setClickable(isPrivateSpaceSettingsAvailable());
                 }
                 lockPill.setVisibility(VISIBLE);
                 lockPill.setOnClickListener(view -> lockingAction(/* lock */ true));
@@ -425,6 +426,7 @@
                 lockPill.setContentDescription(mLockedStateContentDesc);
 
                 mPrivateSpaceSettingsButton.setVisibility(GONE);
+                mPrivateSpaceSettingsButton.setClickable(false);
                 transitionView.setVisibility(GONE);
             }
             case STATE_TRANSITION -> {
@@ -660,10 +662,7 @@
             return;
         }
         attachFloatingMaskView(expand);
-        PropertySetter headerSetter = new AnimatedPropertySetter();
-        headerSetter.add(updateSettingsGearAlpha(expand));
-        headerSetter.add(updateLockTextAlpha(expand));
-        AnimatorSet animatorSet = headerSetter.buildAnim();
+        AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim();
         animatorSet.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -708,12 +707,16 @@
             }
         }));
         if (expand) {
-            animatorSet.playTogether(animateAlphaOfIcons(true),
+            animatorSet.playTogether(updateSettingsGearAlpha(true),
+                    updateLockTextAlpha(true),
+                    animateAlphaOfIcons(true),
                     animatePillTransition(true),
                     translateFloatingMaskView(false));
         } else {
             AnimatorSet parallelSet = new AnimatorSet();
-            parallelSet.playTogether(animateAlphaOfIcons(false),
+            parallelSet.playTogether(updateSettingsGearAlpha(false),
+                    updateLockTextAlpha(false),
+                    animateAlphaOfIcons(false),
                     animatePillTransition(false));
             if (isPrivateSpaceHidden()) {
                 animatorSet.playSequentially(parallelSet,
@@ -794,6 +797,14 @@
             @Override
             public void onAnimationStart(Animator animator) {
                 mPrivateSpaceSettingsButton.setVisibility(VISIBLE);
+                mPrivateSpaceSettingsButton.setClickable(false);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                if (expand) {
+                    mPrivateSpaceSettingsButton.setClickable(true);
+                }
             }
         });
         return settingsAlphaAnim;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 52ce4e8..bd9298b 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -44,11 +44,13 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback;
 import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback;
+import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceGroup.PreferencePositionCallback;
 import androidx.preference.PreferenceScreen;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherFiles;
 import com.android.launcher3.R;
 import com.android.launcher3.states.RotationHelper;
@@ -165,6 +167,7 @@
         private boolean mRestartOnResume = false;
 
         private String mHighLightKey;
+
         private boolean mPreferenceHighlighted = false;
 
         @Override
@@ -198,11 +201,62 @@
                 }
             }
 
+            // If the target preference is not in the current preference screen, find the parent
+            // preference screen that contains the target preference and set it as the preference
+            // screen.
+            if (Flags.navigateToChildPreference()
+                    && mHighLightKey != null
+                    && !isKeyInPreferenceGroup(mHighLightKey, screen)) {
+                final PreferenceScreen parentPreferenceScreen =
+                        findParentPreference(screen, mHighLightKey);
+                if (parentPreferenceScreen != null && getActivity() != null) {
+                    if (!TextUtils.isEmpty(parentPreferenceScreen.getTitle())) {
+                        getActivity().setTitle(parentPreferenceScreen.getTitle());
+                    }
+                    setPreferenceScreen(parentPreferenceScreen);
+                    return;
+                }
+            }
+
             if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
                 getActivity().setTitle(getPreferenceScreen().getTitle());
             }
         }
 
+        private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) {
+            for (int i = 0; i < parent.getPreferenceCount(); i++) {
+                Preference pref = parent.getPreference(i);
+                if (pref.getKey() != null && pref.getKey().equals(targetKey)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Finds the parent preference screen for the given target key.
+         *
+         * @param parent the parent preference screen
+         * @param targetKey the key of the preference to find
+         * @return the parent preference screen that contains the target preference
+         */
+        @Nullable
+        private PreferenceScreen findParentPreference(PreferenceScreen parent, String targetKey) {
+            for (int i = 0; i < parent.getPreferenceCount(); i++) {
+                Preference pref = parent.getPreference(i);
+                if (pref instanceof PreferenceScreen) {
+                    PreferenceScreen foundKey = findParentPreference((PreferenceScreen) pref,
+                            targetKey);
+                    if (foundKey != null) {
+                        return foundKey;
+                    }
+                } else if (pref.getKey() != null && pref.getKey().equals(targetKey)) {
+                    return parent;
+                }
+            }
+            return null;
+        }
+
         @Override
         public void onViewCreated(View view, Bundle savedInstanceState) {
             super.onViewCreated(view, savedInstanceState);
diff --git a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt b/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
new file mode 100644
index 0000000..cf03adc
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.launcher3.allapps
+
+import android.content.Context
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.ImageView
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.util.ActivityContextWrapper
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+class FloatingMaskViewTest {
+    @Mock
+    private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
+
+    @Mock
+    private val mockBottomBox: ImageView? = null
+    private var mVut: FloatingMaskView? = null
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        mVut = FloatingMaskView(context)
+        mVut!!.layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT)
+    }
+
+    @Test
+    fun setParameters_paramsMarginEqualRecyclerViewPadding() {
+        val floatingMaskView = Mockito.spy(mVut)
+        Mockito.`when`(mockAllAppsRecyclerView!!.paddingLeft).thenReturn(PADDING_PX)
+        Mockito.`when`(mockAllAppsRecyclerView.paddingRight).thenReturn(PADDING_PX)
+        Mockito.`when`(mockAllAppsRecyclerView.paddingBottom).thenReturn(PADDING_PX)
+        Mockito.`when`(floatingMaskView!!.bottomBox).thenReturn(mockBottomBox)
+        val lp = floatingMaskView.layoutParams as MarginLayoutParams
+
+        floatingMaskView.setParameters(lp, mockAllAppsRecyclerView)
+
+        Truth.assertThat(lp.leftMargin).isEqualTo(PADDING_PX)
+        Truth.assertThat(lp.rightMargin).isEqualTo(PADDING_PX)
+        Mockito.verify(mockBottomBox)?.minimumHeight = PADDING_PX
+    }
+
+    companion object {
+        private const val PADDING_PX = 15
+    }
+}