[//]: # (Outline Slide) .left-column[ # Today's goals ] .right-column[ - Reminders - Layout Part 3-4 due tomorrow at 10pm - Remember to keep commiting and pushing to your repo for backups! - Accessibility assignment is published! - Please do the section 4 prep (when posted) - Do the required readings/watchings this week - **Finish [Inclusive Design](inclusive-design.html#17)** - Android Accessibility in practice! - Intro to the Accessibility assignment ] --- class: middle, center, inverse # Accessibility on Android CSE 340, Spring 23 --- [//]: # (Outline Slide) .left-column[ # Today's goals ] .right-column[ - Reminders - Layout Part 3-4 due tomorrow at 10pm - Remember to keep commiting and pushing to your repo for backups! - Accessibility assignment is published! - Please do the section 4 prep (when posted) - Do the required readings/watchings this week - Finish [Inclusive Design](inclusive-design.html#17) - **Android Accessibility in practice!** - Intro to the Accessibility assignment ] --- # So you believe accessibility is important... what now? - How to add content descriptions - Making navigation work - Color contrast - Button size - Invisible Things - Testing --- # ContentDescription Talkback will "tell" what an item on the screen is if - it has a content description (`android:contentDescription="..."`) OR - is focusable (`android:focusable="true"`) OR - it is clickable (`android:clickable="true"`) OR - You have attached a `onClickListener` to the interactor. (We'll learn about this in our next class) --- # ContentDescription Use Android Lint can help find (static) problems in your code Set ContentDescription to `null` for decorative elements (such as spacers) Reminder: Don't use state (Selected) or role (Button) in the description. -- count: false - why not? --- # TextEdit .left-column[ ![:img An example screen with edit fields that show hint text, 100%, width](img/android-accessibility/hintexample.png)] ] .right-column[ Use `android:hint` to have a hint show up in the edit box (text that appears in the box but is not "really there" for the purposes of input) - If you set a `android:hint` don't also set a `contentDescription` - You *can* still set `android:labelFor` if the `TextEdit` has a `View` labeling it. ] .footnote[Image from [Material Design Text Fields](https://material.io/components/text-fields)] --- # Can group related content - Make the container for a group `android:focusable` - Android will read all the content together - Good for things like forms (label + TextEdit) or custom ViewGroups that have multiple items but no or a single interaction. - Reduces swiping - Streamlines speech output -- count: false Can also - Use `android:accessibilityTraversalBefore` when the order of traversal should not match the interactor hierarchy - Use `android:labelFor` to indicate when a `TextView` is a label for a `TextEntry` --- # Making Navigation work Things like `android:accessibilityTraversalBefore` help improve navigation So does grouping May also want to *skip* things sometimes (this happens in the assignment) - Don't use content description in this case - May also use `android:importantForAccessibility` or `android:focusable` (you'll need to read up on where and when to use these) --- # Other concerns Color contrast - If your fonts are < 18pt, or < **14pt bold**, color contrast should be >= 4.5:1. - Otherwise 3.0:1. ![:img Picture of the word Text twice on a grey background: Once with low and once with ok contrast ,60%, width](img/android-accessibility/contrast.png) But you can't count on color contrast being enough. Also use descriptive text or other redundant cues. --- # Other concerns Color contrast - If your fonts are < 18pt, or < **14pt bold**, color contrast should be >= 4.5:1. - Otherwise 3.0:1. Make your buttons big enough - 48x48dp - `android:minHeight` in `EditText` objects --- # Other concerns Color contrast - If your fonts are < 18pt, or < **14pt bold**, color contrast should be >= 4.5:1. - Otherwise 3.0:1. **Problem:** what if the color contrast is poor in an `AlertDialog` built by the system? -- count: false **Solution:** Look in `styles.xml` and you might find what the `colorAccent` is for the theme for the app. The actual value for that color, likely stored in the `colors.xml` file, can be changed to something with more contrast: example: ```xml
#D81B60
``` .footnote[Solution from [Making an AlertDialog in Android](https://suragch.medium.com/making-an-alertdialog-in-android-2045381e2edb)] --- # Invisible things **Problem:** Sometimes navigation can't get to an interactor. -- count: false **Solution:** use `android:importantForAccessibility` or `android:focusable` (you'll need to read up on where and when to use these) -- count: false **Problem:** Sometimes Android doesn't know you changed something. -- count: false **Solution:** use `[view].announceForAccessibility()` --- # Case Study: Ask For Help .left-column40[ ![:img Picture the original Help app,70%, width](img/android-accessibility/askforhelp.png) ] .right-column60[ Our Case Study/Assignment will be based on an app called **Ask For Help** - App was written by Jen Mankoff's child who was bedridden in middle school (original interface shown at the left, we reimplemented it) - As a service project they created an app that would allow people to "ask for help" quickly with messages and contacts that are pre-programmed into the app. - At the start of the app, there are no messages and no contacts. You have to go to Settings to fill those in. ] --- # Case Study: Ask For Help .left-column40[ ![:img Ask for Help Case study main screen,70%, width](img/android-accessibility/askforhelp-assignment.png) ] .right-column60[ Demo ] -- .right-column60[ How can we find all of the problems in the app? ] --- # How do we find all these problems? 1. Use Android Lint (Code->Inspect code) to help find problems ![:img The Android Lint with the menu item to invoke it (Code->Inspect code) highlighted ,70%, width](img/android-accessibility/lint.png) --- # Checks for basics, such as - Accessibility/Missing content descriptions --- # How do we find all these problems? 1. Use Android Lint to help find problems 2. Use the [Accessibility Scanner](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en_US) ![:img The Android Playstore install page for the Accessibility Scanner ,100%, width](img/android-accessibility/scanner1.png) --- # Checks for basics, such as - Overlapping clickable items - Clickable items that are too small - TextView has content description. This might interfere with screen reader reading its hint or actual content - Low contrast in image or icon or text - Items with identical speakable text - Item Label ends with redundant role information, e.g. ."Play Button". - URL link may be invalid --- # How it works - Turn it on in settings after installing - Use your app doing the things you would do anyway - Tell it you're done - View the results in the AccessibilityScanner App --- # Turn it on - Agree to authorizations - Agree to display over screens --- # Use your app .left-column[ ![:img The original AskForHelp app with the Accessibility Scanner running ,100%, width](img/android-accessibility/scanner2.png) ] .right-column[ - Click the check mark (Authorize if needed) - Click "Record" - Use the app - *Exit the app* so you can click "Stop" ![:img The stop button for the Accessibility Scanner shown, 10%, width](img/android-accessibility/stop.png) ] --- # See the result in the scanner .column[ ![:img The original AskForHelp app with the Accessibility Scanner running ,80%, width](img/android-accessibility/scanner3.png) ] -- .column[ ![:img The original AskForHelp app with the Accessibility Scanner running ,80%, width](img/android-accessibility/scanner4.png) ] --- # See the result in the scanner .column[ ![:img The original AskForHelp app with the Accessibility Scanner running ,80%, width](img/android-accessibility/scanner5.png) ] .column[ ![:img The original AskForHelp app with the Accessibility Scanner running ,80%, width](img/android-accessibility/scanner6.png) ] --- # How do we find all these problems? 1. Use Android Lint to help find problems 2. Use the [Accessibility Scanner](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en_US) 3. Test your app with TalkBack --- # Why Use Talkback Automated tools don't work. They are spectacularly good at some things and spectacularly bad at others. .left-column50[ ## Good - Can tell you if you have alt text ] .right-column50[ ## Bad - Can't tell you if alt text is useful ] --- # Why Use Talkback .left-column50[ ## Good - Can tell you if you have alt text - Can tell you if an item is too small
- Can tell you if contrast is bad
- ... ] .right-column50[ ## Bad - Can't tell you if alt text is useful - Can't tell you if you can get to it or it is in the right order - Can't tell you whether to increase font or change color - ... ] Trying it yourself is the best way to find problems. You still won't find them all but our research shows that with a checklist of known problems *and* a screen reader you will do much better than with an automated tool. --- # Using Talkback After TalkBack is on, there are two common ways to navigate: - Linear navigation: Quickly swipe right or left to navigate through screen elements in sequence. Double-tap anywhere to select. - Explore by touch: Drag your finger over the screen to hear what's under your finger. Double-tap anywhere to select. **Note:** Talkback in the emulator can be tricky, particular on a Mac. You may have to use arrow keys with a modifier or the tab key to simulate the interaction. Make sure to read the **Aside** in part 5 of the spec. --- # Using Talkback Optional: TalkBack developer settings - Navigate to Accessibility and select TalkBack. - Select Settings > Developer settings: - Log output level: Select VERBOSE. - Display speech output: Turn on this setting to view TalkBack speech output on the screen. --- # How do we find all these problems? 1. Use Android Lint to help find problems 2. Use the [Accessibility Scanner](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en_US) 3. Test your app with TalkBack 4. Turn on [Switch Access](https://developer.android.com/guide/topics/ui/accessibility/testing#turn-on-switch-access) --- # Issues Different phones, emulators, and versions of the operating systems behave differently. Example `announceForAccessibility` may or may not actually announce because of timing issues. One suggestion is to use a small pop up message called a [Toast](https://developer.android.com/guide/topics/ui/notifiers/toasts) which will be read by TalkBack. ```java Toast.makeText(this, getString(id), Toast.LENGTH_LONG).show(); ``` --- # Accessibility assignment **Goal:** find, fix, and document accessibility problems in code for a modified version of AskForHelp using Android Accessibility tools. We will assess your: - modifications to the code using good code quality principles - demonstration of your fixes in a high quality video - reflection on your experience & choices made in making this app inclusive. --- # Accessibility assignment Notes: - There is not "one true solution" for this problem. You have to use your best judgement on how to make the app more accessible. - There are no screenshot tests, the Android tools will help you solve the problem. - We will be running the same Accessibility scanner you will as part of our testing suite. - Different phones, emulators, and versions of the operating systems behave differently. --- # 🤔 🍐 💬 Think, Pair, Share: .left-column40[ ![:img Ask for Help Case study main screen,45%, width](img/android-accessibility/askforhelp-assignment.png) ![:img Ask for Help after deleting a request,45%, width](img/android-accessibility/AFH-delete.png) ] .right-column60[ Think about the following situation: The user deletes one of the requests. - What should be announced to the user? - Where should the focus go so the app is easiest to use for the next task? - What would you do similarly or differently if you're adding a new request? Talk with your neighbor, then we'll share out. ] --- # Accessibility assignment Let's explore the base repository. What do you notice? -- count: false - There are are a lot more Java files -- count: false - There are are a lot more resources --- # `
` in `.xml` Allows for refactoring of commonly used layouts. Example in `activity_edit_requests.xml` ```xml
``` This brings in the contents of the `navigation_buttons.xml` file into the edit requests layout. --- # Strings Strings are stored in `strings.xml`. ```xml
Enter new request text
``` These can be used in your code either - As the resource id itself (if the method takes a string resource as a parameter): `R.string.enter_new_request` - By using `getString()` if you're in an `Activity`: `getString(R.string.enter_new_request))` - By using getting the resources to get the string outside of an `Activity`: `getResources().getString(R.string.enter_new_request))` .footnote[[Difference between getResources().getString() and getString()](https://teamtreehouse.com/community/difference-between-getresourcesgetstring-and-getstring)] --- # Formal Arguments & Strings Avoid getting separate strings and using concatenation (e.g. `stringA + stringB`) for user-facing strings. Instead use `getString(resourceId, Object... formatArgs)`! - The `formatArgs` we pass to `getString` are substituted into the text from the resourceId. - The patterns in `strings.xml` are the same that we use with `String#format`. ```xml
Hello %s
World
``` ```java String result = getString(R.string.format_args_test, getString(R.string.world)); ``` .footnote[See this [StackOverflow post](https://stackoverflow.com/a/6431949) for more on String#format] --- # Format arguments are important In different languages/locales, it might make more sense for words to be in a different order. For example, `"Hello, %s"` where `%s` is a name, might make more sense as the equivalent to `"%s hello"` depending on the language. Using format arguments helps us properly localize our application! - Documentation on [`getString()`](https://developer.android.com/guide/topics/resources/string-resource#formatting-strings) - Guide on [Localizing your app](https://developer.android.com/guide/topics/resources/localization) --- ## Styles Accessbility uses a new (to us) resource file `styles.xml` - Android [Styles and Themes](https://developer.android.com/guide/topics/ui/look-and-feel/themes) are used to refactor common appearances. - A style is a collection of attributes, such as color, dimensions, etc, - Can use a style to change appearance of `View` objects. Example the Settings `ImageButton` in `activity_choose_requests.xml` has an attribute `style="@style/icon"` defined in `styles.xml` ```xml ``` --- # Bundle in Accessibility Recall: a `Bundle` can be used to comunicate when switching activities. Example in `ChooseRequestsActivity` ```java // Instance variable declaration private Bundle mMessageInfo; // Instance variable initialization (in setVariables called by onCreate) mMessageInfo = new Bundle(); // Setting key/value pairs mMessageInfo.putString("last_button_pressed", "none"); // Switching activities Intent intent = new Intent(this, ChooseContactGroupsActivity.class); mMessageInfo.remove("last_button_pressed"); String request = ((TextView)view).getText().toString(); mMessageInfo.putString("last_button_pressed", request); // Attaching the bundle to the intent intent.putExtras(mMessageInfo); startActivity(intent); ``` --- # Bundle in Accessibility Recall: a `Bundle` can be used to comunicate when switching activities. Example in `ChooseContactsActivity` ```java // Instance variable declaration private Bundle mMessageInfo; // Instance variable initialization (in setVariables called by onCreate) mMessageInfo = getIntent().getExtras(); // Add things to the bundle in addNumbersToBundle and textPeople mMessageInfo.putString("contact" + (ii + 1), contactsAsString); // Get information from the bundle String textBody = mMessageInfo.getString("last_button_pressed"); String numbers = "smsto:" + mMessageInfo.getString("contact" + contactID); ``` --- # Shared Preferences Recall, we can save to and restore from `SharedPreferences` file. ```java // setting data SharedPreferences.Editor editor = getPrefs().edit(); editor.putBoolean("mLocationOn", true); editor.putInt("group_selected", mWhichGroup); editor.putString("contactGroup" + (ii + 1), contactGroup); editor.putStringSet("contact_numbers" + (ii + 1), mNumbers.get(ii)); editor.apply(); // have to force the write. // getting data, the last parameter is the default. mLocationOn = getPrefs().getBoolean("mLocationOn", true); mWhichGroup = getPrefs().getInt("group_selected", 1); String contactGroupName = getPrefs().getString("contactGroup" + (ii + 1), ""); Set
ids = getPrefs().getStringSet("contact_ids" + (ii + 1), ``` --- # What's a FAB? .left-column40[ A FAB is a **Floating Action Button** - Often a single press item on the screen - Occasionally "blows open" to display other FABs ] .right-column60[ ![:img Twitter app FAB in close (on the left) and open (on the right) states, 80%,width](img/android-accessibility/twitter-fab.png) ] --- # Defining a FAB in XML ```XML <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/addContactRowFAB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/vMargin" android:backgroundTint="@color/colorPrimary" android:src="@drawable/add" android:tint="@color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> ``` --- # Clicking on a FAB The user clicking on the FAB triggers an event. In order to have YOUR code respond to that user click event, you have to assign a "click listener" to it. ```java // Use setOnClickListener with a lambda on a View object findViewById(R.id.addContactRowFAB).setOnClickListener(v -> startNewContact()); ``` --- # Preview: Click Listeners But what the heck is this?!?!? ```java requestButton.setOnClickListener(request -> { ... }); ``` -- count: false Or this?!?!?! ```java settings.setOnClickListener(view -> switchActivity(view.getContext(), SettingsActivity.class)); ``` -- count: false Stay tuned to our next lecture: Events! --- # END OF DECK