Skip to content

Commit

Permalink
[NavigationView][a11y] Announce number of items in drawer
Browse files Browse the repository at this point in the history
Resolves #678

PiperOrigin-RevId: 445165922
  • Loading branch information
paulfthomas authored and dsn5ft committed Apr 28, 2022
1 parent 13e27a8 commit b1f7d5b
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 24 deletions.
Expand Up @@ -48,10 +48,12 @@
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat;
import androidx.core.widget.TextViewCompat;
import java.util.ArrayList;

Expand Down Expand Up @@ -596,6 +598,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
itemView.setMaxLines(itemMaxLines);
itemView.initialize(item.getMenuItem(), 0);
setAccessibilityDelegate(itemView, position, false);
break;
}
case VIEW_TYPE_SUBHEADER:
Expand All @@ -615,6 +618,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (subheaderColor != null) {
subHeader.setTextColor(subheaderColor);
}
setAccessibilityDelegate(subHeader, position, true);
break;
}
case VIEW_TYPE_SEPARATOR:
Expand All @@ -629,11 +633,46 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
}
case VIEW_TYPE_HEADER:
{
setAccessibilityDelegate(holder.itemView, position, true);
break;
}
}
}

private void setAccessibilityDelegate(View view, int position, boolean isHeader) {
ViewCompat.setAccessibilityDelegate(
view,
new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(
@NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setCollectionItemInfo(
CollectionItemInfoCompat.obtain(
/* rowIndex= */ adjustItemPositionForA11yDelegate(position),
/* rowSpan= */ 1,
/* columnIndex =*/ 1,
/* columnSpan= */ 1,
/* heading= */ isHeader,
/* selected= */ host.isSelected()));
}
});
}

/** Adjusts position based on the presence of separators and header. */
private int adjustItemPositionForA11yDelegate(int position) {
int adjustedPosition = position;
for (int i = 0; i < position; i++) {
if (adapter.getItemViewType(i) == VIEW_TYPE_SEPARATOR) {
adjustedPosition--;
}
}
if (headerLayout.getChildCount() == 0) { // no header
adjustedPosition--;
}
return adjustedPosition;
}

@Override
public void onViewRecycled(ViewHolder holder) {
if (holder instanceof NormalViewHolder) {
Expand Down Expand Up @@ -816,7 +855,8 @@ public void setUpdateSuspended(boolean updateSuspended) {
int getRowCount() {
int itemCount = headerLayout.getChildCount() == 0 ? 0 : 1;
for (int i = 0; i < adapter.getItemCount(); i++) {
if (adapter.getItemViewType(i) == VIEW_TYPE_NORMAL) {
int type = adapter.getItemViewType(i);
if (type == VIEW_TYPE_NORMAL || type == VIEW_TYPE_SUBHEADER) {
itemCount++;
}
}
Expand Down Expand Up @@ -880,7 +920,9 @@ private class NavigationMenuViewAccessibilityDelegate extends RecyclerViewAccess
public void onInitializeAccessibilityNodeInfo(
View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setCollectionInfo(CollectionInfoCompat.obtain(adapter.getRowCount(), 0, false));
info.setCollectionInfo(
CollectionInfoCompat.obtain(
adapter.getRowCount(), /* columnCount= */ 1, /* hierarchical= */ false));
}
}
}
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -13,23 +12,39 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:checkableBehavior="single">
<item android:id="@+id/destination_home"
android:title="@string/navigate_home"
android:icon="@drawable/test_drawable_red" />
<item android:id="@+id/destination_profile"
android:title="@string/navigate_profile"
android:icon="@drawable/test_drawable_green" />
<item android:id="@+id/destination_people"
android:title="@string/navigate_people"
android:icon="@drawable/test_drawable_blue"
app:actionLayout="@layout/action_layout" />
<item android:id="@+id/destination_settings"
android:title="@string/navigate_settings" />
<item android:id="@+id/destination_custom"
app:actionLayout="@layout/action_layout_custom" />
</group>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<group android:checkableBehavior="single">
<item
android:id="@+id/destination_home"
android:icon="@drawable/test_drawable_red"
android:title="@string/navigate_home" />
<item
android:id="@+id/destination_profile"
android:icon="@drawable/test_drawable_green"
android:title="@string/navigate_profile" />
<item
android:id="@+id/destination_people"
android:icon="@drawable/test_drawable_blue"
android:title="@string/navigate_people"
app:actionLayout="@layout/action_layout" />
<item
android:id="@+id/destination_settings"
android:title="@string/navigate_settings" />
<item
android:id="@+id/subheader"
android:title="@string/subheader">
<menu>
<item
android:id="@+id/subheader_item"
android:icon="@drawable/test_drawable_red"
android:title="@string/subheader_item" />
</menu>
</item>
<item
android:id="@+id/destination_custom"
app:actionLayout="@layout/action_layout_custom"
tools:ignore="MenuTitle" />
</group>
</menu>
Expand Up @@ -21,6 +21,8 @@
<string name="navigate_people">People</string>
<string name="navigate_custom">Custom</string>
<string name="navigate_settings">Settings</string>
<string name="subheader">Subheader</string>
<string name="subheader_item">Subheader item</string>

<string name="snackbar_text">This is a test message</string>
<string name="snackbar_action">Undo</string>
Expand Down
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.android.material.navigation;

import static android.os.Build.VERSION_CODES.KITKAT;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
Expand Down Expand Up @@ -61,6 +62,7 @@

import android.content.res.Resources;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Parcelable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.SwitchCompat;
Expand All @@ -74,11 +76,16 @@
import androidx.annotation.IdRes;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.test.espresso.matcher.ViewMatchers.Visibility;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.google.android.material.internal.NavigationMenuView;
import com.google.android.material.testapp.NavigationViewActivity;
import com.google.android.material.testapp.R;
import com.google.android.material.testapp.custom.NavigationTestView;
Expand Down Expand Up @@ -135,7 +142,7 @@ public void testBasics() {
final Menu menu = navigationView.getMenu();
assertNotNull("Menu should not be null", menu);
assertEquals(
"Should have matching number of items", MENU_CONTENT_ITEM_IDS.length + 1, menu.size());
"Should have matching number of items", MENU_CONTENT_ITEM_IDS.length + 2, menu.size());
for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
final MenuItem currItem = menu.getItem(i);
assertEquals("ID for Item #" + i, MENU_CONTENT_ITEM_IDS[i], currItem.getItemId());
Expand Down Expand Up @@ -707,4 +714,51 @@ public void testActionLayout() {
allOf(isAssignableFrom(TextView.class), withEffectiveVisibility(Visibility.GONE))));
onView(customItemMatcher).perform(click());
}

@Test
public void testAccessibility() throws Throwable {
if (VERSION.SDK_INT < KITKAT) {
// CollectionInfo and CollectionItemInfo only available on API 19+.
return;
}

// Open our drawer
onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));

// Add header
onView(withId(R.id.start_drawer))
.perform(inflateHeaderView(R.layout.design_navigation_view_header1));

final NavigationViewActivity activity = activityTestRule.getActivity();
NavigationMenuView navigationMenuView = activity.findViewById(R.id.design_navigation_view);
AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain();
ViewCompat.onInitializeAccessibilityNodeInfo(navigationMenuView, nodeInfo);

CollectionInfoCompat collectionInfo = nodeInfo.getCollectionInfo();
assertEquals(8, collectionInfo.getRowCount());
assertEquals(1, collectionInfo.getColumnCount());

activityTestRule.runOnUiThread(
() -> {
verifyItemInfo(navigationMenuView.getChildAt(0), 0, true);
verifyItemInfo(navigationMenuView.getChildAt(1), 1, false);
verifyItemInfo(navigationMenuView.getChildAt(2), 2, false);
verifyItemInfo(navigationMenuView.getChildAt(3), 3, false);
verifyItemInfo(navigationMenuView.getChildAt(4), 4, false);
// index 5 = separator
verifyItemInfo(navigationMenuView.getChildAt(6), 5, true);
verifyItemInfo(navigationMenuView.getChildAt(7), 6, false);
verifyItemInfo(navigationMenuView.getChildAt(8), 7, false);
});
}

private void verifyItemInfo(View view, int rowIndex, boolean isHeader) {
AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain();
ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo);

CollectionItemInfoCompat itemInfo = nodeInfo.getCollectionItemInfo();
assertEquals(rowIndex, itemInfo.getRowIndex());
assertEquals(1, itemInfo.getColumnIndex());
assertEquals(isHeader, nodeInfo.isHeading());
}
}

0 comments on commit b1f7d5b

Please sign in to comment.