summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Andrey Yepin <ayepin@google.com> 2025-01-10 11:28:37 -0800
committer Andrey Yepin <ayepin@google.com> 2025-01-14 14:38:52 -0800
commitb4558f65a5cbd709678d1368b94c4e66167bca87 (patch)
tree466efea44d064a92b312561a398489453286cbde /java
parenteffd3e6c1bba8d5d5ce8047b9a1e455816fcd4b3 (diff)
Present target grid as hierarchical for a11y purposes.
Present target grid as hierarchical for a11y purposes. This makes TalkBack to announce sub-groups of targets. Fix: 379208685 Test: Manual test for the following cases: (i) all group present (shortcuts, suggested, all-apps), (ii) shortcuts are missing -- the no-target message is shown, (iii) no suggested targets. Flag: com.android.intentresolver.announce_shortcuts_and_suggested_apps Change-Id: If08e9bcccab9db423c5b67c759bc0854d290e880
Diffstat (limited to 'java')
-rw-r--r--java/res/layout/chooser_row.xml1
-rw-r--r--java/res/layout/chooser_row_direct_share.xml1
-rw-r--r--java/res/values/strings.xml10
-rw-r--r--java/src/com/android/intentresolver/ChooserGridLayoutManager.java131
4 files changed, 143 insertions, 0 deletions
diff --git a/java/res/layout/chooser_row.xml b/java/res/layout/chooser_row.xml
index bbe65a85..3fe1ee7d 100644
--- a/java/res/layout/chooser_row.xml
+++ b/java/res/layout/chooser_row.xml
@@ -18,6 +18,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/suggested_apps_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="100dp"
diff --git a/java/res/layout/chooser_row_direct_share.xml b/java/res/layout/chooser_row_direct_share.xml
index d7e36eed..53e666a6 100644
--- a/java/res/layout/chooser_row_direct_share.xml
+++ b/java/res/layout/chooser_row_direct_share.xml
@@ -17,6 +17,7 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/shortcuts_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="200dp">
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 2261a4a8..6504462f 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -340,4 +340,14 @@
<string name="selectable_item">Selectable item</string>
<!-- Accessibility role description for a11y on button. [CHAR LIMIT=NONE] -->
<string name="role_description_button">Button</string>
+
+ <!-- Accessibility announcement for the shortcut group (https://developer.android.com/training/sharing/direct-share-targets)
+ in the list of targets. [CHAR LIMIT=NONE]-->
+ <string name="shortcut_group_a11y_title">Direct share targets</string>
+ <!-- Accessibility announcement for the suggested application group in the list of targets.
+ [CHAR LIMIT=NONE] -->
+ <string name="suggested_apps_group_a11y_title">App suggestions</string>
+ <!-- Accessibility announcement for the all-applications group in the list of targets.
+ [CHAR LIMIT=NONE] -->
+ <string name="all_apps_group_a11y_title">App list</string>
</resources>
diff --git a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
index aaa7554c..5bbb6c24 100644
--- a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
+++ b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
@@ -16,18 +16,35 @@
package com.android.intentresolver;
+import static com.android.intentresolver.Flags.announceShortcutsAndSuggestedApps;
+
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+
/**
* For a11y and per {@link RecyclerView#onInitializeAccessibilityNodeInfo}, override
* methods to ensure proper row counts.
*/
public class ChooserGridLayoutManager extends GridLayoutManager {
+ private CharSequence mShortcutGroupTitle = "";
+ private CharSequence mSuggestedAppsGroupTitle = "";
+ private CharSequence mAllAppListGroupTitle = "";
+ @Nullable
+ private RecyclerView mRecyclerView;
private boolean mVerticalScrollEnabled = true;
/**
@@ -39,6 +56,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public ChooserGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
}
/**
@@ -49,6 +69,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
*/
public ChooserGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
}
/**
@@ -61,6 +84,27 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public ChooserGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
+ }
+
+ private void readGroupTitles(Context context) {
+ mShortcutGroupTitle = context.getString(R.string.shortcut_group_a11y_title);
+ mSuggestedAppsGroupTitle = context.getString(R.string.suggested_apps_group_a11y_title);
+ mAllAppListGroupTitle = context.getString(R.string.all_apps_group_a11y_title);
+ }
+
+ @Override
+ public void onAttachedToWindow(RecyclerView view) {
+ super.onAttachedToWindow(view);
+ mRecyclerView = view;
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ mRecyclerView = null;
}
@Override
@@ -78,4 +122,91 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public boolean canScrollVertically() {
return mVerticalScrollEnabled && super.canScrollVertically();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(
+ RecyclerView.Recycler recycler,
+ RecyclerView.State state,
+ View host,
+ AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+ if (announceShortcutsAndSuggestedApps() && host instanceof ViewGroup) {
+ if (host.getId() == R.id.shortcuts_container) {
+ info.setClassName(GridView.class.getName());
+ info.setContainerTitle(mShortcutGroupTitle);
+ info.setCollectionInfo(createShortcutsA11yCollectionInfo((ViewGroup) host));
+ } else if (host.getId() == R.id.suggested_apps_container) {
+ RecyclerView.Adapter adapter =
+ mRecyclerView == null ? null : mRecyclerView.getAdapter();
+ ChooserListAdapter gridAdapter = adapter instanceof ChooserGridAdapter
+ ? ((ChooserGridAdapter) adapter).getListAdapter()
+ : null;
+ info.setClassName(GridView.class.getName());
+ info.setCollectionInfo(createSuggestedAppsA11yCollectionInfo((ViewGroup) host));
+ if (gridAdapter == null || gridAdapter.getAlphaTargetCount() > 0) {
+ info.setContainerTitle(mSuggestedAppsGroupTitle);
+ } else {
+ // if all applications fit into one row, they will be put into the suggested
+ // applications group.
+ info.setContainerTitle(mAllAppListGroupTitle);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler,
+ @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(recycler, state, info);
+ if (announceShortcutsAndSuggestedApps()) {
+ info.setContainerTitle(mAllAppListGroupTitle);
+ }
+ }
+
+ @Override
+ public boolean isLayoutHierarchical(
+ @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+ return announceShortcutsAndSuggestedApps() || super.isLayoutHierarchical(recycler, state);
+ }
+
+ private CollectionInfoCompat createShortcutsA11yCollectionInfo(ViewGroup container) {
+ // TODO: create a custom view for the shortcuts row and move this logic there.
+ int rowCount = 0;
+ int columnCount = 0;
+ for (int i = 0; i < container.getChildCount(); i++) {
+ View row = container.getChildAt(i);
+ int rowColumnCount = 0;
+ if (row instanceof ViewGroup rowGroup && row.getVisibility() == View.VISIBLE) {
+ for (int j = 0; j < rowGroup.getChildCount(); j++) {
+ View v = rowGroup.getChildAt(j);
+ if (v != null && v.getVisibility() == View.VISIBLE) {
+ rowColumnCount++;
+ if (v instanceof TextView) {
+ // A special case of the no-targets message that also contains an
+ // off-screen item (which looks like a bug).
+ rowColumnCount = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (rowColumnCount > 0) {
+ rowCount++;
+ columnCount = Math.max(columnCount, rowColumnCount);
+ }
+ }
+ return CollectionInfoCompat.obtain(rowCount, columnCount, false);
+ }
+
+ private CollectionInfoCompat createSuggestedAppsA11yCollectionInfo(ViewGroup container) {
+ // TODO: create a custom view for the suggested apps row and move this logic there.
+ int columnCount = 0;
+ for (int i = 0; i < container.getChildCount(); i++) {
+ View v = container.getChildAt(i);
+ if (v.getVisibility() == View.VISIBLE) {
+ columnCount++;
+ }
+ }
+ return CollectionInfoCompat.obtain(1, columnCount, false);
+ }
}