-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Background: Within <xxx role=application>...</xxx>, screen readers should not consume DPAD (arrow key) events. Web apps or widgets with role=application have, per the WAI-ARIA spec's contract, their own JavaScript logic for moving focus [1]. [1] w3c/aria#1049, where we discussed this. Problem: TalkBack does not handle role=application so such web apps loose their 4-way (up/down/left/right) navigation. TalkBack only moves foward/backward which breaks authors' pre-defined TV UX. Solution: Whenever accessibility focus (the green rect) goes to a WebView with <body role=application> or anywhere within a role=application widget, we don't consume the DPAD events; we let them through. Testing done: From TalkBack's test app, open dpad_a11y.html which has <body role=application>. Notice: I. Once the WebView gets accessibilty focus, TalkBack won't consume DPAD key events. II. The key events reach the web page's key handlers in JavaScript. III: TalkBack describes HTML elements once they get focused.
- Loading branch information
1 parent
fa67c12
commit d14ef98
Showing
6 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Example TV web app</title> | ||
<style> | ||
div.outer {display: grid; grid-template-columns: 150px 150px;} | ||
div.inner {background: teal; margin: 20px; color: white; font-size: 30px; font-family: Sans-Serif; display: flex; align-items: center; justify-content: center;} | ||
div:focus {background: purple;} | ||
</style> | ||
</head> | ||
|
||
<body role="application"> | ||
<p tabindex="0">This web app allows navigation in 4 directions. Use your arrow keys (DPAD_UP / DPAD_DOWN / DPAD_LEFT / DPAD_RIGHT).</p> | ||
<p tabindex="0"><em>role=application</em> makes TalkBack forward DPAD events to Chromium.</p> | ||
<div role="grid"> | ||
<div class="outer" role="row"> | ||
<div role="gridcell" class="inner" tabindex="0" id="a" aria-label="Astrid">A</div><div role="gridcell" class="inner" tabindex="0" id="c" aria-label="Caroline">C</div> | ||
</div> | ||
<div class="outer" role="row"> | ||
<div role="gridcell" class="inner" tabindex="0" id="b" aria-label="Bernie">B</div><div role="gridcell" class="inner" tabindex="0" id="d" aria-label="David">D</div> | ||
</div> | ||
</div> | ||
|
||
<input type="radio" name="mechanics" id="usecustomjs" checked> JavaScript moves web focus. | ||
<input type="radio" name="mechanics" id="usechromium"> Chromium's built-in key handling moves web focus. | ||
</body> | ||
|
||
<script> | ||
// These key handlers use preventDefault to stop Chromium's | ||
// built-in focus mechanics, called "Spatial Navigation", which | ||
// is enabled by default in Android WebViews. | ||
function moveFromA(event) { | ||
switch (event.code) { | ||
case 'ArrowUp': | ||
/* go nowhere */ event.preventDefault(); break; | ||
case 'ArrowLeft': | ||
case 'ArrowRight': | ||
c.focus(); event.preventDefault(); break; | ||
case 'ArrowDown': | ||
b.focus(); event.preventDefault(); break; | ||
} | ||
} | ||
|
||
function moveFromC(event) { | ||
switch (event.code) { | ||
case 'ArrowUp': | ||
/* go nowhere */ event.preventDefault(); break; | ||
case 'ArrowLeft': | ||
case 'ArrowRight': | ||
a.focus(); event.preventDefault(); break; | ||
case 'ArrowDown': | ||
d.focus(); event.preventDefault(); break; | ||
} | ||
} | ||
|
||
function moveFromB(event) { | ||
switch (event.code) { | ||
case 'ArrowUp': | ||
a.focus(); event.preventDefault(); break; | ||
case 'ArrowLeft': | ||
case 'ArrowRight': | ||
d.focus(); event.preventDefault(); break; | ||
case 'ArrowDown': | ||
usecustomjs.focus(); event.preventDefault(); break; | ||
} | ||
} | ||
|
||
function moveFromD(event) { | ||
switch (event.code) { | ||
case 'ArrowUp': | ||
c.focus(); event.preventDefault(); break; | ||
case 'ArrowLeft': | ||
case 'ArrowRight': | ||
b.focus(); event.preventDefault(); break; | ||
case 'ArrowDown': | ||
usechromium.focus(); event.preventDefault(); break; | ||
} | ||
} | ||
|
||
// Mimic a typical TV app. | ||
function enableCustomDpadNavigation() { | ||
a.addEventListener('keydown', moveFromA); | ||
b.addEventListener('keydown', moveFromB); | ||
c.addEventListener('keydown', moveFromC); | ||
d.addEventListener('keydown', moveFromD); | ||
} | ||
|
||
function disableCustomDpadNavigation() { | ||
a.removeEventListener('keydown', moveFromA); | ||
b.removeEventListener('keydown', moveFromB); | ||
c.removeEventListener('keydown', moveFromC); | ||
d.removeEventListener('keydown', moveFromD); | ||
} | ||
|
||
function toggleCustomDpad() { | ||
if (usecustomjs.checked) | ||
enableCustomDpadNavigation(); | ||
else | ||
disableCustomDpadNavigation(); | ||
} | ||
usecustomjs.addEventListener('change', toggleCustomDpad); | ||
usechromium.addEventListener('change', toggleCustomDpad); | ||
|
||
enableCustomDpadNavigation(); | ||
a.focus(); | ||
</script> |
54 changes: 54 additions & 0 deletions
54
testapp/app/src/main/java/com/android/talkbacktests/testsession/WebViewDPADTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
* Copyright (C) 2019 The Android Open Source Project | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
* use this file except in compliance with the License. You may obtain a copy of | ||
* the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
* License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
|
||
package com.android.talkbacktests.testsession; | ||
|
||
import android.content.Context; | ||
import android.view.KeyEvent; | ||
import android.view.LayoutInflater; | ||
import android.view.View; | ||
import android.view.ViewGroup; | ||
import android.webkit.WebView; | ||
|
||
import com.android.talkbacktests.R; | ||
|
||
public class WebViewDPADTest extends BaseTestContent { | ||
|
||
public WebViewDPADTest(Context context, String subtitle, String description) { | ||
super(context, subtitle, description); | ||
} | ||
|
||
@Override | ||
public View getView(final LayoutInflater inflater, ViewGroup container, Context context) { | ||
final View view = inflater.inflate(R.layout.test_web_view, container, false); | ||
final WebView webView = (WebView) view.findViewById(R.id.webview); | ||
webView.getSettings().setJavaScriptEnabled(true); | ||
webView.loadUrl("file:///android_asset/dpad_a11y.html"); | ||
webView.setOnKeyListener(new View.OnKeyListener() { | ||
@Override | ||
public boolean onKey(View v, int keyCode, KeyEvent event) { | ||
if (keyCode == KeyEvent.KEYCODE_BACK) { // Exit the WebView | ||
View parent = (View) v.getParent().getParent().getParent(); | ||
View nextButton = parent.findViewById(R.id.next); | ||
// Move Android focus to the native button. | ||
return nextButton != null && nextButton.requestFocus(); | ||
} | ||
return false; | ||
} | ||
}); | ||
return view; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters