Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android Accessibility at GDG Devfest Brussels 2016

AppFoundry
October 23, 2016

Android Accessibility at GDG Devfest Brussels 2016

'Accessibility for everyone' session at the GDG Brussels 2016 Devfest.

AppFoundry

October 23, 2016
Tweet

More Decks by AppFoundry

Other Decks in Programming

Transcript

  1. Accessibility /ˈapps for everybody/ Android apps that are usable by

    everyone. Make no assumptions about the user.
  2. Accessibility? 2 % visual impairment 8 % men color blind

    0,1 % blind 8 out of 10 with visual impairment or blindness are older than 65
  3. Android Accessibility features Large Text Magnification Gestures High Contrast Text

    Color inversion Color correction Caption support App developer Text size = sp Layouts = responsive
  4. AnnounceForAccessibility private void setFavoriteListener(final Team team, final boolean isFavorite) {


    view.setOnClickListener(new View.OnClickListener() {
 
 @Override
 public void onClick(View v) {
 teamListener.onTeamClicked(team);
 String announcement = isFavorite
 ? "Removed " + team.displayTitle() + " from favourites"
 : "Added " + team.displayTitle() + " to favourites";
 view.announceForAccessibility(announcement);
 }
 
 });
 }
  5. AnnounceForAccessibility private void setFavoriteListener(final Team team, final boolean isFavorite) {


    view.setOnClickListener(new View.OnClickListener() {
 
 @Override
 public void onClick(View v) {
 teamListener.onTeamClicked(team);
 String announcement = isFavorite
 ? "Removed " + team.displayTitle() + " from favourites"
 : "Added " + team.displayTitle() + " to favourites";
 view.announceForAccessibility(announcement);
 }
 
 });
 }
  6. Control focus order <LinearLayout
 android:orientation="horizontal">
 
 <EditText
 android:id="@+id/edit"
 android:nextFocusForward="@+id/text" />


    
 <TextView
 android:id="@+id/text"
 android:focusable="true"
 android:text="Hello, I am a focusable TextView" />
 </LinearLayout>
  7. Control focus order <LinearLayout
 android:orientation="horizontal">
 
 <EditText
 android:id="@+id/edit"
 android:nextFocusForward="@+id/text" />


    
 <TextView
 android:id="@+id/text"
 android:focusable="true"
 android:text="Hello, I am a focusable TextView" />
 </LinearLayout>
  8. Talkback detection public boolean isAccessibilityEnabled() { 
 final AccessibilityManager am

    = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 
 boolean isAccessibilityEnabled = am.isEnabled();
 boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled(); 
 return isAccessibilityEnabled && isExploreByTouchEnabled;
 }
  9. Talkback detection public boolean isAccessibilityEnabled() {
 final AccessibilityManager am =

    (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 
 boolean isAccessibilityEnabled = am.isEnabled();
 boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled(); 
 boolean isSpokenFeedbackEnabled = isSpokenFeedbackEnabled(am);
 return isAccessibilityEnabled && isExploreByTouchEnabled && isSpokenFeedbackEnabled;
 } 
 
 /**
 * Check whether 'spoken feedback' services are enabled.
 */
 private boolean isSpokenFeedbackEnabled(final AccessibilityManager am) {
 List<AccessibilityServiceInfo> enabledSpokenFeedbackServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN); 
 return !enabledSpokenFeedbackServices.isEmpty();
 }
  10. Meaningful description private void setAccessibilityText(final TeamInfo teamInfo) {
 String accessibilityText;


    if (teamInfo.coaches().isEmpty()) {
 accessibilityText = getString(R.string.no_trainer_acc, teamInfo.teamName());
 } else {
 List<String> trainerNames = getTrainerNames(teamInfo.coaches());
 
 Joiner joiner = Joiner.on(andStr).skipNulls();
 String concatTrainers = joiner.join(trainerNames);
 accessibilityText = getQuantityString(R.plurals.trainer_acc, teamInfo.coaches().size(), teamInfo.teamName(), concatTrainers);
 }
 
 getItemView().setContentDescription(accessibilityText);
 }
  11. MatchView public interface MatchListener {
 
 void onMatchClicked(Match match);
 


    void onMatchComment(Match match);
 
 void onMatchShare(Match match);
 
 void onMatchLiked(Match match);
 }
  12. DIY public void showMatch(final Match match, final MatchListener listener) {


    …
 
 setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View view) {
 if (isAccessibilityEnabled) {
 showCustomActions(match);
 } else {
 listener.onMatchClicked(match);
 }
 }
 });
  13. Accessibility delegate public static class MatchAccessibilityDelegate extends AccessibilityDelegateCompat {
 


    private final Match match;
 private final MatchListener listener;
 
 public MatchAccessibilityDelegate(final Match match, final MatchListener listener) {
 this.match = match;
 this.listener = listener;
 }
 
 @Override
 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
 super.onInitializeAccessibilityNodeInfo(host, info);
 info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.comment, "Comment"));
 info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.share, "Share"));
 …
 }
 
 @Override
 public boolean performAccessibilityAction(View host, int action, Bundle args) {
 switch (action) {
 case R.id.comment:
 listener.onMatchComment(match);
 return true;
 case R.id.share:
 listener.onMatchShare(match);
 return true;
 …
 return true;

  14. Accessibility delegate public static class MatchAccessibilityDelegate extends AccessibilityDelegateCompat {
 


    private final Match match;
 private final MatchListener listener;
 
 public MatchAccessibilityDelegate(final Match match, final MatchListener listener) {
 this.match = match;
 this.listener = listener;
 }
 
 @Override
 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
 super.onInitializeAccessibilityNodeInfo(host, info);
 info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.comment, "Comment"));
 info.addAction(new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.share, "Share"));
 …
 }
 
 @Override
 public boolean performAccessibilityAction(View host, int action, Bundle args) {
 switch (action) {
 case R.id.comment:
 listener.onMatchComment(match);
 return true;
 case R.id.share:
 listener.onMatchShare(match);
 return true;
 …

  15. Accessibility delegate public void showMatch(final Match match, final MatchListener listener)

    {
 …
 
 ViewCompat.setAccessibilityDelegate(this, new MatchAccessibilityDelegate(match, listener));
 
 …
  16. Talkback basics Touch to explore Advance through elements (swipe left

    - right) Double tap selection Gestures for Back, Home, Context menus, …
  17. Espresso - Accessibility @Test
 public void testPlayStopButtonContentDescription() {
 final ViewInteraction

    playButtonInteraction = onView(withId(R.id.play_button));
 playButtonInteraction.check(matches(withContentDescription("Play")));
 
 playButtonInteraction.perform(click());
 playButtonInteraction.check(matches(withContentDescription("Stop")));
 }
  18. Accessibility checks com..…AccessibilityViewCheckException: There were 2 accessibility errors: AppCompatImageButton{id=2131427416, res-name=imageButton1,

    visibility=VISIBLE, width=144, height=132, has- focus=true, has-focusable=true, has-window-focus=false, is-clickable=true, is-enabled=true, is- focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=48.0, y=48.0}: View falls below the minimum recommended size for touch targets. Minimum touch target size is 48x48dp. Actual size is 48.0x44.0dp (screen density is 3.0)., AppCompatImageButton{id=2131427416, res-name=imageButton1, visibility=VISIBLE, width=144, height=132, has- focus=true, has-focusable=true, has-window-focus=false, is-clickable=true, is-enabled=true, is- focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=48.0, y=48.0}: View is missing speakable text needed for a screen reader at … at android.support.test.espresso.contrib.AccessibilityChecks$2.check(AccessibilityChecks.java:58) at android.support.test.espresso.action.ViewActions$1.perform(ViewActions.java:131) … at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
  19. Resources Accessibility Testing Checklist https://developer.android.com/training/accessibility/testing.html Google’s Accessibility Test Framework github.com/google/Accessibility-Test-Framework-for-Android

    Espresso Accessibility Checking google.github.io/android-testing-support-library/docs/accesibility-checking/ Talkback Gestures support.google.com/accessibility/android/answer/6151827?hl=en