Merge "Desktop Mode Taskbar Recreate Animation" into main
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index fe57da1..46f0e41 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -131,13 +131,11 @@
             android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
             android:readPermission="${applicationId}.permission.READ_SETTINGS" />
 
-        <!--
-        The content provider for exposing various launcher grid options.
-        TODO: Add proper permissions
-        -->
+        <!-- The content provider for exposing various launcher grid options. -->
         <provider
             android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
             android:authorities="${applicationId}.grid_control"
+            android:permission="android.permission.BIND_WALLPAPER"
             android:exported="true" />
 
         <!--
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 625d9b3..3d68dfb 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -29,7 +29,6 @@
         android:layout_height="match_parent"
         android:gravity="center"
         android:scaleType="centerCrop"
-        app:lottie_autoPlay="true"
         app:lottie_loop="true"
 
         app:layout_constraintTop_toTopOf="parent"
@@ -49,11 +48,10 @@
         app:layout_constraintEnd_toEndOf="parent">
 
         <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/text_content_view"
+            android:id="@+id/content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/allset_page_margin_horizontal"
-            android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+            android:paddingHorizontal="@dimen/allset_page_padding_horizontal"
             android:layoutDirection="locale"
             android:textDirection="locale"
             android:forceHasOverlappingRendering="false"
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 0052a73..cf7ba00 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -16,7 +16,7 @@
 -->
 <resources>
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">48dp</dimen>
+    <dimen name="allset_page_padding_horizontal">48dp</dimen>
 
     <!-- Gesture Tutorial menu page -->
     <dimen name="gesture_tutorial_menu_padding_horizontal">48dp</dimen>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 4996582..3e72651 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -37,7 +37,7 @@
     <dimen name="overview_actions_top_margin">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">120dp</dimen>
+    <dimen name="allset_page_padding_horizontal">120dp</dimen>
     <dimen name="allset_page_allset_text_size">38sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">15sp</dimen>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 05f0695..52ebdae 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -277,7 +277,7 @@
     <dimen name="gesture_tutorial_taskbar_margin_bottom">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">40dp</dimen>
+    <dimen name="allset_page_padding_horizontal">40dp</dimen>
     <dimen name="allset_page_allset_text_size">36sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">14sp</dimen>
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5b78c0a..84ae0fe 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,7 +56,6 @@
 import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -79,7 +78,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -94,7 +92,6 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.util.Pair;
 import android.util.Size;
 import android.view.CrossWindowBlurListeners;
@@ -249,16 +246,6 @@
     // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
     private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
             new ArrayList<>();
-    private final ContentObserver mAnimationRemovalObserver = new ContentObserver(
-            ORDERED_BG_EXECUTOR.getHandler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
-                    Global.ANIMATOR_DURATION_SCALE, 1f) > 0
-                    || Global.getFloat(mLauncher.getContentResolver(),
-                    Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
-        }
-    };
 
     private DeviceProfile mDeviceProfile;
 
@@ -287,7 +274,6 @@
     // Pairs of window starting type and starting window background color for starting tasks
     // Will never be larger than MAX_NUM_TASKS
     private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
-    private boolean mAreAnimationsEnabled = true;
 
     private final Interpolator mOpeningXInterpolator;
     private final Interpolator mOpeningInterpolator;
@@ -298,7 +284,6 @@
         mHandler = new Handler(Looper.getMainLooper());
         mDeviceProfile = mLauncher.getDeviceProfile();
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
-        checkAndMonitorIfAnimationsAreEnabled();
 
         Resources res = mLauncher.getResources();
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -1220,8 +1205,6 @@
         unregisterRemoteTransitions();
         mLauncher.removeOnDeviceProfileChangeListener(this);
         SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
-        ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
-                .unregisterContentObserver(mAnimationRemovalObserver));
         if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
             throw new IllegalStateException("Failed to run onEndCallback created from"
                     + " getActivityLaunchOptions()");
@@ -1275,17 +1258,6 @@
         }
     }
 
-    private void checkAndMonitorIfAnimationsAreEnabled() {
-        ORDERED_BG_EXECUTOR.execute(() -> {
-            mAnimationRemovalObserver.onChange(true);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
-
-        });
-    }
-
     private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
         for (RemoteAnimationTarget target : targets) {
             if (target.mode == mode && target.taskInfo != null
@@ -1421,7 +1393,8 @@
                     (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
                     mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
                     isTransluscent, fallbackBackgroundColor);
-        } else if (launcherView != null && mAreAnimationsEnabled) {
+        } else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get(
+                mLauncher).isRemoveAnimationEnabled()) {
             floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
                     mLauncher.getTaskbarUIController() == null
                             ? null
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index b4ffb74..5d1288c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -39,6 +39,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.R
+import com.android.launcher3.RemoveAnimationSettingsTracker
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
@@ -128,6 +129,26 @@
         activityContext.dragLayer.post { maybeShowSearchEdu() }
     }
 
+    /**
+     * Turns off auto play of lottie animations if user has opted to remove animation else attaches
+     * click listener to allow user to play or pause animations.
+     */
+    fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
+        for (animationView in animationViews) {
+            if (
+                RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
+                    .isRemoveAnimationEnabled()
+            ) {
+                animationView.pauseAnimation()
+            } else {
+                animationView.setOnClickListener {
+                    if (animationView.isAnimating) animationView.pauseAnimation()
+                    else animationView.playAnimation()
+                }
+            }
+        }
+    }
+
     /** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
     fun maybeShowSwipeEdu() {
         if (
@@ -145,7 +166,9 @@
                 requireViewById(R.id.taskbar_edu_title),
                 TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
             )
-            requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
+            val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
+            swipeAnimation.supportLightTheme()
+            handleEduAnimations(listOf(swipeAnimation))
             show()
         }
     }
@@ -174,6 +197,7 @@
             splitscreenAnim.supportLightTheme()
             suggestionsAnim.supportLightTheme()
             pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
             if (DisplayController.isTransientTaskbar(activityContext)) {
                 splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
                 suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
@@ -249,9 +273,6 @@
 
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
-                .supportLightTheme()
-
             TypefaceUtils.setTypeface(
                 requireViewById(R.id.taskbar_edu_title),
                 TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
@@ -261,6 +282,10 @@
                 TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
             )
 
+            val pinningAnim =
+                requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
+            pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(pinningAnim))
             updateLayoutParams<BaseDragLayer.LayoutParams> {
                 if (DisplayController.isTransientTaskbar(activityContext)) {
                     bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -304,7 +329,9 @@
         inflateTooltip(R.layout.taskbar_edu_search)
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
+            val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
+            searchEdu.supportLightTheme()
+            handleEduAnimations(listOf(searchEdu))
             val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
 
             TypefaceUtils.setTypeface(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
index 8b53ff1..e9c62d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
@@ -30,11 +30,14 @@
 class TypefaceUtils {
 
     companion object {
-        const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
-        const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
         const val FONT_FAMILY_BODY_SMALL_BASELINE = "variable-body-small"
         const val FONT_FAMILY_BODY_MEDIUM_BASELINE = "variable-body-medium"
+        const val FONT_FAMILY_BODY_LARGE_BASELINE = "variable-body-large"
         const val FONT_FAMILY_LABEL_LARGE_BASELINE = "variable-label-large"
+        const val FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED = "variable-display-small-emphasized"
+        const val FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED = "variable-display-medium-emphasized"
+        const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
+        const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
 
         @JvmStatic
         @JvmOverloads
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 953b0c5..c1bb250 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -107,6 +108,9 @@
 
     private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
 
+    private static final String KEY_BACKGROUND_ANIMATION_TOGGLED_ON =
+            "background_animation_toggled_on";
+
     private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
 
     private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
@@ -124,6 +128,9 @@
 
     private AnimatorPlaybackController mLauncherStartAnim = null;
 
+    // Auto play background animation by default
+    private boolean mBackgroundAnimationToggledOn = true;
+
     private TextView mHintView;
 
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChange;
@@ -200,6 +207,15 @@
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
                 getTheme());
 
+        mBackgroundAnimationToggledOn = savedInstanceState == null
+                || savedInstanceState.getBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, true);
+        // The animated background is behind a scroll view, which intercepts all input.
+        // However, the content view also covers the full screen
+        requireViewById(R.id.content).setOnClickListener(v -> {
+            mBackgroundAnimationToggledOn = !mBackgroundAnimationToggledOn;
+            maybeResumeOrPauseBackgroundAnimation();
+        });
+
         setUpBackgroundAnimation(getDP().isTablet);
         getIDP().addOnChangeListener(mOnIDPChangeListener);
 
@@ -208,6 +224,12 @@
         ActivityPreloadUtil.preloadOverviewForSUWAllSet(this);
     }
 
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, mBackgroundAnimationToggledOn);
+    }
+
     private InvariantDeviceProfile getIDP() {
         return LauncherAppState.getInstance(this).getInvariantDeviceProfile();
     }
@@ -368,8 +390,10 @@
 
     private void maybeResumeOrPauseBackgroundAnimation() {
         boolean shouldPlayAnimation =
-                getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
-                        && isResumed();
+                !RemoveAnimationSettingsTracker.INSTANCE.get(this).isRemoveAnimationEnabled()
+                        && getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
+                        && isResumed()
+                        && mBackgroundAnimationToggledOn;
         if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) {
             mAnimatedBackground.pauseAnimation();
         } else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 0fc95e2..e73fb3b 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.taskbar.TypefaceUtils;
 import com.android.launcher3.views.ClipIconView;
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -177,6 +178,7 @@
 
         mFeedbackTitleView.setText(getIntroductionTitle());
         mFeedbackSubtitleView.setText(getIntroductionSubtitle());
+        setTitleTypefaces();
 
         mExitingAppView.setClipToOutline(true);
         mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
@@ -434,6 +436,10 @@
 
         if (isGestureSuccessful) {
             if (mTutorialFragment.isAtFinalStep()) {
+                TypefaceUtils.setTypeface(
+                        mDoneButton,
+                        TypefaceUtils.FONT_FAMILY_LABEL_LARGE_BASELINE
+                );
                 showActionButton();
             }
 
@@ -458,7 +464,8 @@
         pauseAndHideLottieAnimation();
         mCheckmarkAnimation.setVisibility(View.VISIBLE);
         mCheckmarkAnimation.playAnimation();
-        mFeedbackTitleView.setTextAppearance(mContext, getSuccessTitleTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
+        setTitleTypefaces();
     }
 
     public boolean isGestureCompleted() {
@@ -513,8 +520,10 @@
         updateDrawables();
         updateLayout();
 
-        mFeedbackTitleView.setTextAppearance(mContext, getTitleTextAppearance());
-        mDoneButton.setTextAppearance(mContext, getDoneButtonTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
+        mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
+
+        setTitleTypefaces();
         mDoneButton.getBackground().setTint(getDoneButtonColor());
         mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
                 ? R.raw.checkmark_animation_end
@@ -533,6 +542,21 @@
         }
     }
 
+    /**
+     * Apply expressive typefaces to the feedback title and subtitle views.
+     */
+    private void setTitleTypefaces() {
+        TypefaceUtils.setTypeface(
+                mFeedbackTitleView,
+                mTutorialFragment.isLargeScreen()
+                        ? TypefaceUtils.FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED
+                        : TypefaceUtils.FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED);
+        TypefaceUtils.setTypeface(
+                mFeedbackSubtitleView,
+                TypefaceUtils.FONT_FAMILY_BODY_LARGE_BASELINE
+        );
+    }
+
     protected void resetViewsForBackGesture() {
         mFakeTaskView.setVisibility(View.VISIBLE);
         mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f53583a..d17dfb8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3908,6 +3908,22 @@
                 // the only invariant point in landscape split screen.
                 snapToLastTask = true;
             }
+            if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) {
+                TaskView lastLargeTile = mUtils.getLastLargeTaskView();
+                if (lastLargeTile != null) {
+                    // Calculate the distance to put last large tile back to middle of the screen.
+                    int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
+                    int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile));
+                    longGridRowWidthDiff = primaryScroll - lastLargeTileScroll;
+
+                    if (!isClearAllHidden) {
+                        // If ClearAllButton is visible, reduce the distance by scroll difference
+                        // between ClearAllButton and the last task.
+                        longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0,
+                                getPagedOrientationHandler().getPrimarySize(mClearAllButton));
+                    }
+                }
+            }
 
             // If we need to animate the grid to compensate the clear all gap, we split the second
             // half of the dismiss pending animation (in which the non-dismissed tasks slide into
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 8df7430..67318ac 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -85,6 +85,9 @@
     /** Counts [TaskView]s that are large tiles. */
     fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
 
+    /** Counts [TaskView]s that are grid tasks. */
+    fun getGridTaskCount(): Int = taskViews.count { it.isGridTask }
+
     /** Returns the first TaskView that should be displayed as a large tile. */
     fun getFirstLargeTaskView(): TaskView? =
         taskViews.firstOrNull {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5072e37..d6abb56 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.app.animation.Interpolators.SCROLL;
+import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
@@ -33,7 +34,6 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -1756,8 +1756,8 @@
         }
 
         if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
-            duration *= Settings.Global.getFloat(getContext().getContentResolver(),
-                    Settings.Global.WINDOW_ANIMATION_SCALE, 1);
+            duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue(
+                    WINDOW_ANIMATION_SCALE_URI);
         }
 
         whichPage = validateNewPage(whichPage);
diff --git a/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
new file mode 100644
index 0000000..dbc04f1
--- /dev/null
+++ b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.provider.Settings.Global.ANIMATOR_DURATION_SCALE
+import android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE
+import android.provider.Settings.Global.WINDOW_ANIMATION_SCALE
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/** Tracker Class for when user turns on/off remove animation setting. */
+@LauncherAppSingleton
+class RemoveAnimationSettingsTracker
+@Inject
+constructor(@ApplicationContext val context: Context, tracker: DaggerSingletonTracker) :
+    ContentObserver(Handler(Looper.getMainLooper())) {
+
+    private val contentResolver = context.contentResolver
+
+    /** Caches the last seen value for registered keys. */
+    private val cache: MutableMap<Uri, Float> = ConcurrentHashMap()
+
+    init {
+        UI_HELPER_EXECUTOR.execute {
+            contentResolver.registerContentObserver(WINDOW_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(TRANSITION_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(ANIMATOR_DURATION_SCALE_URI, false, this)
+        }
+
+        tracker.addCloseable {
+            UI_HELPER_EXECUTOR.execute { contentResolver.unregisterContentObserver(this) }
+        }
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [updateValue] to fetch.
+     */
+    fun getValue(uri: Uri): Float {
+        return getValue(uri, 1f)
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [getValueFromSettingsGlobal] to fetch.
+     */
+    private fun getValue(uri: Uri, defaultValue: Float): Float {
+        return cache.computeIfAbsent(uri) { getValueFromSettingsGlobal(uri, defaultValue) }
+    }
+
+    /** Returns if user has opted into having no animation on their device. */
+    fun isRemoveAnimationEnabled(): Boolean {
+        return getValue(WINDOW_ANIMATION_SCALE_URI) == 0f &&
+            getValue(TRANSITION_ANIMATION_SCALE_URI) == 0f &&
+            getValue(ANIMATOR_DURATION_SCALE_URI) == 0f
+    }
+
+    override fun onChange(selfChange: Boolean, uri: Uri?) {
+        if (uri == null) return
+        updateValue(uri)
+    }
+
+    private fun getValueFromSettingsGlobal(uri: Uri, defaultValue: Float = 1f): Float {
+        return Settings.Global.getFloat(contentResolver, uri.lastPathSegment, defaultValue)
+    }
+
+    private fun updateValue(uri: Uri, defaultValue: Float = 1f) {
+        val newValue = getValueFromSettingsGlobal(uri, defaultValue)
+        cache[uri] = newValue
+    }
+
+    companion object {
+        @JvmField
+        val INSTANCE =
+            DaggerSingletonObject(LauncherAppComponent::getRemoveAnimationSettingsTracker)
+        @JvmField
+        val WINDOW_ANIMATION_SCALE_URI: Uri = Settings.Global.getUriFor(WINDOW_ANIMATION_SCALE)
+        @JvmField
+        val TRANSITION_ANIMATION_SCALE_URI: Uri =
+            Settings.Global.getUriFor(TRANSITION_ANIMATION_SCALE)
+        @JvmField
+        val ANIMATOR_DURATION_SCALE_URI: Uri = Settings.Global.getUriFor(ANIMATOR_DURATION_SCALE)
+    }
+}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 249a214..150761f 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
 import com.android.launcher3.graphics.GridCustomizationsProxy;
 import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.LauncherIcons.IconPool;
@@ -80,8 +81,8 @@
     LockedUserState getLockedUserState();
     InvariantDeviceProfile getIDP();
     IconPool getIconPool();
+    RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
     LauncherAppState getLauncherAppState();
-
     GridCustomizationsProxy getGridCustomizationsProxy();
 
     /** Builder for LauncherBaseAppComponent. */
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 062c753..40863c4 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -24,11 +24,9 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
@@ -297,12 +295,6 @@
 
     @Override
     public Bundle call(@NonNull String method, String arg, Bundle extras) {
-        if (mContext.checkPermission("android.permission.BIND_WALLPAPER",
-                Binder.getCallingPid(), Binder.getCallingUid())
-                != PackageManager.PERMISSION_GRANTED) {
-            return null;
-        }
-
         if (METHOD_GET_PREVIEW.equals(method)) {
             return getPreview(extras);
         } else {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d1eceb9..3cdb250 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -239,25 +239,31 @@
                             boolean isTargetValid = !cn.getClassName().equals(
                                     IconCache.EMPTY_CLASS_NAME);
                             if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+                                int requestQuery = ShortcutRequest.PINNED;
+                                if (Flags.restoreArchivedShortcuts()) {
+                                    // Avoid race condition where shortcut service has no record of
+                                    // unarchived shortcut being pinned after restore.
+                                    // Launcher should be source-of-truth for if shortcut is pinned.
+                                    requestQuery = ShortcutRequest.ALL;
+                                }
                                 List<ShortcutInfo> shortcut =
                                         new ShortcutRequest(context, mUser)
                                                 .forPackage(cn.getPackageName(),
                                                         itemInfo.getDeepShortcutId())
-                                                .query(ShortcutRequest.PINNED);
-                                if (shortcut.isEmpty()
-                                        && !(Flags.restoreArchivedShortcuts()
-                                            && !itemInfo.isArchived())
-                                ) {
+                                                .query(requestQuery);
+                                if (shortcut.isEmpty()) {
                                     isTargetValid = false;
                                     if (DEBUG) {
-                                        Log.d(TAG, "Pinned Shortcut not found for updated"
-                                                + " package=" + itemInfo.getTargetPackage());
-                                    }
-                                } else if (!shortcut.isEmpty()) {
-                                    if (DEBUG) {
-                                        Log.d(TAG, "Found pinned shortcut for updated"
+                                        Log.d(TAG, "Shortcut not found for updated"
                                                 + " package=" + itemInfo.getTargetPackage()
-                                                + ", isTargetValid=" + isTargetValid);
+                                                + ", isArchived=" + itemInfo.isArchived());
+                                    }
+                                } else {
+                                    if (DEBUG) {
+                                        Log.d(TAG, "Found shortcut for updated"
+                                                + " package=" + itemInfo.getTargetPackage()
+                                                + ", isTargetValid=" + isTargetValid
+                                                + ", isArchived=" + itemInfo.isArchived());
                                     }
                                     itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
diff --git a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
index 1e21ee5..44df5b8 100644
--- a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.util.BaseLauncherActivityTest;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.views.ActivityContext;
 
 import org.junit.Test;
@@ -64,7 +63,6 @@
     }
 
     @Test
-    @ScreenRecord  //b/378167329
     public void testAllAppsExitSearchAndFocusSearchResults() {
         loadLauncherSync();
         goToState(LauncherState.ALL_APPS);
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index a123170..38970fe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
@@ -37,6 +36,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
+import com.android.launcher3.dagger.LauncherComponentProvider;
 import com.android.launcher3.icons.mono.ThemedIconDrawable;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.util.BaseLauncherActivityTest;
@@ -139,7 +139,7 @@
         return icon;
     }
 
-    private void setThemeEnabled(boolean isEnabled) throws Exception {
+    private void setThemeEnabled(boolean isEnabled) {
         Uri uri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(targetContext().getPackageName() + ".grid_control")
@@ -147,11 +147,10 @@
                 .build();
         ContentValues values = new ContentValues();
         values.put("boolean_value", isEnabled);
-        try (ContentProviderClient client = targetContext().getContentResolver()
-                .acquireContentProviderClient(uri)) {
-            int result = client.update(uri, values, null);
-            assertTrue(result > 0);
-        }
+
+        int result = LauncherComponentProvider.get(targetContext()).getGridCustomizationsProxy()
+                .update(uri, values, null, null);
+        assertTrue(result > 0);
     }
 
     private void switchToAllApps() {