Merge "Init nav mode in TaskbarActivityContext between fold/unfold" into udc-qpr-dev
diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 195443e..00e47c9 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -32,7 +32,7 @@
         android:clickable="true"
         android:gravity="center_vertical"
         android:orientation="horizontal"
-        android:background="@drawable/top_rounded_popup_ripple"
+        android:background="@drawable/rounded_popup_ripple"
         android:paddingEnd="10dp"
         android:paddingStart="10dp"
         android:theme="@style/PopupItem">
@@ -59,40 +59,4 @@
             android:text="@string/always_show_taskbar" />
 
     </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/navigation_mode_switch_option"
-        android:layout_width="match_parent"
-        android:layout_height="52dp"
-        android:layout_gravity="center_vertical"
-        android:elevation="2dp"
-        android:clickable="true"
-        android:focusable="true"
-        android:background="@drawable/bottom_rounded_popup_ripple"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:paddingEnd="10dp"
-        android:paddingStart="10dp"
-        android:theme="@style/PopupItem">
-
-        <View
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_margin="4dp"
-            android:background="@drawable/ic_touch"
-            android:backgroundTint="?android:attr/textColorPrimary" />
-
-        <com.android.launcher3.BubbleTextView
-            style="@style/BaseIcon"
-            android:id="@+id/change_navigation_mode_text"
-            android:gravity="start|center_vertical"
-            android:textAlignment="viewStart"
-            android:paddingStart="12dp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:textSize="14sp"
-            android:textColor="?android:attr/textColorPrimary"
-            android:text="@string/change_navigation_mode" />
-
-    </LinearLayout>
 </com.android.launcher3.taskbar.TaskbarDividerPopupView>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index a347908..b0d2c3c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -17,7 +17,6 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.content.Intent
 import android.graphics.Rect
 import android.graphics.drawable.GradientDrawable
 import android.util.AttributeSet
@@ -44,9 +43,6 @@
     companion object {
         private const val TAG = "TaskbarDividerPopupView"
         private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
-        private const val SETTINGS_PACKAGE_NAME = "com.android.settings"
-        private const val CHANGE_NAVIGATION_MODE_ACTION =
-            "com.android.settings.NAVIGATION_MODE_SETTINGS"
 
         @JvmStatic
         fun createAndPopulate(
@@ -103,21 +99,12 @@
         super.onFinishInflate()
         val taskbarSwitchOption = findViewById<LinearLayout>(R.id.taskbar_switch_option)
         val alwaysShowTaskbarSwitch = findViewById<Switch>(R.id.taskbar_pinning_switch)
-        val navigationModeChangeOption =
-            findViewById<LinearLayout>(R.id.navigation_mode_switch_option)
         alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
         taskbarSwitchOption.setOnClickListener {
             alwaysShowTaskbarSwitch.isClickable = true
             alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
             onClickAlwaysShowTaskbarSwitchOption()
         }
-        navigationModeChangeOption.setOnClickListener {
-            context.startActivity(
-                Intent(CHANGE_NAVIGATION_MODE_ACTION)
-                    .setPackage(SETTINGS_PACKAGE_NAME)
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            )
-        }
     }
 
     /** Orient object as usual and then center object horizontally. */
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8cb542c..1ef4039 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -518,20 +518,22 @@
         // Set up a entire animation lifecycle callback to notify the current recents view when
         // the animation is canceled
         mGestureState.runOnceAtState(STATE_RECENTS_ANIMATION_CANCELED, () -> {
-                HashMap<Integer, ThumbnailData> snapshots =
-                        mGestureState.consumeRecentsAnimationCanceledSnapshot();
-                if (snapshots != null) {
-                    mRecentsView.switchToScreenshot(snapshots, () -> {
-                        if (mRecentsAnimationController != null) {
-                            mRecentsAnimationController.cleanupScreenshot();
-                        } else if (mDeferredCleanupRecentsAnimationController != null) {
-                            mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
-                            mDeferredCleanupRecentsAnimationController = null;
-                        }
-                    });
-                    mRecentsView.onRecentsAnimationComplete();
-                }
-            });
+            if (mRecentsView == null) return;
+
+            HashMap<Integer, ThumbnailData> snapshots =
+                    mGestureState.consumeRecentsAnimationCanceledSnapshot();
+            if (snapshots != null) {
+                mRecentsView.switchToScreenshot(snapshots, () -> {
+                    if (mRecentsAnimationController != null) {
+                        mRecentsAnimationController.cleanupScreenshot();
+                    } else if (mDeferredCleanupRecentsAnimationController != null) {
+                        mDeferredCleanupRecentsAnimationController.cleanupScreenshot();
+                        mDeferredCleanupRecentsAnimationController = null;
+                    }
+                });
+                mRecentsView.onRecentsAnimationComplete();
+            }
+        });
 
         setupRecentsViewUi();
         mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 4b13cd1..0e90e50 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static android.view.MotionEvent.ACTION_BUTTON_RELEASE;
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
@@ -29,6 +28,7 @@
 import android.graphics.Rect;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
 import androidx.annotation.Nullable;
@@ -130,8 +130,8 @@
     public void onMotionEvent(MotionEvent ev) {
         mLongPressDetector.onTouchEvent(ev);
         if (mState != STATE_ACTIVE) {
-            boolean isStashedTaskbarHovered =
-                    isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
+            boolean isStashedTaskbarHovered = isMouseEvent(ev)
+                    && isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
             if (!isStashedTaskbarHovered) {
                 mDelegate.onMotionEvent(ev);
             }
@@ -229,7 +229,7 @@
                         mHasPassedTaskbarNavThreshold = false;
                         mIsInBubbleBarArea = false;
                         break;
-                    case ACTION_BUTTON_RELEASE:
+                    case MotionEvent.ACTION_BUTTON_RELEASE:
                         if (isStashedTaskbarHovered) {
                             mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
                         }
@@ -342,4 +342,8 @@
                 dp.heightPx);
         return mStashedTaskbarHandleBounds.contains(x, y);
     }
+
+    private boolean isMouseEvent(MotionEvent event) {
+        return event.getSource() == InputDevice.SOURCE_MOUSE;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 9d188ed..40d0ac7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.testing.shared.TestProtocol.FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP;
 import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
 
 import static org.junit.Assert.assertEquals;
@@ -50,6 +52,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 import com.android.quickstep.views.RecentsView;
@@ -320,6 +323,7 @@
     @Test
     @ScreenRecord // b/242163205
     @PlatinumTest(focusArea = "launcher")
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/286084688
     public void testQuickSwitchToPreviousAppForTablet() throws Exception {
         assumeTrue(mLauncher.isTablet());
         startTestActivity(2);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index dc7cb9d..1aa7ab6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -16,6 +16,9 @@
 package com.android.quickstep;
 
 
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -27,6 +30,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 
 import org.junit.After;
@@ -71,6 +75,7 @@
     @Test
     @PortraitLandscape
     @TaskbarModeSwitch
+    @TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524
     public void testSplitAppFromHomeWithItself() throws Exception {
         // Currently only tablets have Taskbar in Overview, so test is only active on tablets
         assumeTrue(mLauncher.isTablet());
diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml
index 8471cd4..1e7cff2 100644
--- a/res/drawable/all_apps_tabs_background.xml
+++ b/res/drawable/all_apps_tabs_background.xml
@@ -30,7 +30,7 @@
                 android:state_selected="false">
                 <shape android:shape="rectangle">
                     <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-                    <solid android:color="@color/all_apps_tabs_background" />
+                    <solid android:color="@color/material_color_surface_bright" />
                 </shape>
             </item>
 
@@ -39,7 +39,7 @@
                 android:state_selected="true">
                 <shape android:shape="rectangle">
                     <corners android:radius="@dimen/all_apps_header_pill_corner_radius" />
-                    <solid android:color="@color/all_apps_tab_background_selected" />
+                    <solid android:color="@color/material_color_primary" />
                 </shape>
             </item>
         </selector>
diff --git a/res/drawable/bottom_rounded_popup_ripple.xml b/res/drawable/bottom_rounded_popup_ripple.xml
deleted file mode 100644
index 739833a..0000000
--- a/res/drawable/bottom_rounded_popup_ripple.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
--->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="?android:attr/colorControlHighlight">
-    <item android:id="@android:id/mask">
-        <shape android:shape="rectangle">
-            <solid android:color="#FFFFFFFF"/>
-            <corners android:bottomLeftRadius="@dimen/dialogCornerRadius"
-                android:bottomRightRadius="@dimen/dialogCornerRadius"
-                android:topLeftRadius="0dp"
-                android:topRightRadius="0dp"/>
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/res/drawable/top_rounded_popup_ripple.xml b/res/drawable/rounded_popup_ripple.xml
similarity index 80%
rename from res/drawable/top_rounded_popup_ripple.xml
rename to res/drawable/rounded_popup_ripple.xml
index 7468480..b0dcc80 100644
--- a/res/drawable/top_rounded_popup_ripple.xml
+++ b/res/drawable/rounded_popup_ripple.xml
@@ -18,10 +18,7 @@
     <item android:id="@android:id/mask">
         <shape android:shape="rectangle">
             <solid android:color="#FFFFFFFF"/>
-            <corners android:bottomLeftRadius="0dp"
-                android:bottomRightRadius="0dp"
-                android:topLeftRadius="@dimen/dialogCornerRadius"
-                android:topRightRadius="@dimen/dialogCornerRadius"/>
+            <corners android:radius="@dimen/dialogCornerRadius" />
         </shape>
     </item>
 </ripple>
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 52c5a49..695270e 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,7 +15,7 @@
 <com.android.launcher3.allapps.WorkPausedCard xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="@dimen/work_edu_card_margin"
+    android:padding="@dimen/all_apps_tabs_margin_top"
     android:orientation="vertical"
     android:gravity="center_horizontal">
 
@@ -25,7 +25,6 @@
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="40dp"
         android:text="@string/work_apps_paused_title"
         android:textAlignment="center"
         android:textSize="18sp" />
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1695c58..4cb6414 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -32,7 +32,8 @@
     <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
         <item name="android:textColorSecondary">#DE000000</item>
         <item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
-        <item name="allappsHeaderProtectionColor">@color/popup_color_tertiary_light</item>
+        <item name="allappsHeaderProtectionColor">
+            @color/material_color_surface_container_highest</item>
         <item name="allAppsNavBarScrimColor">#66FFFFFF</item>
         <item name="popupColorPrimary">@color/popup_color_primary_light</item>
         <item name="popupColorSecondary">@color/popup_color_secondary_light</item>
@@ -149,6 +150,7 @@
         <item name="android:colorControlHighlight">#19FFFFFF</item>
         <item name="android:colorPrimary">#FF212121</item>
         <item name="allAppsScrimColor">?attr/materialColorSurfaceDim</item>
+        <item name="allappsHeaderProtectionColor">@color/material_color_surface_container_low</item>
         <item name="allAppsNavBarScrimColor">#80000000</item>
         <item name="popupColorPrimary">@color/popup_color_primary_dark</item>
         <item name="popupColorSecondary">@color/popup_color_secondary_dark</item>
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 8876a1b..808cf70 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -17,12 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
 
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.app.WallpaperManager.OnColorsChangedListener;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -32,6 +27,7 @@
 import android.view.Display;
 import android.view.View;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -41,9 +37,11 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.OnColorHintListener;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.WallpaperColorHints;
 import com.android.launcher3.util.WindowBounds;
 
 /**
@@ -51,7 +49,7 @@
  */
 @SuppressWarnings("NewApi")
 public abstract class BaseDraggingActivity extends BaseActivity
-        implements OnColorsChangedListener, DisplayInfoChangeListener {
+        implements OnColorHintListener, DisplayInfoChangeListener {
 
     private static final String TAG = "BaseDraggingActivity";
 
@@ -63,8 +61,7 @@
     protected boolean mIsSafeModeEnabled;
 
     private Runnable mOnStartCallback;
-    private RunnableList mOnResumeCallbacks = new RunnableList();
-
+    private final RunnableList mOnResumeCallbacks = new RunnableList();
     private int mThemeRes = R.style.AppTheme;
 
     @Override
@@ -76,10 +73,7 @@
         DisplayController.INSTANCE.get(this).addChangeListener(this);
 
         // Update theme
-        if (Utilities.ATLEAST_P) {
-            THREAD_POOL_EXECUTOR.execute(() -> getSystemService(WallpaperManager.class)
-                    .addOnColorsChangedListener(this, MAIN_EXECUTOR.getHandler()));
-        }
+        WallpaperColorHints.get(this).registerOnColorHintsChangedListener(this);
         int themeRes = Themes.getActivityThemeRes(this);
         if (themeRes != mThemeRes) {
             mThemeRes = themeRes;
@@ -97,8 +91,9 @@
         mOnResumeCallbacks.add(callback);
     }
 
+    @MainThread
     @Override
-    public void onColorsChanged(WallpaperColors wallpaperColors, int which) {
+    public void onColorHintsChanged(int colorHints) {
         updateTheme();
     }
 
@@ -175,10 +170,8 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        if (Utilities.ATLEAST_P) {
-            getSystemService(WallpaperManager.class).removeOnColorsChangedListener(this);
-        }
         DisplayController.INSTANCE.get(this).removeChangeListener(this);
+        WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 7457f30..b51373c 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -21,10 +21,12 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
 import android.view.View;
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.R;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
@@ -37,7 +39,6 @@
  * A utility class to generate preview bitmap for dragging.
  */
 public class DragPreviewProvider {
-
     private final Rect mTempRect = new Rect();
 
     protected final View mView;
@@ -99,6 +100,14 @@
             height = mView.getHeight();
         }
 
+        if (mView instanceof BubbleTextView) {
+            FastBitmapDrawable icon = ((BubbleTextView) mView).getIcon();
+            Drawable drawable = icon.getConstantState().newDrawable();
+            float xInset = (float) blurSizeOutline / (float) (width + blurSizeOutline);
+            float yInset = (float) blurSizeOutline / (float) (height + blurSizeOutline);
+            return new InsetDrawable(drawable, xInset / 2, yInset / 2, xInset / 2, yInset / 2);
+        }
+
         return new FastBitmapDrawable(
                 BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
                         height + blurSizeOutline, (c) -> drawDragView(c, scale)));
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index a5c663f..60951ba 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -21,8 +21,6 @@
 
 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
 
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -48,16 +46,9 @@
 
     public static final String KEY_THEMED_ICONS = "themed_icons";
 
+    /** Gets the WallpaperColorHints and then uses those to get the correct activity theme res. */
     public static int getActivityThemeRes(Context context) {
-        final int colorHints;
-        if (Utilities.ATLEAST_P) {
-            WallpaperColors colors = context.getSystemService(WallpaperManager.class)
-                    .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
-            colorHints = colors == null ? 0 : colors.getColorHints();
-        } else {
-            colorHints = 0;
-        }
-        return getActivityThemeRes(context, colorHints);
+        return getActivityThemeRes(context, WallpaperColorHints.get(context).getHints());
     }
 
     public static int getActivityThemeRes(Context context, int wallpaperColorHints) {
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
new file mode 100644
index 0000000..1361c1e
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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.util
+
+import android.app.WallpaperColors
+import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_SYSTEM
+import android.app.WallpaperManager.OnColorsChangedListener
+import android.content.Context
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+
+/**
+ * This class caches the system's wallpaper color hints for use by other classes as a performance
+ * enhancer. It also centralizes all the WallpaperManager color hint code in one location.
+ */
+class WallpaperColorHints(private val context: Context) : SafeCloseable {
+    var hints: Int = 0
+        private set
+    private val wallpaperManager
+        get() = context.getSystemService(WallpaperManager::class.java)!!
+    private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
+    private val onClose: SafeCloseable
+
+    init {
+        if (Utilities.ATLEAST_S) {
+            hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
+            val onColorsChangedListener = OnColorsChangedListener { colors, which ->
+                onColorsChanged(colors, which)
+            }
+            UI_HELPER_EXECUTOR.execute {
+                wallpaperManager.addOnColorsChangedListener(
+                    onColorsChangedListener,
+                    MAIN_EXECUTOR.handler
+                )
+            }
+            onClose = SafeCloseable {
+                UI_HELPER_EXECUTOR.execute {
+                    wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
+                }
+            }
+        } else {
+            onClose = SafeCloseable {}
+        }
+    }
+
+    @MainThread
+    private fun onColorsChanged(colors: WallpaperColors?, which: Int) {
+        if ((which and FLAG_SYSTEM) != 0 && Utilities.ATLEAST_S) {
+            val newHints = colors?.colorHints ?: 0
+            if (newHints != hints) {
+                hints = newHints
+                onColorHintsChangedListeners.forEach { it.onColorHintsChanged(newHints) }
+            }
+        }
+    }
+
+    override fun close() = onClose.close()
+
+    fun registerOnColorHintsChangedListener(listener: OnColorHintListener) {
+        onColorHintsChangedListeners.add(listener)
+    }
+
+    fun unregisterOnColorsChangedListener(listener: OnColorHintListener) {
+        onColorHintsChangedListeners.remove(listener)
+    }
+
+    companion object {
+        @VisibleForTesting
+        @JvmField
+        val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+        @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
+    }
+}
+
+interface OnColorHintListener {
+    fun onColorHintsChanged(colorHints: Int)
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 5a52440..ac0749e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -54,6 +54,7 @@
       "src/com/android/launcher3/util/rule/SamplerRule.java",
       "src/com/android/launcher3/util/rule/ScreenRecordRule.java",
       "src/com/android/launcher3/util/rule/ShellCommandRule.java",
+      "src/com/android/launcher3/util/rule/TestIsolationRule.java",
       "src/com/android/launcher3/util/rule/TestStabilityRule.java",
       "src/com/android/launcher3/util/rule/TISBindRule.java",
       "src/com/android/launcher3/util/viewcapture_analysis/*.java",
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 5240e6a..8fac53d 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -68,6 +68,7 @@
 import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestIsolationRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
 
@@ -123,6 +124,10 @@
                 }, DEFAULT_UI_TIMEOUT, launcher);
     }
 
+    public void checkDetectedLeaks() {
+        checkDetectedLeaks(mLauncher);
+    }
+
     private static String getActivityLeakErrorMessage(LauncherInstrumentation launcher) {
         sActivityLeakReported = true;
         return "Activity leak detector has found leaked activities, "
@@ -215,7 +220,8 @@
 
     @Rule
     public TestRule mOrderSensitiveRules = RuleChain
-            .outerRule(new SamplerRule())
+            .outerRule(new TestIsolationRule(this))
+            .around(new SamplerRule())
             .around(new TestStabilityRule())
             .around(getRulesInsideActivityMonitor());
 
@@ -488,6 +494,12 @@
     }
 
     protected void closeLauncherActivity() {
+        finishLauncherActivity();
+        waitForLauncherCondition(
+                "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
+    }
+
+    public void finishLauncherActivity() {
         // Destroy Launcher activity.
         executeOnLauncher(launcher -> {
             if (launcher != null) {
@@ -495,8 +507,6 @@
                 launcher.finish();
             }
         });
-        waitForLauncherCondition(
-                "Launcher still active", launcher -> launcher == null, DEFAULT_UI_TIMEOUT);
     }
 
     protected boolean isInLaunchedApp(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 1ade1b0..9387c66 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -531,6 +531,7 @@
     @Test
     @PortraitLandscape
     @PlatinumTest(focusArea = "launcher")
+    @ScreenRecord // TODO(b/293944634): Remove after flaky debug
     public void testUninstallFromWorkspace() throws Exception {
         installDummyAppAndWaitForUIUpdate();
         try {
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 4580082..261436b 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -227,7 +227,8 @@
                     UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
                     LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
                     DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
-                    SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
+                    SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
+                    LockedUserState.INSTANCE, WallpaperColorHints.INSTANCE,
                     ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
 
             // System settings cache content provider. Ensure that they are statically initialized
diff --git a/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
new file mode 100644
index 0000000..d4bfa6b
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/TestIsolationRule.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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.util.rule;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Isolates tests from some of the state created by the previous test.
+ */
+public class TestIsolationRule implements TestRule {
+    final AbstractLauncherUiTest mTest;
+
+    public TestIsolationRule(AbstractLauncherUiTest test) {
+        mTest = test;
+    }
+
+    @NonNull
+    @Override
+    public Statement apply(@NonNull Statement base, @NonNull Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                mTest.finishLauncherActivity();
+                mTest.checkDetectedLeaks();
+                try {
+                    base.evaluate();
+                } finally {
+                    mTest.finishLauncherActivity();
+                }
+                mTest.checkDetectedLeaks();
+            }
+        };
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 9b4d273..a05b499 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -30,6 +30,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
@@ -308,7 +309,8 @@
             Point stashedTaskbarHintArea = new Point(mLauncher.getRealDisplaySize().x / 2,
                     mLauncher.getRealDisplaySize().y - stashedTaskbarBottomEdge - 1);
             mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_ENTER,
-                    new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null);
+                    new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y), null,
+                    InputDevice.SOURCE_MOUSE);
 
             mLauncher.getDevice().wait(mStashedTaskbarHintScaleCondition,
                     LauncherInstrumentation.WAIT_TIME_MS);
@@ -317,19 +319,21 @@
                     "cursor clicking stashed taskbar to go home")) {
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_HOVER_EXIT,
                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
-                        null);
+                        null, InputDevice.SOURCE_MOUSE);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
-                        LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER);
+                        LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER,
+                        InputDevice.SOURCE_MOUSE);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS,
                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
-                        null);
+                        null, InputDevice.SOURCE_MOUSE);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE,
                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
-                        null);
+                        null, InputDevice.SOURCE_MOUSE);
                 mLauncher.sendPointer(downTime, downTime, MotionEvent.ACTION_UP,
                         new Point(stashedTaskbarHintArea.x, stashedTaskbarHintArea.y),
-                        LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER);
+                        LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER,
+                        InputDevice.SOURCE_MOUSE);
 
                 return mLauncher.getWorkspace();
             }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 940ea3b..262d5ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1728,11 +1728,11 @@
     }
 
     private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
-            float x, float y) {
+            float x, float y, int source) {
         return MotionEvent.obtain(downTime, eventTime, action, 1,
-                new MotionEvent.PointerProperties[] {getPointerProperties(0)},
-                new MotionEvent.PointerCoords[] {getPointerCoords(x, y)},
-                0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+                new MotionEvent.PointerProperties[]{getPointerProperties(0)},
+                new MotionEvent.PointerCoords[]{getPointerCoords(x, y)},
+                0, 0, 1.0f, 1.0f, 0, 0, source, 0);
     }
 
     private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
@@ -1768,6 +1768,12 @@
 
     public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
+        sendPointer(downTime, currentTime, action, point, gestureScope,
+                InputDevice.SOURCE_TOUCHSCREEN);
+    }
+
+    public void sendPointer(long downTime, long currentTime, int action, Point point,
+            GestureScope gestureScope, int source) {
         final boolean hasTIS = hasTIS();
         int pointerCount = mPointerCount;
 
@@ -1867,7 +1873,7 @@
                 ? getTrackpadMotionEvent(
                         downTime, currentTime, action, point.x, point.y, pointerCount,
                         mTrackpadGestureType)
-                : getMotionEvent(downTime, currentTime, action, point.x, point.y);
+                : getMotionEvent(downTime, currentTime, action, point.x, point.y, source);
         if (action == MotionEvent.ACTION_BUTTON_PRESS
                 || action == MotionEvent.ACTION_BUTTON_RELEASE) {
             event.setActionButton(MotionEvent.BUTTON_PRIMARY);