Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 952a2df6 authored by Andrey Yepin's avatar Andrey Yepin
Browse files

Present target grid as hierarchical for a11y purposes.

A retrofit of the ag/31256785 into the legacy codebase (which is still
used by MediaProjection, at least).

Present target grid as hierarchical for a11y purposes. This makes
TalkBack to announce sub-groups of targets.

Fix: 380211084
Test: Manual testing for the both cases when the suggested apps are
present and missing.
Flag: android.service.chooser.announce_shortcuts_and_suggested_apps_legacy

Change-Id: Ifc13bcfacf791cea8ca4d661a847dfa9dafc950f
parent 8b9ebf71
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
package: "android.service.chooser"
container: "system"

flag {
  name: "announce_shortcuts_and_suggested_apps_legacy"
  namespace: "intentresolver"
  description: "Enable talkback announcement for the app shortcuts and the suggested apps target groups in the legacy sharesheet codebase."
  bug: "380211084"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
  name: "chooser_album_text"
  is_exported: true
+128 −0
Original line number Diff line number Diff line
@@ -16,9 +16,20 @@

package com.android.internal.app;

import static android.service.chooser.Flags.announceShortcutsAndSuggestedAppsLegacy;

import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.widget.GridView;
import android.widget.TextView;

import com.android.internal.R;
import com.android.internal.app.ChooserActivity.ChooserGridAdapter;
import com.android.internal.widget.GridLayoutManager;
import com.android.internal.widget.RecyclerView;

@@ -28,6 +39,11 @@ import com.android.internal.widget.RecyclerView;
 */
public class ChooserGridLayoutManager extends GridLayoutManager {

    private CharSequence mShortcutGroupTitle = "";
    private CharSequence mSuggestedAppsGroupTitle = "";
    private CharSequence mAllAppListGroupTitle = "";
    @Nullable
    private RecyclerView mRecyclerView;
    private boolean mVerticalScrollEnabled = true;

    /**
@@ -39,6 +55,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
    public ChooserGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        if (announceShortcutsAndSuggestedAppsLegacy()) {
            readGroupTitles(context);
        }
    }

    /**
@@ -49,6 +68,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
     */
    public ChooserGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
        if (announceShortcutsAndSuggestedAppsLegacy()) {
            readGroupTitles(context);
        }
    }

    /**
@@ -61,6 +83,27 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
    public ChooserGridLayoutManager(Context context, int spanCount, int orientation,
            boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
        if (announceShortcutsAndSuggestedAppsLegacy()) {
            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 +121,89 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
    public boolean canScrollVertically() {
        return mVerticalScrollEnabled && super.canScrollVertically();
    }

    @Override
    public void onInitializeAccessibilityNodeInfoForItem(
            RecyclerView.Recycler recycler,
            RecyclerView.State state,
            View host,
            AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
        if (announceShortcutsAndSuggestedAppsLegacy() && 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.chooser_row) {
                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(RecyclerView.Recycler recycler,
            RecyclerView.State state, AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(recycler, state, info);
        if (announceShortcutsAndSuggestedAppsLegacy()) {
            info.setContainerTitle(mAllAppListGroupTitle);
        }
    }

    @Override
    public boolean isLayoutHierarchical(RecyclerView.Recycler recycler, RecyclerView.State state) {
        return announceShortcutsAndSuggestedAppsLegacy()
                || super.isLayoutHierarchical(recycler, state);
    }

    private CollectionInfo createShortcutsA11yCollectionInfo(ViewGroup container) {
        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 CollectionInfo.obtain(rowCount, columnCount, false);
    }

    private CollectionInfo createSuggestedAppsA11yCollectionInfo(ViewGroup container) {
        int columnCount = 0;
        for (int i = 0; i < container.getChildCount(); i++) {
            View v = container.getChildAt(i);
            if (v.getVisibility() == View.VISIBLE) {
                columnCount++;
            }
        }
        return CollectionInfo.obtain(1, columnCount, false);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/chooser_row"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="100dp"
+1 −0
Original line number Diff line number Diff line
@@ -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">
+10 −0
Original line number Diff line number Diff line
@@ -5744,6 +5744,16 @@
    <string name="unpin_specific_target">Unpin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
    <!-- View application info for a target. -->
    <string name="app_info">App info</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>
    <!-- The representation of a time duration when negative. An example is -1:14. This can be used with a countdown timer for example.-->
    <string name="negative_duration">\u2212<xliff:g id="time" example="1:14">%1$s</xliff:g></string>
Loading