Merge "Remove Widget Resize Border on Accessibility delete" into main
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index c50e82d..c2cabd0 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -229,9 +229,7 @@
                     (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
             if (isPredictedIcon(child) && child.isEnabled()) {
                 PredictedAppIcon icon = (PredictedAppIcon) child;
-                boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
-                icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
-                if (animateIconChange) {
+                if (icon.applyFromWorkspaceItemWithAnimation(predictedItem, numViewsAnimated)) {
                     numViewsAnimated++;
                 }
                 icon.finishBinding(mPredictionLongClickListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 741853e..6b9f5a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
 import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.Themes;
@@ -595,10 +596,12 @@
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
             if (hotseatView instanceof BubbleTextView btv
                     && hotseatItemInfo instanceof WorkspaceItemInfo workspaceInfo) {
-                boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
-                btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
-                if (animate) {
-                    numViewsAnimated++;
+                if (btv instanceof PredictedAppIcon pai) {
+                    if (pai.applyFromWorkspaceItemWithAnimation(workspaceInfo, numViewsAnimated)) {
+                        numViewsAnimated++;
+                    }
+                } else {
+                    btv.applyFromWorkspaceItem(workspaceInfo);
                 }
             }
             setClickAndLongClickListenersForIcon(hotseatView);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 535ae1c..caac35e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
-import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
 import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
 
 import android.animation.Animator;
@@ -26,8 +25,6 @@
 import android.animation.Keyframe;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
@@ -37,7 +34,6 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Process;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
@@ -48,12 +44,12 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
-import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.icons.LauncherIcons;
@@ -64,10 +60,6 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A BubbleTextView with a ring around it's drawable
  */
@@ -105,12 +97,12 @@
     private final BlurMaskFilter mShadowFilter;
 
     private boolean mIsPinned = false;
-    private int mPlateColor;
+    private final AnimColorHolder mPlateColor = new AnimColorHolder();
     boolean mDrawForDrag = false;
 
-    // Used for the "slot-machine" education animation.
-    private List<Drawable> mSlotMachineIcons;
-    private Animator mSlotMachineAnim;
+    // Used for the "slot-machine" animation when prediction changes.
+    private final Rect mSlotIconBound = new Rect(0, 0, getIconSize(), getIconSize());
+    private Drawable mSlotMachineIcon;
     private float mSlotMachineIconTranslationY;
 
     // Used to animate the "ring" around predicted icons
@@ -153,34 +145,26 @@
     @Override
     public void onDraw(Canvas canvas) {
         int count = canvas.save();
-        boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
+        boolean isSlotMachineAnimRunning = mSlotMachineIcon != null;
         if (!mIsPinned) {
             drawEffect(canvas);
             if (isSlotMachineAnimRunning) {
                 // Clip to to outside of the ring during the slot machine animation.
                 canvas.clipPath(mRingPath);
             }
-            canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
-            canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
+            canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO,
+                    getWidth() * .5f, getHeight() * .5f);
+            if (isSlotMachineAnimRunning) {
+                canvas.translate(0, mSlotMachineIconTranslationY);
+                mSlotMachineIcon.setBounds(mSlotIconBound);
+                mSlotMachineIcon.draw(canvas);
+                canvas.translate(0, getSlotMachineIconPlusSpacingSize());
+            }
         }
-        if (isSlotMachineAnimRunning) {
-            drawSlotMachineIcons(canvas);
-        } else {
-            super.onDraw(canvas);
-        }
+        super.onDraw(canvas);
         canvas.restoreToCount(count);
     }
 
-    private void drawSlotMachineIcons(Canvas canvas) {
-        canvas.translate((getWidth() - getIconSize()) / 2f,
-                (getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
-        for (Drawable icon : mSlotMachineIcons) {
-            icon.setBounds(0, 0, getIconSize(), getIconSize());
-            icon.draw(canvas);
-            canvas.translate(0, getSlotMachineIconPlusSpacingSize());
-        }
-    }
-
     private float getSlotMachineIconPlusSpacingSize() {
         return getIconSize() + getOutlineOffsetY();
     }
@@ -196,104 +180,88 @@
         mIsDrawingDot = false;
     }
 
-    @Override
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
-        // Create the slot machine animation first, since it uses the current icon to start.
-        Animator slotMachineAnim = animate
-                ? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
-                : null;
-        super.applyFromWorkspaceItem(info, animate, staggerIndex);
-        int oldPlateColor = mPlateColor;
+    /**
+     * Returns whether the newInfo differs from the current getTag().
+     */
+    private boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
+        boolean changedIcons = getTag() instanceof WorkspaceItemInfo oldInfo
+                && oldInfo.getTargetComponent() != null
+                && newInfo.getTargetComponent() != null
+                && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
+        return changedIcons && isShown();
+    }
 
-        int newPlateColor;
+    @Override
+    public void applyIconAndLabel(ItemInfoWithIcon info) {
+        super.applyIconAndLabel(info);
         if (getIcon().isThemed()) {
-            newPlateColor = getResources().getColor(android.R.color.system_accent1_300);
+            mPlateColor.endColor = getResources().getColor(android.R.color.system_accent1_300);
         } else {
             float[] hctPlateColor = new float[3];
             ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
-            newPlateColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
+            mPlateColor.endColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
         }
+        mPlateColor.onUpdate();
+    }
+
+    /**
+     * Tries to apply the icon with animation and returns true if the icon was indeed animated
+     */
+    public boolean applyFromWorkspaceItemWithAnimation(WorkspaceItemInfo info, int staggerIndex) {
+        boolean animate = shouldAnimateIconChange(info);
+        Drawable oldIcon = getIcon();
+        int oldPlateColor = mPlateColor.currentColor;
+        applyFromWorkspaceItem(info, null);
+
+        setContentDescription(
+                mIsPinned ? info.contentDescription :
+                        getContext().getString(R.string.hotseat_prediction_content_description,
+                                info.contentDescription));
 
         if (!animate) {
-            mPlateColor = newPlateColor;
-        }
-        if (mIsPinned) {
-            setContentDescription(info.contentDescription);
+            mPlateColor.startColor = mPlateColor.endColor;
+            mPlateColor.progress.value = 1;
+            mPlateColor.onUpdate();
         } else {
-            setContentDescription(
-                    getContext().getString(R.string.hotseat_prediction_content_description,
-                            info.contentDescription));
-        }
+            mPlateColor.startColor = oldPlateColor;
+            mPlateColor.progress.value = 0;
+            mPlateColor.onUpdate();
 
-        if (animate) {
-            ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    oldPlateColor, newPlateColor);
-            plateColorAnim.addUpdateListener(valueAnimator -> {
-                mPlateColor = (int) valueAnimator.getAnimatedValue();
-                invalidate();
-            });
             AnimatorSet changeIconAnim = new AnimatorSet();
-            if (slotMachineAnim != null) {
+
+            ObjectAnimator plateColorAnim =
+                    ObjectAnimator.ofFloat(mPlateColor.progress, AnimatedFloat.VALUE, 0, 1);
+            plateColorAnim.setAutoCancel(true);
+            changeIconAnim.play(plateColorAnim);
+
+            if (!mIsPinned && oldIcon != null) {
+                // Play the slot machine icon
+                mSlotMachineIcon = oldIcon;
+
+                float finalTrans = -getSlotMachineIconPlusSpacingSize();
+                Keyframe[] keyframes = new Keyframe[] {
+                        Keyframe.ofFloat(0f, 0f),
+                        Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
+                        Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
+                };
+                keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
+                keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
+
+                ObjectAnimator slotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+                        PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
+                slotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
+                    mSlotMachineIcon = null;
+                    mSlotMachineIconTranslationY = 0;
+                    invalidate();
+                }));
+                slotMachineAnim.setAutoCancel(true);
                 changeIconAnim.play(slotMachineAnim);
             }
-            changeIconAnim.play(plateColorAnim);
+
             changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
             changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
         }
-    }
-
-    /**
-     * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
-     * and ending with the original icon.
-     */
-    public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
-        return createSlotMachineAnim(iconsToAnimate, true);
-    }
-
-    /**
-     * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
-     * with the original icon, then cycling through the given icons, optionally ending back with
-     * the original icon.
-     * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
-     *                            than the last item in iconsToAnimate.
-     */
-    public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
-            boolean endWithOriginalIcon) {
-        if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
-            return null;
-        }
-        if (mSlotMachineAnim != null) {
-            mSlotMachineAnim.end();
-        }
-
-        // Bookend the other animating icons with the original icon on both ends.
-        mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
-        mSlotMachineIcons.add(getIcon());
-        iconsToAnimate.stream()
-                .map(iconInfo -> iconInfo.newIcon(mContext, FLAG_THEMED))
-                .forEach(mSlotMachineIcons::add);
-        if (endWithOriginalIcon) {
-            mSlotMachineIcons.add(getIcon());
-        }
-
-        float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
-        Keyframe[] keyframes = new Keyframe[] {
-                Keyframe.ofFloat(0f, 0f),
-                Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
-                Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
-        };
-        keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
-        keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
-
-        mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
-                PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
-        mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
-            mSlotMachineIcons = null;
-            mSlotMachineAnim = null;
-            mSlotMachineIconTranslationY = 0;
-            invalidate();
-        }));
-        return mSlotMachineAnim;
+        return animate;
     }
 
     /**
@@ -345,6 +313,7 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
+        mSlotIconBound.offsetTo((w - getIconSize()) / 2, (h - getIconSize()) / 2);
         updateRingPath();
     }
 
@@ -355,18 +324,12 @@
     }
 
     private void updateRingPath() {
-        boolean isBadged = false;
-        if (getTag() instanceof WorkspaceItemInfo) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
-            isBadged = !Process.myUserHandle().equals(info.user)
-                    || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        }
-
         mRingPath.reset();
         mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
-
         mRingPath.addPath(mShapePath, mTmpMatrix);
-        if (isBadged) {
+
+        FastBitmapDrawable icon = getIcon();
+        if (icon != null && icon.getBadge() != null) {
             float outlineSize = mNormalizedIconSize * RING_EFFECT_RATIO;
             float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
             float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
@@ -422,7 +385,7 @@
             canvas.scale(mRingScale, mRingScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
         }
         canvas.drawPath(mRingPath, mIconRingPaint);
-        mIconRingPaint.setColor(mPlateColor);
+        mIconRingPaint.setColor(mPlateColor.currentColor);
         mIconRingPaint.setMaskFilter(null);
         canvas.drawPath(mRingPath, mIconRingPaint);
         canvas.restoreToCount(count);
@@ -474,6 +437,21 @@
         return icon;
     }
 
+    private class AnimColorHolder {
+
+        public final AnimatedFloat progress = new AnimatedFloat(this::onUpdate, 1);
+        public final ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+        public Integer startColor = 0;
+        public Integer endColor = 0;
+
+        public int currentColor = 0;
+
+        private void onUpdate() {
+            currentColor = (Integer) evaluator.evaluate(progress.value, startColor, endColor);
+            invalidate();
+        }
+    }
+
     /**
      * Draws Predicted Icon outline on cell layout
      */
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index ec6a9c3..961dc58 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -337,7 +337,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @ScreenRecord // b/313464374
     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6f293b6..501e650 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -308,6 +308,8 @@
     <string name="folder_name_format_exact">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> items</string>
     <!-- Folder name format when folder has 4 or more items shown in preview-->
     <string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
+    <!-- Accessibility announement for unnamed folders -->
+    <string name="unnamed_folder">Unnamed folder</string>
 
     <!-- App pair accessibility -->
     <!-- App pair name -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c78666e..b8fdfe7 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -369,27 +369,9 @@
 
     @UiThread
     public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
-        applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
-    }
-
-    @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
         applyFromWorkspaceItem(info, null);
     }
 
-    /**
-     * Returns whether the newInfo differs from the current getTag().
-     */
-    public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
-        WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
-                ? (WorkspaceItemInfo) getTag()
-                : null;
-        boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
-                && newInfo.getTargetComponent() != null
-                && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
-        return changedIcons && isShown();
-    }
-
     @Override
     public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
         if (delegate instanceof BaseAccessibilityDelegate) {
@@ -1333,7 +1315,6 @@
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof WorkspaceItemInfo) {
                 applyFromWorkspaceItem((WorkspaceItemInfo) info);
-                mActivity.invalidateParent(info);
             } else if (info != null) {
                 applyFromItemInfoWithIcon(info);
             }
@@ -1347,12 +1328,11 @@
      * Verifies that the current icon is high-res otherwise posts a request to load the icon.
      */
     public void verifyHighRes() {
-        if (mIconLoadRequest != null) {
-            mIconLoadRequest.cancel();
-            mIconLoadRequest = null;
-        }
         if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress
                 && info.getMatchingLookupFlag().useLowRes()) {
+            if (mIconLoadRequest != null) {
+                mIconLoadRequest.cancel();
+            }
             mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
                     .updateIconInBackground(BubbleTextView.this, info);
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b38efc2..b9e4710 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -72,7 +72,6 @@
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
-import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -206,7 +205,6 @@
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -831,26 +829,6 @@
         return true;
     }
 
-    @Override
-    public void invalidateParent(ItemInfo info) {
-        if (info.container >= 0) {
-            View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
-            if (collectionIcon instanceof FolderIcon folderIcon
-                    && collectionIcon.getTag() instanceof FolderInfo) {
-                if (createFolderGridOrganizer(getDeviceProfile())
-                        .setFolderInfo((FolderInfo) folderIcon.getTag())
-                        .isItemInPreview(info.rank)) {
-                    folderIcon.invalidate();
-                }
-            } else if (collectionIcon instanceof AppPairIcon appPairIcon
-                    && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) {
-                if (appPairInfo.getContents().contains(info)) {
-                    appPairIcon.getIconDrawableArea().redraw();
-                }
-            }
-        }
-    }
-
     /**
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 81d6631..78b53a9 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -199,6 +199,8 @@
                 host.requestFocus();
                 host.sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
                 host.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
+                AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false,
+                        AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME);
             });
             return true;
         } else if (action == DEEP_SHORTCUTS) {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index de1bcc3..2481a1a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -816,6 +816,10 @@
      * Returns a formatted accessibility title for folder
      */
     public String getAccessiblityTitle(CharSequence title) {
+        if (title == null) {
+            // Avoids "Talkback -> Folder: null" announcement.
+            title = getContext().getString(R.string.unnamed_folder);
+        }
         int size = mInfo.getContents().size();
         if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
             return getContext().getString(R.string.folder_name_format_exact, title, size);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 8d751e6..22f1164 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
-import android.graphics.drawable.Drawable;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -541,19 +540,10 @@
             ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
             for (int i = parent.getChildCount() - 1; i >= 0; i--) {
                 View iconView = parent.getChildAt(i);
-                Drawable d = null;
                 if (iconView instanceof BubbleTextView btv) {
                     btv.verifyHighRes();
-                    d = btv.getIcon();
                 } else if (iconView instanceof AppPairIcon api) {
                     api.verifyHighRes();
-                    d = api.getIconDrawableArea().getDrawable();
-                }
-
-                // Set the callback back to the actual icon, in case
-                // it was captured by the FolderIcon
-                if (d != null) {
-                    d.setCallback(iconView);
                 }
             }
         }
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2276ac7..5ee6a25 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.folder;
 
 import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -43,12 +44,14 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.apppairs.AppPairIconDrawingParams;
 import com.android.launcher3.apppairs.AppPairIconGraphic;
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
@@ -460,6 +463,18 @@
         // Set the callback to FolderIcon as it is responsible to drawing the icon. The
         // callback will be released when the folder is opened.
         p.drawable.setCallback(mIcon);
+
+        // Verify high res
+        if (item instanceof ItemInfoWithIcon info
+                && info.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)) {
+            LauncherAppState.getInstance(mContext).getIconCache().updateIconInBackground(
+                    newInfo -> {
+                        if (p.item == newInfo) {
+                            setDrawable(p, newInfo);
+                            mIcon.invalidate();
+                        }
+                    }, info);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b8481c5..b164b7f 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -105,13 +105,6 @@
         return null;
     }
 
-    /**
-     * For items with tree hierarchy, notifies the activity to invalidate the parent when a root
-     * is invalidated
-     * @param info info associated with a root node.
-     */
-    default void invalidateParent(ItemInfo info) { }
-
     default AccessibilityDelegate getAccessibilityDelegate() {
         return null;
     }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index b877d7a..94ae69c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -14,6 +16,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.icons.cache.BaseIconCache;
@@ -108,6 +111,13 @@
 
         Point cellSize = new Point();
         for (DeviceProfile dp : idp.supportedProfiles) {
+            // On phones we no longer support regular landscape, only fixed landscape for this
+            // reason we don't need to take regular landscape into account in phones
+            if (Flags.oneGridSpecs() && dp.inv.deviceType == TYPE_PHONE
+                    && dp.inv.isFixedLandscape != dp.isLandscape) {
+                continue;
+            }
+
             dp.getCellSize(cellSize);
             Rect widgetPadding = dp.widgetPadding;
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
index 8952b85..2e556e8 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
@@ -80,6 +80,15 @@
             )
     }
 
+    /**
+     * Sets the test app for app icons to the specified Component
+     *
+     * @param testAppComponent ComponentName to use for app icons
+     */
+    fun setTestAppComponent(testAppComponent: ComponentName) {
+        appComponentName = testAppComponent
+    }
+
     private fun addCorrespondingWidgetRect(
         widgetRect: WidgetRect,
         transaction: FavoriteItemsTransaction,
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 7f481b7..548cf5b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -17,21 +17,22 @@
 package com.android.launcher3.folder
 
 import android.R
-import android.content.Context
 import android.graphics.Bitmap
 import android.os.Process
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
 import com.android.launcher3.LauncherPrefs.Companion.get
 import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver
+import com.android.launcher3.icons.PlaceHolderIconDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
 import com.android.launcher3.icons.mono.MonoThemedBitmap
 import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
 import com.android.launcher3.model.data.WorkspaceItemInfo
@@ -40,6 +41,7 @@
 import com.android.launcher3.util.FlagOp
 import com.android.launcher3.util.LauncherLayoutBuilder
 import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
 import com.android.launcher3.util.TestUtil
 import com.android.launcher3.util.UserIconInfo
 import com.google.common.truth.Truth.assertThat
@@ -47,6 +49,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 /** Tests for [PreviewItemManager] */
 @SmallTest
@@ -54,22 +63,27 @@
 class PreviewItemManagerTest {
 
     private lateinit var previewItemManager: PreviewItemManager
-    private lateinit var context: Context
-    private lateinit var folderItems: ArrayList<ItemInfo>
+    private lateinit var context: SandboxModelContext
+    private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
     private lateinit var modelHelper: LauncherModelHelper
     private lateinit var folderIcon: FolderIcon
+    private lateinit var iconCache: IconCache
 
     private var defaultThemedIcons = false
 
     @Before
     fun setup() {
-        getInstrumentation().runOnMainSync {
-            folderIcon =
-                FolderIcon(ActivityContextWrapper(ApplicationProvider.getApplicationContext()))
-        }
-        context = getInstrumentation().targetContext
-        previewItemManager = PreviewItemManager(folderIcon)
         modelHelper = LauncherModelHelper()
+        context = modelHelper.sandboxContext
+        folderIcon = FolderIcon(ActivityContextWrapper(context))
+
+        val app = spy(LauncherAppState.getInstance(context))
+        iconCache = spy(app.iconCache)
+        doReturn(iconCache).whenever(app).iconCache
+        context.putObject(LauncherAppState.INSTANCE, app)
+        doReturn(null).whenever(iconCache).updateIconInBackground(any(), any())
+
+        previewItemManager = PreviewItemManager(folderIcon)
         modelHelper
             .setupDefaultLayoutProvider(
                 LauncherLayoutBuilder()
@@ -82,33 +96,35 @@
                     .build()
             )
             .loadModelSync()
-        folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents()
+
+        // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
+        folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
         folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
         folderIcon.mInfo.getContents().addAll(folderItems)
 
-        // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
-        val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
         // Set first icon to be themed.
-        folderApps[0].bitmap.themedBitmap =
+        folderItems[0].bitmap.themedBitmap =
             MonoThemedBitmap(
-                folderApps[0].bitmap.icon,
+                folderItems[0].bitmap.icon,
                 Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
 
         // Set second icon to be non-themed.
-        folderApps[1].bitmap.themedBitmap = null
+        folderItems[1].bitmap.themedBitmap = null
 
         // Set third icon to be themed with badge.
-        folderApps[2].bitmap.themedBitmap =
+        folderItems[2].bitmap.themedBitmap =
             MonoThemedBitmap(
-                folderApps[2].bitmap.icon,
+                folderItems[2].bitmap.icon,
                 Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
-        folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+        folderItems[2].bitmap =
+            folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
 
         // Set fourth icon to be non-themed with badge.
-        folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
-        folderApps[3].bitmap.themedBitmap = null
+        folderItems[3].bitmap =
+            folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+        folderItems[3].bitmap.themedBitmap = null
 
         defaultThemedIcons = get(context).get(THEMED_ICONS)
     }
@@ -232,6 +248,31 @@
         assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java)
     }
 
+    @Test
+    fun `Preview item loads and apply high res icon`() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+        val originalBitmap = folderItems[3].bitmap
+        folderItems[3].bitmap = BitmapInfo.LOW_RES_INFO
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+        assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        val callbackCaptor = argumentCaptor<ItemInfoUpdateReceiver>()
+        verify(iconCache).updateIconInBackground(callbackCaptor.capture(), eq(folderItems[3]))
+
+        // Restore high-res icon
+        folderItems[3].bitmap = originalBitmap
+
+        // Calling with a different item info will ignore the update
+        callbackCaptor.firstValue.reapplyItemInfo(folderItems[2])
+        assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        // Calling with correct value will update the drawable to high-res
+        callbackCaptor.firstValue.reapplyItemInfo(folderItems[3])
+        assertThat(drawingParams.drawable).isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(drawingParams.drawable).isInstanceOf(FastBitmapDrawable::class.java)
+    }
+
     private fun profileFlagOp(type: Int) =
         UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
 }