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);
+ }
}