Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Make loading page progress bar accessible to TalkBack.
Browse files Browse the repository at this point in the history
  • Loading branch information
eeejay authored and jonalmeida committed Mar 28, 2019
1 parent 8831fac commit db229c9
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.widget.ProgressBar
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.menu.BrowserMenuBuilder
Expand Down Expand Up @@ -130,6 +131,17 @@ internal class DisplayToolbar(
context, null, android.R.attr.progressBarStyleHorizontal
).apply {
visibility = View.GONE
setAccessibilityDelegate(object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) {
super.onInitializeAccessibilityEvent(host, event)
if (event?.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
// Populate the scroll event with the current progress.
// See accessibility note in `updateProgress()`.
event.scrollY = progress
event.maxScrollY = max
}
}
})
}

private val browserActions: MutableList<DisplayAction> = mutableListOf()
Expand Down Expand Up @@ -185,11 +197,42 @@ internal class DisplayToolbar(

/**
* Updates the progress to be displayed.
*
* Accessibility note:
* ProgressBars can be made accessible to TalkBack by setting `android:accessibilityLiveRegion`.
* They will emit TYPE_VIEW_SELECTED events. TalkBack will format those events into percentage
* announcements along with a pitch-change earcon. We are not using that feature here for
* several reasons:
* 1. They are dispatched via a 200ms timeout. Since loading a page can be a short process,
* and since we only update the bar a handful of times, these events often never fire and
* they don't give the user a true sense of the progress.
* 2. The last 100% event is dispatched after the view is hidden. This prevents the event
* from being fired, so the user never gets a "complete" event.
* 3. Live regions in TalkBack have their role announced, so the user will hear
* "Progress bar, 25%". For a common feature like page load this is very chatty and unintuitive.
* 4. We can provide custom strings instead of the less useful percentage utterance, but
* TalkBack will not play an earcon if an event has its own text.
*
* For all those reasons, we are going another route here with a "loading" announcement
* when the progress bar first appears along with scroll events that have the same
* pitch-change earcon in TalkBack (although they are a bit louder). This gives a concise and
* consistent feedback to the user that they can depend on.
*
*/
fun updateProgress(progress: Int) {
if (!progressView.isVisible() && progress > 0) {
// Loading has just started, make visible and announce "loading" for accessibility.
progressView.visibility = View.VISIBLE
progressView.announceForAccessibility(context.getString(R.string.mozac_browser_toolbar_progress_loading))
}

progressView.progress = progress
progressView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED)

progressView.visibility = if (progress < progressView.max && progress > 0) View.VISIBLE else View.GONE
if (progress >= progressView.max) {
// Loading is done, hide progress bar.
progressView.visibility = View.GONE
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<!-- Content description (not visible, for screen readers etc.): Description for the overflow menu button in the browser toolbar. -->
<string name="mozac_browser_toolbar_menu_button">Menü</string>
<string name="mozac_clear_button_description">Leeren</string>
<string name="mozac_browser_toolbar_progress_loading">Wird geladen</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<!-- Content description (not visible, for screen readers etc.): Description for the overflow menu button in the browser toolbar. -->
<string name="mozac_browser_toolbar_menu_button">菜单</string>
<string name="mozac_clear_button_description">清除</string>
<string name="mozac_browser_toolbar_progress_loading">正在加载</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
<!-- Content description (not visible, for screen readers etc.): Description for the overflow menu button in the browser toolbar. -->
<string name="mozac_browser_toolbar_menu_button">選單</string>
<string name="mozac_clear_button_description">清除</string>
<string name="mozac_browser_toolbar_progress_loading">載入中</string>
</resources>
2 changes: 2 additions & 0 deletions components/browser/toolbar/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
<!-- Content description (not visible, for screen readers etc.): Description for the overflow menu button in the browser toolbar. -->
<string name="mozac_browser_toolbar_menu_button">Menu</string>
<string name="mozac_clear_button_description">Clear</string>
<!-- Announcement made by the screen reader when the progress bar is shown and a page is loading -->
<string name="mozac_browser_toolbar_progress_loading">Loading</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import android.graphics.drawable.Drawable
import android.support.v13.view.inputmethod.EditorInfoCompat
import android.util.AttributeSet
import android.view.View
import android.view.ViewParent
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.widget.ImageButton
import android.widget.LinearLayout
import androidx.test.core.app.ApplicationProvider
Expand All @@ -32,15 +35,18 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows

@RunWith(RobolectricTestRunner::class)
class BrowserToolbarTest {
Expand Down Expand Up @@ -135,6 +141,39 @@ class BrowserToolbarTest {
verify(ediToolbar).updateUrl("https://www.mozilla.org")
}

@Test
fun `displayProgress will send accessibility events`() {
val toolbar = BrowserToolbar(context)
val root = mock(ViewParent::class.java)
Shadows.shadowOf(toolbar).setMyParent(root)
`when`(root.requestSendAccessibilityEvent(any(), any())).thenReturn(false)

Shadows.shadowOf(context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager).setEnabled(true)

toolbar.displayProgress(10)
toolbar.displayProgress(50)
toolbar.displayProgress(100)

val captor = ArgumentCaptor.forClass(AccessibilityEvent::class.java)

verify(root, times(4)).requestSendAccessibilityEvent(any(), captor.capture())

assertEquals(AccessibilityEvent.TYPE_ANNOUNCEMENT, captor.allValues[0].eventType)
assertEquals(context.getString(R.string.mozac_browser_toolbar_progress_loading), captor.allValues[0].text[0])

assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[1].eventType)
assertEquals(10, captor.allValues[1].scrollY)
assertEquals(100, captor.allValues[1].maxScrollY)

assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[2].eventType)
assertEquals(50, captor.allValues[2].scrollY)
assertEquals(100, captor.allValues[2].maxScrollY)

assertEquals(AccessibilityEvent.TYPE_VIEW_SCROLLED, captor.allValues[3].eventType)
assertEquals(100, captor.allValues[3].scrollY)
assertEquals(100, captor.allValues[3].maxScrollY)
}

@Test
fun `displayProgress will be forwarded to display toolbar`() {
val toolbar = BrowserToolbar(context)
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ permalink: /changelog/
* **browser-storage-sync**, **browser-storage-memory**
* Implementations of `concept-storage`/`HistoryStorage` expose newly added `deleteVisit`.

* **browser-toolbar**
* Add TalkBack support for page load status.

# 0.48.0

* [Commits](https://github.com/mozilla-mobile/android-components/compare/v0.47.0...v0.48.0)
Expand Down

0 comments on commit db229c9

Please sign in to comment.