Hybrid Hotseat a11y

- speak meaningful accessibility label for predicted items
- disable accessibility focus for on-boarding preview items
- add PIN as an accessibility action
- remove move and remove actions for prediction icons

Bug:152376193
Bug:152359303
Bug:152374583
Bug:152357657
Bug:152268303
Bug:152379490

Change-Id: I40fe0ef6329cd5b1d9215ac5fa1716f15db89ac8
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 8944088..b8c4a21 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -210,6 +210,7 @@
             WorkspaceItemInfo info = predictions.get(i);
             PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
             icon.setEnabled(false);
+            icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             icon.verifyHighRes();
             CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
             mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index d3bb4f9..9bc0975 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -349,7 +349,10 @@
                 mHotSeatItemsCount);
     }
 
-    private void pinPrediction(ItemInfo info) {
+    /**
+     * Pins a predicted app icon into place.
+     */
+    public void pinPrediction(ItemInfo info) {
         PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
                 mHotseat.getCellXFromOrder(info.rank),
                 mHotseat.getCellYFromOrder(info.rank));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 4bbb48c..304c77f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides;
 
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.PIN_PREDICTION;
 import static com.android.launcher3.graphics.IconShape.getShape;
 
 import android.content.Context;
@@ -26,15 +27,19 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
@@ -43,7 +48,8 @@
 /**
  * A BubbleTextView with a ring around it's drawable
  */
-public class PredictedAppIcon extends DoubleShadowBubbleTextView {
+public class PredictedAppIcon extends DoubleShadowBubbleTextView implements
+        LauncherAccessibilityDelegate.AccessibilityActionHandler {
 
     private static final float RING_EFFECT_RATIO = 0.11f;
 
@@ -97,6 +103,13 @@
         super.applyFromWorkspaceItem(info);
         int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
         mIconRingPaint.setColor(ColorUtils.setAlphaComponent(color, 200));
+        if (mIsPinned) {
+            setContentDescription(info.contentDescription);
+        } else {
+            setContentDescription(
+                    getContext().getString(R.string.hotseat_prediction_content_description,
+                            info.contentDescription));
+        }
     }
 
     /**
@@ -104,9 +117,9 @@
      */
     public void pin(WorkspaceItemInfo info) {
         if (mIsPinned) return;
+        mIsPinned = true;
         applyFromWorkspaceItem(info);
         setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-        mIsPinned = true;
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
         invalidate();
     }
@@ -122,6 +135,27 @@
     }
 
     @Override
+    public void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo) {
+        accessibilityNodeInfo.addAction(
+                new AccessibilityNodeInfo.AccessibilityAction(PIN_PREDICTION,
+                        getContext().getText(R.string.pin_prediction)));
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, ItemInfo info) {
+        QuickstepLauncher launcher = Launcher.cast(Launcher.getLauncher(getContext()));
+        if (action == PIN_PREDICTION) {
+            if (launcher == null || launcher.getHotseatPredictionController() == null) {
+                return false;
+            }
+            HotseatPredictionController controller = launcher.getHotseatPredictionController();
+            controller.pinPrediction(info);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
     public void getIconBounds(Rect outBounds) {
         super.getIconBounds(outBounds);
         if (!mIsPinned && !mIsDrawingDot) {
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 40d7c7a..37516c6 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -93,6 +93,9 @@
     <!-- tip shown if user declines migration and has some open spots for prediction -->
     <string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
 
+    <!-- content description for hotseat items -->
+    <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
+
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
     <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 1675a98..ef67613 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -109,6 +109,7 @@
     <item type="id" name="action_dismiss_notification" />
     <item type="id" name="action_remote_action_shortcut" />
     <item type="id" name="action_dismiss_prediction" />
+    <item type="id" name="action_pin_prediction"/>
 
     <!-- QSB IDs. DO not change -->
     <item type="id" name="search_container_workspace" />
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 423f2bb..6f0ebd2 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -67,7 +67,7 @@
     public boolean supportsAccessibilityDrop(ItemInfo info, View view) {
         if (info instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
-            return info.screenId >= 0;
+            return canRemove(info);
         }
 
         return (info instanceof LauncherAppWidgetInfo)
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6f7f8e6..0337fd6 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -56,6 +56,7 @@
     public static final int REMOVE = R.id.action_remove;
     public static final int UNINSTALL = R.id.action_uninstall;
     public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
+    public static final int PIN_PREDICTION = R.id.action_pin_prediction;
     public static final int RECONFIGURE = R.id.action_reconfigure;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
@@ -120,6 +121,10 @@
         if (!(host.getTag() instanceof ItemInfo)) return;
         ItemInfo item = (ItemInfo) host.getTag();
 
+        if (host instanceof AccessibilityActionHandler) {
+            ((AccessibilityActionHandler) host).addSupportedAccessibilityActions(info);
+        }
+
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
         if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) {
@@ -154,7 +159,7 @@
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
         if (item instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
-            return item.screenId >= 0;
+            return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
         }
         return (item instanceof LauncherAppWidgetInfo)
                 || (item instanceof FolderInfo);
@@ -185,7 +190,10 @@
                 return true;
             }
         }
-
+        if (host instanceof AccessibilityActionHandler
+                && ((AccessibilityActionHandler) host).performAccessibilityAction(action, item)) {
+            return true;
+        }
         if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
@@ -456,4 +464,20 @@
         }
         return screenId;
     }
+
+    /**
+     * An interface allowing views to handle their own action.
+     */
+    public interface AccessibilityActionHandler {
+
+        /**
+         * performs accessibility action and returns true on success
+         */
+        boolean performAccessibilityAction(int action, ItemInfo itemInfo);
+
+        /**
+         * adds all the accessibility actions that can be handled.
+         */
+        void addSupportedAccessibilityActions(AccessibilityNodeInfo accessibilityNodeInfo);
+    }
 }