Merge "Make work edu close button not say "Close, Image" in a11y." into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 80da467..5a8fba6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -86,7 +87,7 @@
     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
             dp -> {
                 onStashedInAppChanged(dp);
-                adjustHotseatForBubbleBar();
+                postAdjustHotseatForBubbleBar();
                 if (mControllers != null && mControllers.taskbarViewController != null) {
                     mControllers.taskbarViewController.onRotationChanged(dp);
                 }
@@ -275,13 +276,16 @@
         }
     }
 
-    private void adjustHotseatForBubbleBar() {
+    private void postAdjustHotseatForBubbleBar() {
         Hotseat hotseat = mLauncher.getHotseat();
-        if (mControllers.bubbleControllers.isEmpty() || hotseat == null) return;
-        boolean hiddenForBubbles =
-                mControllers.bubbleControllers.get().bubbleBarViewController.isHiddenForNoBubbles();
-        if (hiddenForBubbles) return;
-        hotseat.post(() -> adjustHotseatForBubbleBar(/* isBubbleBarVisible= */ true));
+        if (hotseat == null || !isBubbleBarVisible()) return;
+        hotseat.post(() -> adjustHotseatForBubbleBar(isBubbleBarVisible()));
+    }
+
+    private boolean isBubbleBarVisible() {
+        BubbleControllers bubbleControllers = mControllers.bubbleControllers.orElse(null);
+        return bubbleControllers != null
+                && bubbleControllers.bubbleBarViewController.isBubbleBarVisible();
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index c81edcd..089706f 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -470,7 +470,7 @@
         // Stops requesting focused after first view gets focused.
         recentsView.getTaskViewAt(keyboardTaskFocusIndex).requestFocus() ||
             recentsView.nextTaskView.requestFocus() ||
-            recentsView.getTaskViewAt(0).requestFocus() ||
+            recentsView.getFirstTaskView().requestFocus() ||
             recentsView.requestFocus()
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index dbd7273..4660c51 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3169,7 +3169,7 @@
      * Skips rebalance.
      */
     private void updateGridProperties() {
-        updateGridProperties(Integer.MAX_VALUE);
+        updateGridProperties(null);
     }
 
     /**
@@ -3179,10 +3179,10 @@
      * This method only calculates the potential position and depends on {@link #setGridProgress} to
      * apply the actual scaling and translation.
      *
-     * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE
-     *                            to skip rebalance
+     * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use
+     *                                         `null` to skip rebalance.
      */
-    private void updateGridProperties(int startRebalanceAfter) {
+    private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) {
         if (!hasTaskViews()) {
             return;
         }
@@ -3195,19 +3195,10 @@
         float topAccumulatedTranslationX = 0;
         float bottomAccumulatedTranslationX = 0;
 
-        // Contains whether the child index is in top or bottom of grid (for non-focused task)
-        // Different from mTopRowIdSet, which contains the taskViewId of what task is in top row
-        IntSet topSet = new IntSet();
-        IntSet bottomSet = new IntSet();
-
-        final int taskCount = getTaskViewCount();
-        // Horizontal grid translation for each task
-        float[] gridTranslations = new float[taskCount];
+        // Horizontal grid translation for each task.
+        Map<TaskView, Float> gridTranslations = new HashMap<>();
 
         TaskView lastLargeTaskView = mUtils.getLastLargeTaskView();
-        int lastLargeTaskIndex =
-                (lastLargeTaskView == null) ? Integer.MAX_VALUE : indexOfChild(lastLargeTaskView);
-        Set<Integer> largeTasksIndices = new HashSet<>();
         int focusedTaskShift = 0;
         int largeTaskWidthAndSpacing = 0;
         int snappedTaskRowWidth = 0;
@@ -3220,29 +3211,46 @@
         if (!mAnyTaskHasBeenDismissed) {
             mTopRowIdSet.clear();
         }
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+
+        // Consecutive task views in the top row or bottom row, which means another one set will
+        // be cleared up while starting to add TaskViews to one of them. Also means only one of
+        // them can be non-empty at most.
+        Set<TaskView> lastTopTaskViews = new HashSet<>();
+        Set<TaskView> lastBottomTaskViews = new HashSet<>();
+
+        int largeTasksCount = 0;
+        // True if the last large TaskView has been visited during the TaskViews iteration.
+        boolean encounteredLastLargeTaskView = false;
+        // True if the highest index visible TaskView has been visited during the TaskViews
+        // iteration.
+        boolean encounteredLastVisibleTaskView = false;
+        for (TaskView taskView : getTaskViews()) {
+            if (taskView == lastLargeTaskView) {
+                encounteredLastLargeTaskView = true;
+            }
+            if (taskView == lastVisibleTaskViewDuringDismiss) {
+                encounteredLastVisibleTaskView = true;
+            }
+            float gridTranslation = 0f;
             int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing;
             // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
             // which case keep tasks in their respective rows. For the running task, don't join
             // the grid.
-            boolean isLargeTile = taskView.isLargeTile();
-
-            if (isLargeTile) {
+            if (taskView.isLargeTile()) {
+                largeTasksCount++;
                 // DesktopTaskView`s are hidden during split select state, so we shouldn't count
                 // them when calculating row width.
                 if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) {
                     topRowWidth += taskWidthAndSpacing;
                     bottomRowWidth += taskWidthAndSpacing;
                 }
-                gridTranslations[i] += focusedTaskShift;
-                gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+                gridTranslation += focusedTaskShift;
+                gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
 
                 // Center view vertically in case it's from different orientation.
                 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
                         - taskView.getLayoutParams().height) / 2f);
 
-                largeTasksIndices.add(i);
                 largeTaskWidthAndSpacing = taskWidthAndSpacing;
 
                 if (taskView == snappedTaskView) {
@@ -3250,9 +3258,9 @@
                     snappedTaskRowWidth = taskWidthAndSpacing;
                 }
             } else {
-                if (i > lastLargeTaskIndex) {
+                if (encounteredLastLargeTaskView) {
                     // For tasks after the last large task, shift by large task's width and spacing.
-                    gridTranslations[i] +=
+                    gridTranslation +=
                             mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
                 } else {
                     // For task before the focused task, accumulate the width and spacing to
@@ -3261,10 +3269,10 @@
                 }
                 int taskViewId = taskView.getTaskViewId();
 
-                // Rebalance the grid starting after a certain index
                 boolean isTopRow;
                 if (mAnyTaskHasBeenDismissed) {
-                    if (i > startRebalanceAfter) {
+                    // Rebalance the grid starting after a certain index.
+                    if (encounteredLastVisibleTaskView) {
                         mTopRowIdSet.remove(taskViewId);
                         isTopRow = topRowWidth <= bottomRowWidth;
                     } else {
@@ -3281,47 +3289,43 @@
                     } else {
                         topRowWidth += taskWidthAndSpacing;
                     }
-                    topSet.add(i);
                     mTopRowIdSet.add(taskViewId);
-
                     taskView.setGridTranslationY(mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
-                    for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
-                        if (largeTasksIndices.contains(j)) {
-                            continue;
-                        }
-                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                    for (TaskView bottomTaskView : lastBottomTaskViews) {
+                        widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
-                    gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX;
+                    gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX;
                     topAccumulatedTranslationX += currentTaskTranslationX;
+                    lastTopTaskViews.add(taskView);
+                    lastBottomTaskViews.clear();
                 } else {
                     bottomRowWidth += taskWidthAndSpacing;
-                    bottomSet.add(i);
 
                     // Move into bottom row.
                     taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
-                    for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
-                        if (largeTasksIndices.contains(j)) {
-                            continue;
-                        }
-                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                    for (TaskView topTaskView : lastTopTaskViews) {
+                        widthOffset += topTaskView.getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
-                    gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX;
+                    gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX;
                     bottomAccumulatedTranslationX += currentTaskTranslationX;
+                    lastBottomTaskViews.add(taskView);
+                    lastTopTaskViews.clear();
                 }
                 if (taskView == snappedTaskView) {
                     snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth;
                 }
             }
+            gridTranslations.put(taskView, gridTranslation);
         }
 
         // We need to maintain snapped task's page scroll invariant between quick switch and
@@ -3332,22 +3336,22 @@
         if (snappedTaskView != null) {
             snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
                     /*gridEnabled=*/false);
-            snappedTaskGridTranslationX = gridTranslations[snappedPage];
+            snappedTaskGridTranslationX = gridTranslations.get(snappedTaskView);
         }
 
         // Use the accumulated translation of the row containing the last task.
-        float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1)
+        float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty()
                 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX;
 
         // If the last task is on the shorter row, ClearAllButton will embed into the shorter row
         // which is not what we want. Compensate the width difference of the 2 rows in that case.
         float shorterRowCompensation = 0;
         if (topRowWidth <= bottomRowWidth) {
-            if (topSet.contains(taskCount - 1)) {
+            if (!lastTopTaskViews.isEmpty()) {
                 shorterRowCompensation = bottomRowWidth - topRowWidth;
             }
         } else {
-            if (bottomSet.contains(taskCount - 1)) {
+            if (!lastBottomTaskViews.isEmpty()) {
                 shorterRowCompensation = topRowWidth - bottomRowWidth;
             }
         }
@@ -3363,8 +3367,7 @@
         // for ClearAllButton translation. The space at the left side of the large task will be
         // empty and it should be move ClearAllButton further away as well.
         // TODO(b/359573248): Validate the translation for ClearAllButton for grid only.
-        boolean hasOnlyLargeTasks = taskCount == largeTasksIndices.size();
-        if (enableLargeDesktopWindowingTile() && hasOnlyLargeTasks) {
+        if (enableLargeDesktopWindowingTile() && largeTasksCount == getTaskViewCount()) {
             longRowWidth = largeTaskWidthAndSpacing;
         }
 
@@ -3388,7 +3391,7 @@
         float clearAllTotalTranslationX =
                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
                         + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment;
-        if (!largeTasksIndices.isEmpty()) {
+        if (largeTasksCount > 0) {
             // Shift by focused task's width and spacing if a task is focused.
             clearAllTotalTranslationX +=
                     mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
@@ -3412,10 +3415,10 @@
             }
         }
 
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
-            taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX
-                    + snappedTaskNonGridScrollAdjustment);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setGridTranslationX(
+                    gridTranslations.get(taskView) - snappedTaskGridTranslationX
+                            + snappedTaskNonGridScrollAdjustment);
         }
 
         final TaskView runningTask = getRunningTaskView();
@@ -4154,7 +4157,8 @@
                             // Rebalance tasks in the grid
                             int highestVisibleTaskIndex = getHighestVisibleTaskIndex();
                             if (highestVisibleTaskIndex < Integer.MAX_VALUE) {
-                                TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex);
+                                final TaskView taskView = requireTaskViewAt(
+                                        highestVisibleTaskIndex);
 
                                 boolean shouldRebalance;
                                 int screenStart = getPagedOrientationHandler().getPrimaryScroll(
@@ -4182,7 +4186,7 @@
                                 }
 
                                 if (shouldRebalance) {
-                                    updateGridProperties(highestVisibleTaskIndex);
+                                    updateGridProperties(taskView);
                                     updateScrollSynchronously();
                                 }
                             }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index ba6ed66..81d6631 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -305,7 +305,6 @@
                 info.spanX, info.spanY);
         host.requestLayout();
         mContext.getModelWriter().updateItemInDatabase(info);
-        announceConfirmation(mContext.getString(R.string.widget_resized, info.spanX, info.spanY));
         return true;
     }
 
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 84d6a6f..65b0662 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -114,9 +114,7 @@
         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
 
         View child = mView.getChildAt(x, y);
-        if (child == null || child == dragInfo.item) {
-            return mContext.getString(R.string.item_moved);
-        } else {
+        if (child != null && child != dragInfo.item) {
             ItemInfo info = (ItemInfo) child.getTag();
             if (Folder.willAccept(info)) {
                 return mContext.getString(R.string.folder_created);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 5defef3..e68e3c9 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -680,6 +680,7 @@
         closeOpenFolder(openFolder);
 
         mContent.bindItems(items);
+        mContent.setCanAnnouncePageDescriptionForFolder(true);
         centerAboutIcon();
         mItemsInvalidated = true;
         updateTextViewFocus();
@@ -813,6 +814,7 @@
     @Override
     protected void handleClose(boolean animate) {
         mIsOpen = false;
+        mContent.setCanAnnouncePageDescriptionForFolder(false);
 
         if (!animate && mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.cancel();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index fe26194..8d751e6 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -100,6 +100,8 @@
     // animating or is open.
     private boolean mViewsBound = false;
 
+    private boolean mCanAnnouncePageDescription;
+
     public FolderPagedView(Context context, AttributeSet attrs) {
         this(
                 context,
@@ -170,6 +172,19 @@
         mViewsBound = true;
     }
 
+    void setCanAnnouncePageDescriptionForFolder(boolean canAnnounce) {
+        mCanAnnouncePageDescription = canAnnounce;
+    }
+
+    private boolean canAnnouncePageDescriptionForFolder() {
+        return mCanAnnouncePageDescription;
+    }
+
+    @Override
+    protected boolean canAnnouncePageDescription() {
+        return super.canAnnouncePageDescription() && canAnnouncePageDescriptionForFolder();
+    }
+
     /**
      * Removes all the icons from the folder
      */