Commit 56942ef2 authored by Yue Zhang's avatar Yue Zhang Committed by Commit Bot

Update a11y string for tab group opening

This CL updates the content description of cards in grid tab switcher
so that when focus on a group card, it should announce
"expand [name] tab group with X tabs". Note that if there is no
customized title, the [name] field should be empty.

Bug: 1111942
Change-Id: I4630deeb5a0f0fe60707722af2b34e6dd4eff002
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2367028
Commit-Queue: Yue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800374}
parent 2856ce3d
...@@ -145,6 +145,8 @@ class TabGridViewBinder { ...@@ -145,6 +145,8 @@ class TabGridViewBinder {
faviconView.setPadding(padding, padding, padding, padding); faviconView.setPadding(padding, padding, padding, padding);
} else if (TabProperties.THUMBNAIL_FETCHER == propertyKey) { } else if (TabProperties.THUMBNAIL_FETCHER == propertyKey) {
updateThumbnail(view, model); updateThumbnail(view, model);
} else if (TabProperties.CONTENT_DESCRIPTION_STRING == propertyKey) {
view.setContentDescription(model.get(TabProperties.CONTENT_DESCRIPTION_STRING));
} }
} }
......
...@@ -780,6 +780,7 @@ class TabListMediator { ...@@ -780,6 +780,7 @@ class TabListMediator {
int index = mModel.indexFromId(currentGroupSelectedTab.getId()); int index = mModel.indexFromId(currentGroupSelectedTab.getId());
if (index == TabModel.INVALID_TAB_INDEX) return; if (index == TabModel.INVALID_TAB_INDEX) return;
mModel.get(index).model.set(TabProperties.TITLE, title); mModel.get(index).model.set(TabProperties.TITLE, title);
updateDescriptionString(PseudoTab.fromTab(tab), mModel.get(index).model);
} }
@Override @Override
...@@ -1018,6 +1019,7 @@ class TabListMediator { ...@@ -1018,6 +1019,7 @@ class TabListMediator {
mModel.get(index).model.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener); mModel.get(index).model.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener);
mModel.get(index).model.set(TabProperties.IS_SELECTED, isSelected); mModel.get(index).model.set(TabProperties.IS_SELECTED, isSelected);
mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(pseudoTab)); mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(pseudoTab));
updateDescriptionString(pseudoTab, mModel.get(index).model);
if (isRealTab) { if (isRealTab) {
mModel.get(index).model.set( mModel.get(index).model.set(
TabProperties.URL_DOMAIN, getDomainForTab(pseudoTab.getTab())); TabProperties.URL_DOMAIN, getDomainForTab(pseudoTab.getTab()));
...@@ -1275,6 +1277,7 @@ class TabListMediator { ...@@ -1275,6 +1277,7 @@ class TabListMediator {
} else { } else {
tabInfo.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener); tabInfo.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener);
tabInfo.set(TabProperties.TAB_CLOSED_LISTENER, isRealTab ? mTabClosedListener : null); tabInfo.set(TabProperties.TAB_CLOSED_LISTENER, isRealTab ? mTabClosedListener : null);
updateDescriptionString(pseudoTab, tabInfo);
} }
if (index >= mModel.size()) { if (index >= mModel.size()) {
...@@ -1332,6 +1335,20 @@ class TabListMediator { ...@@ -1332,6 +1335,20 @@ class TabListMediator {
return TextUtils.join(", ", domainNames); return TextUtils.join(", ", domainNames);
} }
private void updateDescriptionString(PseudoTab pseudoTab, PropertyModel model) {
if (!mActionsOnAllRelatedTabs) return;
int numOfRelatedTabs = getRelatedTabsForId(pseudoTab.getId()).size();
if (numOfRelatedTabs > 1) {
String title = getLatestTitleForTab(pseudoTab);
title = title.equals(pseudoTab.getTitle(mTitleProvider)) ? "" : title;
model.set(TabProperties.CONTENT_DESCRIPTION_STRING,
mContext.getString(R.string.accessibility_expand_tab_group, title,
String.valueOf(numOfRelatedTabs)));
} else {
model.set(TabProperties.CONTENT_DESCRIPTION_STRING, null);
}
}
@VisibleForTesting @VisibleForTesting
protected static String getDomain(Tab tab) { protected static String getDomain(Tab tab) {
// TODO(crbug.com/1116613) Investigate how uninitialized Tabs are appearing // TODO(crbug.com/1116613) Investigate how uninitialized Tabs are appearing
......
...@@ -109,6 +109,9 @@ public class TabProperties { ...@@ -109,6 +109,9 @@ public class TabProperties {
public static final PropertyModel.WritableIntPropertyKey SEARCH_CHIP_ICON_DRAWABLE_ID = public static final PropertyModel.WritableIntPropertyKey SEARCH_CHIP_ICON_DRAWABLE_ID =
new PropertyModel.WritableIntPropertyKey(); new PropertyModel.WritableIntPropertyKey();
public static final WritableObjectPropertyKey<String> CONTENT_DESCRIPTION_STRING =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS_TAB_GRID = new PropertyKey[] {TAB_ID, public static final PropertyKey[] ALL_KEYS_TAB_GRID = new PropertyKey[] {TAB_ID,
TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, THUMBNAIL_FETCHER, IPH_PROVIDER, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, THUMBNAIL_FETCHER, IPH_PROVIDER,
TITLE, IS_SELECTED, CHECKED_DRAWABLE_STATE_LIST, CREATE_GROUP_LISTENER, CARD_ALPHA, TITLE, IS_SELECTED, CHECKED_DRAWABLE_STATE_LIST, CREATE_GROUP_LISTENER, CARD_ALPHA,
...@@ -116,7 +119,8 @@ public class TabProperties { ...@@ -116,7 +119,8 @@ public class TabProperties {
IS_INCOGNITO, SELECTED_TAB_BACKGROUND_DRAWABLE_ID, TABSTRIP_FAVICON_BACKGROUND_COLOR_ID, IS_INCOGNITO, SELECTED_TAB_BACKGROUND_DRAWABLE_ID, TABSTRIP_FAVICON_BACKGROUND_COLOR_ID,
SELECTABLE_TAB_ACTION_BUTTON_BACKGROUND, SELECTABLE_TAB_ACTION_BUTTON_BACKGROUND,
SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL_DOMAIN, ACCESSIBILITY_DELEGATE, SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND, URL_DOMAIN, ACCESSIBILITY_DELEGATE,
SEARCH_QUERY, SEARCH_LISTENER, SEARCH_CHIP_ICON_DRAWABLE_ID, CARD_TYPE}; SEARCH_QUERY, SEARCH_LISTENER, SEARCH_CHIP_ICON_DRAWABLE_ID, CARD_TYPE,
CONTENT_DESCRIPTION_STRING};
public static final PropertyKey[] ALL_KEYS_TAB_STRIP = public static final PropertyKey[] ALL_KEYS_TAB_STRIP =
new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON, new PropertyKey[] {TAB_ID, TAB_SELECTED_LISTENER, TAB_CLOSED_LISTENER, FAVICON,
......
...@@ -243,6 +243,11 @@ ...@@ -243,6 +243,11 @@
Share group Share group
</message> </message>
<!-- Tab Grid Dialog accessibility strings -->
<message name="IDS_ACCESSIBILITY_EXPAND_TAB_GROUP" desc="The accessibility text to read when a card representing a tab group is clicked in grid tab switcher. TITLE_OF_GROUP is the title of the group. Note that TITLE_OF_GROUP can be an empty string. NUMBER_OF_TABS is the number of tabs within this group. Note that there are always at least two tabs in a group so plural form should always be used.">
Expand <ph name="TITLE_OF_GROUP">%1$s<ex>Shopping cart</ex></ph> tab group with <ph name="NUMBER_OF_TABS">%2$s<ex>2</ex></ph> tabs.
</message>
<!-- Tab Grid Drag-and-drop IPH strings --> <!-- Tab Grid Drag-and-drop IPH strings -->
<message name="IDS_IPH_DRAG_AND_DROP_INTRODUCTION" desc="This text shows on the entry point for the in-product help for drag-and-drop. It introduces that this IPH is about how to use drag-and-drop."> <message name="IDS_IPH_DRAG_AND_DROP_INTRODUCTION" desc="This text shows on the entry point for the in-product help for drag-and-drop. It introduces that this IPH is about how to use drag-and-drop.">
Drag tabs to group them Drag tabs to group them
......
...@@ -777,6 +777,50 @@ public class TabGridDialogTest { ...@@ -777,6 +777,50 @@ public class TabGridDialogTest {
verifyBackgroundViewAccessibilityImportance(cta, false); verifyBackgroundViewAccessibilityImportance(cta, false);
} }
@Test
@MediumTest
@Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID})
public void testAccessibilityString() throws ExecutionException {
final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
createTabs(cta, false, 3);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Verify the initial content description.
RecyclerView recyclerView = cta.findViewById(R.id.tab_list_view);
View firstItem = recyclerView.findViewHolderForAdapterPosition(0).itemView;
String targetString = "Expand tab group with 3 tabs.";
assertEquals(targetString, firstItem.getContentDescription());
// Content description should update with group title.
openDialogFromTabSwitcherAndVerify(cta, 3, null);
editDialogTitle(cta, CUSTOMIZED_TITLE1);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
targetString = String.format("Expand %s tab group with 3 tabs.", CUSTOMIZED_TITLE1);
assertEquals(targetString, firstItem.getContentDescription());
// Content description should update with group count change.
openDialogFromTabSwitcherAndVerify(cta, 3, CUSTOMIZED_TITLE1);
closeFirstTabInDialog();
verifyShowingDialog(cta, 2, CUSTOMIZED_TITLE1);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
targetString = String.format("Expand %s tab group with 2 tabs.", CUSTOMIZED_TITLE1);
assertEquals(targetString, firstItem.getContentDescription());
// Content description should restore when the group becomes a single tab.
openDialogFromTabSwitcherAndVerify(cta, 2, CUSTOMIZED_TITLE1);
closeFirstTabInDialog();
verifyShowingDialog(cta, 1, "1 tab");
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
assertEquals(null, firstItem.getContentDescription());
}
private void openDialogFromTabSwitcherAndVerify( private void openDialogFromTabSwitcherAndVerify(
ChromeTabbedActivity cta, int tabCount, String customizedTitle) { ChromeTabbedActivity cta, int tabCount, String customizedTitle) {
clickFirstCardFromTabSwitcher(cta); clickFirstCardFromTabSwitcher(cta);
......
...@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt; ...@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.ArgumentMatchers.refEq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -1396,6 +1397,14 @@ public class TabListMediatorUnitTest { ...@@ -1396,6 +1397,14 @@ public class TabListMediatorUnitTest {
@Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID}) @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
public void updateTabGroupTitle_GTS() { public void updateTabGroupTitle_GTS() {
setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER); setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
doAnswer(invocation -> {
String title = invocation.getArgument(1);
String num = invocation.getArgument(2);
return String.format("Expand %s tab group with %s tabs.", title, num);
})
.when(mContext)
.getString(anyInt(), anyString(), anyString());
String targetString = "Expand tab group with 2 tabs.";
assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE)); assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
// Mock that tab1 and newTab are in the same group and group root id is TAB1_ID. // Mock that tab1 and newTab are in the same group and group root id is TAB1_ID.
...@@ -1409,6 +1418,8 @@ public class TabListMediatorUnitTest { ...@@ -1409,6 +1418,8 @@ public class TabListMediatorUnitTest {
assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE),
equalTo(CUSTOMIZED_DIALOG_TITLE1)); equalTo(CUSTOMIZED_DIALOG_TITLE1));
assertThat(mModel.get(POSITION1).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
} }
@Test @Test
...@@ -2197,6 +2208,42 @@ public class TabListMediatorUnitTest { ...@@ -2197,6 +2208,42 @@ public class TabListMediatorUnitTest {
TabListMediator.getDomain(tab); TabListMediator.getDomain(tab);
} }
@Test
public void testTabDescriptionStringSetup() {
setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
// Setup the string template.
doAnswer(invocation -> {
String title = invocation.getArgument(1);
String num = invocation.getArgument(2);
return String.format("Expand %s tab group with %s tabs.", title, num);
})
.when(mContext)
.getString(anyInt(), anyString(), anyString());
String targetString = "Expand tab group with 2 tabs.";
// Setup a tab group with {tab2, tab3}.
List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) {
tabs.add(mTabModel.getTabAt(i));
}
TabImpl tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab2, tab3));
createTabGroup(group1, TAB2_ID);
// Reset with show quickly.
assertThat(mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false, false),
equalTo(true));
assertThat(mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
// Reset without show quickly.
mModel.clear();
assertThat(mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false, false),
equalTo(false));
assertThat(mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
equalTo(targetString));
}
private void initAndAssertAllProperties() { private void initAndAssertAllProperties() {
List<Tab> tabs = new ArrayList<>(); List<Tab> tabs = new ArrayList<>();
for (int i = 0; i < mTabModel.getCount(); i++) { for (int i = 0; i < mTabModel.getCount(); i++) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment