Bug 1390356: Override AS context menu a11y title. r=sebastian
In theory, we could block the UI thread for the page domain and with the
current implementation, it'd be fine:
- It's async only because the first time this method is called, it reads a file
from disk. Otherwise, it's unnecessary.
- This method is guaranteed to have already been called because about:home
shows before this context menu can show
But the implementation could change and it seemed incorrect to block showing
this context menu for *all* solely for a11y text that can be estimated.
MozReview-Commit-ID: HeX7hTpVtEP
--- a/mobile/android/app/src/main/res/layout/activity_stream_contextmenu_bottomsheet.xml
+++ b/mobile/android/app/src/main/res/layout/activity_stream_contextmenu_bottomsheet.xml
@@ -6,17 +6,20 @@
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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- a11y: When the dialog first appears, the title is announced so there is no
- need to allow refocusing the title. -->
+ need to allow refocusing the title.
+
+ Also, we override the a11y title of the dialog in code. If you change the text in id/url or id/title,
+ update the code! -->
<RelativeLayout
android:id="@+id/info_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:importantForAccessibility="noHideDescendants">
<org.mozilla.gecko.activitystream.homepanel.stream.StreamOverridablePageIconLayout
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/BottomSheetContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/menu/BottomSheetContextMenu.java
@@ -1,26 +1,28 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.activitystream.homepanel.menu;
import android.app.Activity;
import android.content.Context;
+import android.net.Uri;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.NavigationView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import org.mozilla.gecko.R;
import org.mozilla.gecko.activitystream.ActivityStreamTelemetry;
import org.mozilla.gecko.activitystream.homepanel.model.Item;
import org.mozilla.gecko.activitystream.homepanel.stream.StreamOverridablePageIconLayout;
import org.mozilla.gecko.home.HomePager;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.URIUtils;
@@ -36,16 +38,19 @@ import java.net.URISyntaxException;
private final BottomSheetDialog bottomSheetDialog;
private final NavigationView navigationView;
private final View content;
private final View activityView;
+ /** A reference, that represents the page domain, that allows a return value from an async task. */
+ private String[] pageDomainTextReference = new String[] { "" };
+
public BottomSheetContextMenu(final Context context,
final ActivityStreamTelemetry.Extras.Builder telemetryExtraBuilder,
final MenuMode mode,
final Item item,
final boolean shouldOverrideIconWithImageProvider,
HomePager.OnUrlOpenListener onUrlOpenListener,
HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
final int tilesWidth, final int tilesHeight) {
@@ -63,31 +68,34 @@ import java.net.URISyntaxException;
bottomSheetDialog = new BottomSheetDialog(context);
final LayoutInflater inflater = LayoutInflater.from(context);
this.content = inflater.inflate(R.layout.activity_stream_contextmenu_bottomsheet, (ViewGroup) activityView, false);
bottomSheetDialog.setContentView(content);
final String pageTitle = item.getTitle();
final String sheetPageTitle = !TextUtils.isEmpty(pageTitle) ? pageTitle : item.getUrl();
- ((TextView) content.findViewById(R.id.title)).setText(sheetPageTitle);
+ final TextView titleView = (TextView) content.findViewById(R.id.title);
+ titleView.setText(sheetPageTitle);
final TextView pageDomainView = (TextView) content.findViewById(R.id.url);
final URI itemURI;
try {
itemURI = new URI(item.getUrl());
final UpdatePageDomainAsyncTask updateDomainAsyncTask = new UpdatePageDomainAsyncTask(context, pageDomainView,
- itemURI);
+ itemURI, pageDomainTextReference);
updateDomainAsyncTask.execute();
} catch (final URISyntaxException e) {
// Invalid URI: not much processing we can do. Like the async task, the page title view sets itself to the
// url on error so we leave this field blank.
pageDomainView.setText("");
}
+ overrideInitialAccessibilityAnnouncement(pageDomainView, titleView, sheetPageTitle, item.getUrl());
+
// Copy layouted parameters from the Highlights / TopSites items to ensure consistency
final StreamOverridablePageIconLayout pageIconLayout =
(StreamOverridablePageIconLayout) content.findViewById(R.id.page_icon_layout);
final ViewGroup.LayoutParams layoutParams = pageIconLayout.getLayoutParams();
layoutParams.width = tilesWidth;
layoutParams.height = tilesHeight;
pageIconLayout.setLayoutParams(layoutParams);
@@ -96,16 +104,48 @@ import java.net.URISyntaxException;
pageIconLayout.updateIcon(item.getUrl(), overrideIconURL);
navigationView = (NavigationView) content.findViewById(R.id.menu);
navigationView.setNavigationItemSelectedListener(this);
super.postInit();
}
+ private void overrideInitialAccessibilityAnnouncement(final View pageDomainView, final View pageTitleView,
+ final String pageTitle, final String urlStr) {
+ final View.AccessibilityDelegate initialAnnouncementDelegate = new View.AccessibilityDelegate() {
+ @Override
+ public void onPopulateAccessibilityEvent(final View hostView, final AccessibilityEvent event) {
+ final String finalHost;
+ final String shortenedHost = pageDomainTextReference[0]; // the UpdatePageDomainAsyncTask return value.
+ if (!TextUtils.isEmpty(shortenedHost)) {
+ finalHost = shortenedHost;
+ } else if (TextUtils.isEmpty(urlStr)) {
+ // There's no url so we can't do any better.
+ finalHost = "";
+ } else {
+ // The async host isn't completed yet so we'll have to do a best approximation.
+ final Uri uri = Uri.parse(urlStr);
+ final String host = uri.getHost();
+ finalHost = !TextUtils.isEmpty(host) ? host : urlStr;
+ }
+
+ final String announcementText = finalHost + ", " + pageTitle;
+ event.getText().add(announcementText);
+ super.onPopulateAccessibilityEvent(hostView, event);
+ }
+ };
+
+ // The dialog finds the first available TextView and announces its text as the accessibility title. The
+ // pageDomainView is first but since the title is completed asynchronously, may not have text yet, and thus
+ // may be an invalid first TextView, we have to set the listener on both.
+ pageDomainView.setAccessibilityDelegate(initialAnnouncementDelegate);
+ pageTitleView.setAccessibilityDelegate(initialAnnouncementDelegate);
+ }
+
@Override
public MenuItem getItemByID(int id) {
return navigationView.getMenu().findItem(id);
}
@Override
public void show() {
// Try to use a 16:9 "keyline", i.e. we leave a 16:9 window of activity content visible
@@ -164,20 +204,23 @@ import java.net.URISyntaxException;
public void dismiss() {
bottomSheetDialog.dismiss();
}
/** Updates the given TextView's text to the page domain. */
private static class UpdatePageDomainAsyncTask extends URIUtils.GetFormattedDomainAsyncTask {
private final WeakReference<TextView> pageDomainViewWeakReference;
+ private final String[] pageDomainTextReference;
- private UpdatePageDomainAsyncTask(final Context context, final TextView pageDomainView, final URI uri) {
+ private UpdatePageDomainAsyncTask(final Context context, final TextView pageDomainView, final URI uri,
+ final String[] pageDomainTextReference) {
super(context, uri, true, 0); // baseDomain.
this.pageDomainViewWeakReference = new WeakReference<>(pageDomainView);
+ this.pageDomainTextReference = pageDomainTextReference;
}
@Override
protected void onPostExecute(final String baseDomain) {
super.onPostExecute(baseDomain);
final TextView pageDomainView = pageDomainViewWeakReference.get();
if (pageDomainView == null) {
@@ -190,12 +233,13 @@ import java.net.URISyntaxException;
// In the unlikely error case, we leave the field blank rather than setting it to the url because
// the page title view sets itself to the url on error.
} else {
final String normalizedHost = StringUtils.stripCommonSubdomains(uri.getHost());
updateText = !TextUtils.isEmpty(normalizedHost) ? normalizedHost : "";
}
+ pageDomainTextReference[0] = updateText;
pageDomainView.setText(updateText);
}
}
}