Merge "Refactor WebView accessibility code into a separate class." into jb-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
308904dcd0
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
* Copyright (C) 2012 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.
|
||||
@@ -16,484 +16,335 @@
|
||||
|
||||
package android.webkit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextUtils.SimpleStringSplitter;
|
||||
import android.util.Log;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.webkit.WebViewCore.EventHub;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class injects accessibility into WebViews with disabled JavaScript or
|
||||
* WebViews with enabled JavaScript but for which we have no accessibility
|
||||
* script to inject.
|
||||
* </p>
|
||||
* Note: To avoid changes in the framework upon changing the available
|
||||
* navigation axis, or reordering the navigation axis, or changing
|
||||
* the key bindings, or defining sequence of actions to be bound to
|
||||
* a given key this class is navigation axis agnostic. It is only
|
||||
* aware of one navigation axis which is in fact the default behavior
|
||||
* of webViews while using the DPAD/TrackBall.
|
||||
* </p>
|
||||
* In general a key binding is a mapping from modifiers + key code to
|
||||
* a sequence of actions. For more detail how to specify key bindings refer to
|
||||
* {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
|
||||
* </p>
|
||||
* The possible actions are invocations to
|
||||
* {@link #setCurrentAxis(int, boolean, String)}, or
|
||||
* {@link #traverseCurrentAxis(int, boolean, String)}
|
||||
* {@link #traverseGivenAxis(int, int, boolean, String)}
|
||||
* {@link #prefromAxisTransition(int, int, boolean, String)}
|
||||
* referred via the values of:
|
||||
* {@link #ACTION_SET_CURRENT_AXIS},
|
||||
* {@link #ACTION_TRAVERSE_CURRENT_AXIS},
|
||||
* {@link #ACTION_TRAVERSE_GIVEN_AXIS},
|
||||
* {@link #ACTION_PERFORM_AXIS_TRANSITION},
|
||||
* respectively.
|
||||
* The arguments for the action invocation are specified as offset
|
||||
* hexademical pairs. Note the last argument of the invocation
|
||||
* should NOT be specified in the binding as it is provided by
|
||||
* this class. For details about the key binding implementation
|
||||
* refer to {@link AccessibilityWebContentKeyBinding}.
|
||||
* Handles injecting accessibility JavaScript and related JavaScript -> Java
|
||||
* APIs.
|
||||
*/
|
||||
class AccessibilityInjector {
|
||||
private static final String LOG_TAG = "AccessibilityInjector";
|
||||
// The WebViewClassic this injector is responsible for managing.
|
||||
private final WebViewClassic mWebViewClassic;
|
||||
|
||||
private static final boolean DEBUG = true;
|
||||
// Cached reference to mWebViewClassic.getContext(), for convenience.
|
||||
private final Context mContext;
|
||||
|
||||
private static final int ACTION_SET_CURRENT_AXIS = 0;
|
||||
private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
|
||||
private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
|
||||
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
|
||||
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
|
||||
// Cached reference to mWebViewClassic.getWebView(), for convenience.
|
||||
private final WebView mWebView;
|
||||
|
||||
// the default WebView behavior abstracted as a navigation axis
|
||||
private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
|
||||
// The Java objects that are exposed to JavaScript.
|
||||
private TextToSpeech mTextToSpeech;
|
||||
|
||||
// these are the same for all instances so make them process wide
|
||||
private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
|
||||
new ArrayList<AccessibilityWebContentKeyBinding>();
|
||||
// Lazily loaded helper objects.
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
private AccessibilityInjectorFallback mAccessibilityInjector;
|
||||
|
||||
// handle to the WebViewClassic this injector is associated with.
|
||||
private final WebViewClassic mWebView;
|
||||
// Whether the accessibility script has been injected into the current page.
|
||||
private boolean mAccessibilityScriptInjected;
|
||||
|
||||
// events scheduled for sending as soon as we receive the selected text
|
||||
private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
|
||||
// Constants for determining script injection strategy.
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
|
||||
@SuppressWarnings("unused")
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
|
||||
|
||||
// the current traversal axis
|
||||
private int mCurrentAxis = 2; // sentence
|
||||
// Aliases for Java objects exposed to JavaScript.
|
||||
private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
|
||||
|
||||
// we need to consume the up if we have handled the last down
|
||||
private boolean mLastDownEventHandled;
|
||||
|
||||
// getting two empty selection strings in a row we let the WebView handle the event
|
||||
private boolean mIsLastSelectionStringNull;
|
||||
|
||||
// keep track of last direction
|
||||
private int mLastDirection;
|
||||
// Template for JavaScript that injects a screen-reader.
|
||||
private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
|
||||
"javascript:(function() {" +
|
||||
" var chooser = document.createElement('script');" +
|
||||
" chooser.type = 'text/javascript';" +
|
||||
" chooser.src = '%1s';" +
|
||||
" document.getElementsByTagName('head')[0].appendChild(chooser);" +
|
||||
" })();";
|
||||
|
||||
/**
|
||||
* Creates a new injector associated with a given {@link WebViewClassic}.
|
||||
* Creates an instance of the AccessibilityInjector based on
|
||||
* {@code webViewClassic}.
|
||||
*
|
||||
* @param webView The associated WebViewClassic.
|
||||
* @param webViewClassic The WebViewClassic that this AccessibilityInjector
|
||||
* manages.
|
||||
*/
|
||||
public AccessibilityInjector(WebViewClassic webView) {
|
||||
mWebView = webView;
|
||||
ensureWebContentKeyBindings();
|
||||
public AccessibilityInjector(WebViewClassic webViewClassic) {
|
||||
mWebViewClassic = webViewClassic;
|
||||
mWebView = webViewClassic.getWebView();
|
||||
mContext = webViewClassic.getContext();
|
||||
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a key down <code>event</code>.
|
||||
*
|
||||
* @return True if the event was processed.
|
||||
* Attempts to load scripting interfaces for accessibility.
|
||||
* <p>
|
||||
* This should be called when the window is attached.
|
||||
* </p>
|
||||
*/
|
||||
public boolean onKeyEvent(KeyEvent event) {
|
||||
// We do not handle ENTER in any circumstances.
|
||||
if (isEnterActionKey(event.getKeyCode())) {
|
||||
public void addAccessibilityApisIfNecessary() {
|
||||
if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
addTtsApis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to unload scripting interfaces for accessibility.
|
||||
* <p>
|
||||
* This should be called when the window is detached.
|
||||
* </p>
|
||||
*/
|
||||
public void removeAccessibilityApisIfNecessary() {
|
||||
removeTtsApis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to handle key events when accessibility is turned on.
|
||||
*
|
||||
* @param event The key event to handle.
|
||||
* @return {@code true} if the event was handled.
|
||||
*/
|
||||
public boolean handleKeyEventIfNecessary(KeyEvent event) {
|
||||
if (!isAccessibilityEnabled()) {
|
||||
mAccessibilityScriptInjected = false;
|
||||
toggleFallbackAccessibilityInjector(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
return mLastDownEventHandled;
|
||||
}
|
||||
|
||||
mLastDownEventHandled = false;
|
||||
|
||||
AccessibilityWebContentKeyBinding binding = null;
|
||||
for (AccessibilityWebContentKeyBinding candidate : sBindings) {
|
||||
if (event.getKeyCode() == candidate.getKeyCode()
|
||||
&& event.hasModifiers(candidate.getModifiers())) {
|
||||
binding = candidate;
|
||||
break;
|
||||
if (mAccessibilityScriptInjected) {
|
||||
// if an accessibility script is injected we delegate to it the key
|
||||
// handling. this script is a screen reader which is a fully fledged
|
||||
// solution for blind users to navigate in and interact with web
|
||||
// pages.
|
||||
if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
|
||||
} else if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (binding == null) {
|
||||
if (mAccessibilityInjector != null) {
|
||||
// if an accessibility injector is present (no JavaScript enabled or
|
||||
// the site opts out injecting our JavaScript screen reader) we let
|
||||
// it decide whether to act on and consume the event.
|
||||
return mAccessibilityInjector.onKeyEvent(event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to handle selection change events when accessibility is using a
|
||||
* non-JavaScript method.
|
||||
*
|
||||
* @param selectionString The selection string.
|
||||
*/
|
||||
public void handleSelectionChangedIfNecessary(String selectionString) {
|
||||
if (mAccessibilityInjector != null) {
|
||||
mAccessibilityInjector.onSelectionStringChange(selectionString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares for injecting accessibility scripts into a new page.
|
||||
*
|
||||
* @param url The URL that will be loaded.
|
||||
*/
|
||||
public void onPageStarted(String url) {
|
||||
mAccessibilityScriptInjected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to inject the accessibility script using a {@code <script>} tag.
|
||||
* <p>
|
||||
* This should be called after a page has finished loading.
|
||||
* </p>
|
||||
*
|
||||
* @param url The URL that just finished loading.
|
||||
*/
|
||||
public void onPageFinished(String url) {
|
||||
if (!isAccessibilityEnabled()) {
|
||||
mAccessibilityScriptInjected = false;
|
||||
toggleFallbackAccessibilityInjector(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldInjectJavaScript(url)) {
|
||||
toggleFallbackAccessibilityInjector(true);
|
||||
return;
|
||||
}
|
||||
|
||||
toggleFallbackAccessibilityInjector(false);
|
||||
|
||||
final String injectionUrl = getScreenReaderInjectionUrl();
|
||||
mWebView.loadUrl(injectionUrl);
|
||||
|
||||
mAccessibilityScriptInjected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the non-JavaScript method for handling accessibility.
|
||||
*
|
||||
* @param enabled {@code true} to enable the non-JavaScript method, or
|
||||
* {@code false} to disable it.
|
||||
*/
|
||||
private void toggleFallbackAccessibilityInjector(boolean enabled) {
|
||||
if (enabled && (mAccessibilityInjector == null)) {
|
||||
mAccessibilityInjector = new AccessibilityInjectorFallback(mWebViewClassic);
|
||||
} else {
|
||||
mAccessibilityInjector = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether it's okay to inject JavaScript into a given URL.
|
||||
*
|
||||
* @param url The URL to check.
|
||||
* @return {@code true} if JavaScript should be injected, {@code false} if a
|
||||
* non-JavaScript method should be used.
|
||||
*/
|
||||
private boolean shouldInjectJavaScript(String url) {
|
||||
// Respect the WebView's JavaScript setting.
|
||||
if (!isJavaScriptEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0, count = binding.getActionCount(); i < count; i++) {
|
||||
int actionCode = binding.getActionCode(i);
|
||||
String contentDescription = Integer.toHexString(binding.getAction(i));
|
||||
switch (actionCode) {
|
||||
case ACTION_SET_CURRENT_AXIS:
|
||||
int axis = binding.getFirstArgument(i);
|
||||
boolean sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
setCurrentAxis(axis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_TRAVERSE_CURRENT_AXIS:
|
||||
int direction = binding.getFirstArgument(i);
|
||||
// on second null selection string in same direction - WebView handles the event
|
||||
if (direction == mLastDirection && mIsLastSelectionStringNull) {
|
||||
mIsLastSelectionStringNull = false;
|
||||
return false;
|
||||
}
|
||||
mLastDirection = direction;
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
|
||||
contentDescription);
|
||||
break;
|
||||
case ACTION_TRAVERSE_GIVEN_AXIS:
|
||||
direction = binding.getFirstArgument(i);
|
||||
// on second null selection string in same direction => WebView handle the event
|
||||
if (direction == mLastDirection && mIsLastSelectionStringNull) {
|
||||
mIsLastSelectionStringNull = false;
|
||||
return false;
|
||||
}
|
||||
mLastDirection = direction;
|
||||
axis = binding.getSecondArgument(i);
|
||||
sendEvent = (binding.getThirdArgument(i) == 1);
|
||||
traverseGivenAxis(direction, axis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_PERFORM_AXIS_TRANSITION:
|
||||
int fromAxis = binding.getFirstArgument(i);
|
||||
int toAxis = binding.getSecondArgument(i);
|
||||
sendEvent = (binding.getThirdArgument(i) == 1);
|
||||
prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
|
||||
// This is a special case since we treat the default WebView navigation
|
||||
// behavior as one of the possible navigation axis the user can use.
|
||||
// If we are not on the default WebView navigation axis this is NOP.
|
||||
if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
|
||||
// While WebVew handles navigation we do not get null selection
|
||||
// strings so do not check for that here as the cases above.
|
||||
mLastDirection = binding.getFirstArgument(i);
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
|
||||
sendEvent, contentDescription);
|
||||
mLastDownEventHandled = false;
|
||||
} else {
|
||||
mLastDownEventHandled = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.w(LOG_TAG, "Unknown action code: " + actionCode);
|
||||
}
|
||||
}
|
||||
|
||||
return mLastDownEventHandled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current navigation axis which will be used while
|
||||
* calling {@link #traverseCurrentAxis(int, boolean, String)}.
|
||||
*
|
||||
* @param axis The axis to set.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
*/
|
||||
private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
|
||||
mCurrentAxis = axis;
|
||||
if (sendEvent) {
|
||||
AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
|
||||
event.getText().add(String.valueOf(axis));
|
||||
event.setContentDescription(contentDescription);
|
||||
sendAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs conditional transition one axis to another.
|
||||
*
|
||||
* @param fromAxis The axis which must be the current for the transition to occur.
|
||||
* @param toAxis The axis to which to transition.
|
||||
* @param sendEvent Flag if to send an event to announce successful transition.
|
||||
* @param contentDescription A description of the performed action.
|
||||
*/
|
||||
private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
if (mCurrentAxis == fromAxis) {
|
||||
setCurrentAxis(toAxis, sendEvent, contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the document along the current navigation axis.
|
||||
*
|
||||
* @param direction The direction of traversal.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
* @param contentDescription A description of the performed action.
|
||||
* @see #setCurrentAxis(int, boolean, String)
|
||||
*/
|
||||
private boolean traverseCurrentAxis(int direction, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the document along the given navigation axis.
|
||||
*
|
||||
* @param direction The direction of traversal.
|
||||
* @param axis The axis along which to traverse.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
* @param contentDescription A description of the performed action.
|
||||
*/
|
||||
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
WebViewCore webViewCore = mWebView.getWebViewCore();
|
||||
if (webViewCore == null) {
|
||||
// Allow the page to opt out of Accessibility script injection.
|
||||
if (getAxsUrlParameterValue(url) == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessibilityEvent event = null;
|
||||
if (sendEvent) {
|
||||
event = getPartialyPopulatedAccessibilityEvent();
|
||||
// the text will be set upon receiving the selection string
|
||||
event.setContentDescription(contentDescription);
|
||||
}
|
||||
mScheduledEventStack.push(event);
|
||||
|
||||
// if the axis is the default let WebView handle the event which will
|
||||
// result in cursor ring movement and selection of its content
|
||||
if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
|
||||
// The user must explicitly enable Accessibility script injection.
|
||||
if (!isScriptInjectionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the <code>selectionString</code> has changed.
|
||||
* @return {@code true} if the user has explicitly enabled Accessibility
|
||||
* script injection.
|
||||
*/
|
||||
public void onSelectionStringChange(String selectionString) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Selection string: " + selectionString);
|
||||
}
|
||||
mIsLastSelectionStringNull = (selectionString == null);
|
||||
if (mScheduledEventStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = mScheduledEventStack.pop();
|
||||
if (event != null) {
|
||||
event.getText().add(selectionString);
|
||||
sendAccessibilityEvent(event);
|
||||
}
|
||||
private boolean isScriptInjectionEnabled() {
|
||||
final int injectionSetting = Settings.Secure.getInt(
|
||||
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0);
|
||||
return (injectionSetting == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an {@link AccessibilityEvent}.
|
||||
* Attempts to initialize and add interfaces for TTS, if that hasn't already
|
||||
* been done.
|
||||
*/
|
||||
private void addTtsApis() {
|
||||
if (mTextToSpeech != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String pkgName = mContext.getPackageName();
|
||||
|
||||
mTextToSpeech = new TextToSpeech(mContext, null, null, pkgName + ".**webview**", true);
|
||||
mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to shutdown and remove interfaces for TTS, if that hasn't
|
||||
* already been done.
|
||||
*/
|
||||
private void removeTtsApis() {
|
||||
if (mTextToSpeech == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWebView.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
|
||||
mTextToSpeech.stop();
|
||||
mTextToSpeech.shutdown();
|
||||
mTextToSpeech = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script injection preference requested by the URL, or
|
||||
* {@link #ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED} if the page has no
|
||||
* preference.
|
||||
*
|
||||
* @param event The event to send.
|
||||
* @param url The URL to check.
|
||||
* @return A script injection preference.
|
||||
*/
|
||||
private void sendAccessibilityEvent(AccessibilityEvent event) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Dispatching: " + event);
|
||||
}
|
||||
// accessibility may be disabled while waiting for the selection string
|
||||
AccessibilityManager accessibilityManager =
|
||||
AccessibilityManager.getInstance(mWebView.getContext());
|
||||
if (accessibilityManager.isEnabled()) {
|
||||
accessibilityManager.sendAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An accessibility event whose members are populated except its
|
||||
* text and content description.
|
||||
*/
|
||||
private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
|
||||
event.setClassName(mWebView.getClass().getName());
|
||||
event.setPackageName(mWebView.getContext().getPackageName());
|
||||
event.setEnabled(mWebView.getWebView().isEnabled());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the Web content key bindings are loaded.
|
||||
*/
|
||||
private void ensureWebContentKeyBindings() {
|
||||
if (sBindings.size() > 0) {
|
||||
return;
|
||||
private int getAxsUrlParameterValue(String url) {
|
||||
if (url == null) {
|
||||
return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
|
||||
}
|
||||
|
||||
String webContentKeyBindingsString = Settings.Secure.getString(
|
||||
mWebView.getContext().getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
|
||||
try {
|
||||
final List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), null);
|
||||
|
||||
SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
|
||||
semiColonSplitter.setString(webContentKeyBindingsString);
|
||||
|
||||
while (semiColonSplitter.hasNext()) {
|
||||
String bindingString = semiColonSplitter.next();
|
||||
if (TextUtils.isEmpty(bindingString)) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
|
||||
+ webContentKeyBindingsString);
|
||||
continue;
|
||||
}
|
||||
String[] keyValueArray = bindingString.split("=");
|
||||
if (keyValueArray.length != 2) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
|
||||
String[] actionStrings = keyValueArray[1].split(":");
|
||||
int[] actions = new int[actionStrings.length];
|
||||
for (int i = 0, count = actions.length; i < count; i++) {
|
||||
actions[i] = Integer.decode(actionStrings[i].trim());
|
||||
for (NameValuePair param : params) {
|
||||
if ("axs".equals(param.getName())) {
|
||||
return verifyInjectionValue(param.getValue());
|
||||
}
|
||||
sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
|
||||
}
|
||||
|
||||
private boolean isEnterActionKey(int keyCode) {
|
||||
return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_ENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
|
||||
private int verifyInjectionValue(String value) {
|
||||
try {
|
||||
final int parsed = Integer.parseInt(value);
|
||||
|
||||
switch (parsed) {
|
||||
case ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT:
|
||||
return ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT;
|
||||
case ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED:
|
||||
return ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a web content key-binding.
|
||||
* @return The URL for injecting the screen reader.
|
||||
*/
|
||||
private static final class AccessibilityWebContentKeyBinding {
|
||||
private String getScreenReaderInjectionUrl() {
|
||||
final String screenReaderUrl = Settings.Secure.getString(
|
||||
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
|
||||
return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
|
||||
}
|
||||
|
||||
private static final int MODIFIERS_OFFSET = 32;
|
||||
private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
|
||||
/**
|
||||
* @return {@code true} if JavaScript is enabled in the {@link WebView}
|
||||
* settings.
|
||||
*/
|
||||
private boolean isJavaScriptEnabled() {
|
||||
return mWebView.getSettings().getJavaScriptEnabled();
|
||||
}
|
||||
|
||||
private static final int KEY_CODE_OFFSET = 0;
|
||||
private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
|
||||
|
||||
private static final int ACTION_OFFSET = 24;
|
||||
private static final int ACTION_MASK = 0xFF000000;
|
||||
|
||||
private static final int FIRST_ARGUMENT_OFFSET = 16;
|
||||
private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
|
||||
|
||||
private static final int SECOND_ARGUMENT_OFFSET = 8;
|
||||
private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
|
||||
|
||||
private static final int THIRD_ARGUMENT_OFFSET = 0;
|
||||
private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
|
||||
|
||||
private final long mKeyCodeAndModifiers;
|
||||
|
||||
private final int [] mActionSequence;
|
||||
|
||||
/**
|
||||
* @return The key code of the binding key.
|
||||
*/
|
||||
public int getKeyCode() {
|
||||
return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The meta state of the binding key.
|
||||
*/
|
||||
public int getModifiers() {
|
||||
return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of actions in the key binding.
|
||||
*/
|
||||
public int getActionCount() {
|
||||
return mActionSequence.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The action for a given action <code>index</code>.
|
||||
*/
|
||||
public int getAction(int index) {
|
||||
return mActionSequence[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The action code for a given action <code>index</code>.
|
||||
*/
|
||||
public int getActionCode(int index) {
|
||||
return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The first argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getFirstArgument(int index) {
|
||||
return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The second argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getSecondArgument(int index) {
|
||||
return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The third argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getThirdArgument(int index) {
|
||||
return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param keyCodeAndModifiers The key for the binding (key and modifiers).
|
||||
* @param actionSequence The sequence of action for the binding.
|
||||
*/
|
||||
public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
|
||||
mKeyCodeAndModifiers = keyCodeAndModifiers;
|
||||
mActionSequence = actionSequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("modifiers: ");
|
||||
builder.append(getModifiers());
|
||||
builder.append(", keyCode: ");
|
||||
builder.append(getKeyCode());
|
||||
builder.append(", actions[");
|
||||
for (int i = 0, count = getActionCount(); i < count; i++) {
|
||||
builder.append("{actionCode");
|
||||
builder.append(i);
|
||||
builder.append(": ");
|
||||
builder.append(getActionCode(i));
|
||||
builder.append(", firstArgument: ");
|
||||
builder.append(getFirstArgument(i));
|
||||
builder.append(", secondArgument: ");
|
||||
builder.append(getSecondArgument(i));
|
||||
builder.append(", thirdArgument: ");
|
||||
builder.append(getThirdArgument(i));
|
||||
builder.append("}");
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
/**
|
||||
* @return {@code true} if accessibility is enabled.
|
||||
*/
|
||||
private boolean isAccessibilityEnabled() {
|
||||
return mAccessibilityManager.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
499
core/java/android/webkit/AccessibilityInjectorFallback.java
Normal file
499
core/java/android/webkit/AccessibilityInjectorFallback.java
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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 android.webkit;
|
||||
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextUtils.SimpleStringSplitter;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.webkit.WebViewCore.EventHub;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* This class injects accessibility into WebViews with disabled JavaScript or
|
||||
* WebViews with enabled JavaScript but for which we have no accessibility
|
||||
* script to inject.
|
||||
* </p>
|
||||
* Note: To avoid changes in the framework upon changing the available
|
||||
* navigation axis, or reordering the navigation axis, or changing
|
||||
* the key bindings, or defining sequence of actions to be bound to
|
||||
* a given key this class is navigation axis agnostic. It is only
|
||||
* aware of one navigation axis which is in fact the default behavior
|
||||
* of webViews while using the DPAD/TrackBall.
|
||||
* </p>
|
||||
* In general a key binding is a mapping from modifiers + key code to
|
||||
* a sequence of actions. For more detail how to specify key bindings refer to
|
||||
* {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
|
||||
* </p>
|
||||
* The possible actions are invocations to
|
||||
* {@link #setCurrentAxis(int, boolean, String)}, or
|
||||
* {@link #traverseCurrentAxis(int, boolean, String)}
|
||||
* {@link #traverseGivenAxis(int, int, boolean, String)}
|
||||
* {@link #prefromAxisTransition(int, int, boolean, String)}
|
||||
* referred via the values of:
|
||||
* {@link #ACTION_SET_CURRENT_AXIS},
|
||||
* {@link #ACTION_TRAVERSE_CURRENT_AXIS},
|
||||
* {@link #ACTION_TRAVERSE_GIVEN_AXIS},
|
||||
* {@link #ACTION_PERFORM_AXIS_TRANSITION},
|
||||
* respectively.
|
||||
* The arguments for the action invocation are specified as offset
|
||||
* hexademical pairs. Note the last argument of the invocation
|
||||
* should NOT be specified in the binding as it is provided by
|
||||
* this class. For details about the key binding implementation
|
||||
* refer to {@link AccessibilityWebContentKeyBinding}.
|
||||
*/
|
||||
class AccessibilityInjectorFallback {
|
||||
private static final String LOG_TAG = "AccessibilityInjector";
|
||||
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private static final int ACTION_SET_CURRENT_AXIS = 0;
|
||||
private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
|
||||
private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
|
||||
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
|
||||
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
|
||||
|
||||
// the default WebView behavior abstracted as a navigation axis
|
||||
private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
|
||||
|
||||
// these are the same for all instances so make them process wide
|
||||
private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
|
||||
new ArrayList<AccessibilityWebContentKeyBinding>();
|
||||
|
||||
// handle to the WebViewClassic this injector is associated with.
|
||||
private final WebViewClassic mWebView;
|
||||
|
||||
// events scheduled for sending as soon as we receive the selected text
|
||||
private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
|
||||
|
||||
// the current traversal axis
|
||||
private int mCurrentAxis = 2; // sentence
|
||||
|
||||
// we need to consume the up if we have handled the last down
|
||||
private boolean mLastDownEventHandled;
|
||||
|
||||
// getting two empty selection strings in a row we let the WebView handle the event
|
||||
private boolean mIsLastSelectionStringNull;
|
||||
|
||||
// keep track of last direction
|
||||
private int mLastDirection;
|
||||
|
||||
/**
|
||||
* Creates a new injector associated with a given {@link WebViewClassic}.
|
||||
*
|
||||
* @param webView The associated WebViewClassic.
|
||||
*/
|
||||
public AccessibilityInjectorFallback(WebViewClassic webView) {
|
||||
mWebView = webView;
|
||||
ensureWebContentKeyBindings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a key down <code>event</code>.
|
||||
*
|
||||
* @return True if the event was processed.
|
||||
*/
|
||||
public boolean onKeyEvent(KeyEvent event) {
|
||||
// We do not handle ENTER in any circumstances.
|
||||
if (isEnterActionKey(event.getKeyCode())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
return mLastDownEventHandled;
|
||||
}
|
||||
|
||||
mLastDownEventHandled = false;
|
||||
|
||||
AccessibilityWebContentKeyBinding binding = null;
|
||||
for (AccessibilityWebContentKeyBinding candidate : sBindings) {
|
||||
if (event.getKeyCode() == candidate.getKeyCode()
|
||||
&& event.hasModifiers(candidate.getModifiers())) {
|
||||
binding = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (binding == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0, count = binding.getActionCount(); i < count; i++) {
|
||||
int actionCode = binding.getActionCode(i);
|
||||
String contentDescription = Integer.toHexString(binding.getAction(i));
|
||||
switch (actionCode) {
|
||||
case ACTION_SET_CURRENT_AXIS:
|
||||
int axis = binding.getFirstArgument(i);
|
||||
boolean sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
setCurrentAxis(axis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_TRAVERSE_CURRENT_AXIS:
|
||||
int direction = binding.getFirstArgument(i);
|
||||
// on second null selection string in same direction - WebView handles the event
|
||||
if (direction == mLastDirection && mIsLastSelectionStringNull) {
|
||||
mIsLastSelectionStringNull = false;
|
||||
return false;
|
||||
}
|
||||
mLastDirection = direction;
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
|
||||
contentDescription);
|
||||
break;
|
||||
case ACTION_TRAVERSE_GIVEN_AXIS:
|
||||
direction = binding.getFirstArgument(i);
|
||||
// on second null selection string in same direction => WebView handle the event
|
||||
if (direction == mLastDirection && mIsLastSelectionStringNull) {
|
||||
mIsLastSelectionStringNull = false;
|
||||
return false;
|
||||
}
|
||||
mLastDirection = direction;
|
||||
axis = binding.getSecondArgument(i);
|
||||
sendEvent = (binding.getThirdArgument(i) == 1);
|
||||
traverseGivenAxis(direction, axis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_PERFORM_AXIS_TRANSITION:
|
||||
int fromAxis = binding.getFirstArgument(i);
|
||||
int toAxis = binding.getSecondArgument(i);
|
||||
sendEvent = (binding.getThirdArgument(i) == 1);
|
||||
prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
|
||||
mLastDownEventHandled = true;
|
||||
break;
|
||||
case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
|
||||
// This is a special case since we treat the default WebView navigation
|
||||
// behavior as one of the possible navigation axis the user can use.
|
||||
// If we are not on the default WebView navigation axis this is NOP.
|
||||
if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
|
||||
// While WebVew handles navigation we do not get null selection
|
||||
// strings so do not check for that here as the cases above.
|
||||
mLastDirection = binding.getFirstArgument(i);
|
||||
sendEvent = (binding.getSecondArgument(i) == 1);
|
||||
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
|
||||
sendEvent, contentDescription);
|
||||
mLastDownEventHandled = false;
|
||||
} else {
|
||||
mLastDownEventHandled = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.w(LOG_TAG, "Unknown action code: " + actionCode);
|
||||
}
|
||||
}
|
||||
|
||||
return mLastDownEventHandled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current navigation axis which will be used while
|
||||
* calling {@link #traverseCurrentAxis(int, boolean, String)}.
|
||||
*
|
||||
* @param axis The axis to set.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
*/
|
||||
private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
|
||||
mCurrentAxis = axis;
|
||||
if (sendEvent) {
|
||||
AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
|
||||
event.getText().add(String.valueOf(axis));
|
||||
event.setContentDescription(contentDescription);
|
||||
sendAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs conditional transition one axis to another.
|
||||
*
|
||||
* @param fromAxis The axis which must be the current for the transition to occur.
|
||||
* @param toAxis The axis to which to transition.
|
||||
* @param sendEvent Flag if to send an event to announce successful transition.
|
||||
* @param contentDescription A description of the performed action.
|
||||
*/
|
||||
private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
if (mCurrentAxis == fromAxis) {
|
||||
setCurrentAxis(toAxis, sendEvent, contentDescription);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the document along the current navigation axis.
|
||||
*
|
||||
* @param direction The direction of traversal.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
* @param contentDescription A description of the performed action.
|
||||
* @see #setCurrentAxis(int, boolean, String)
|
||||
*/
|
||||
private boolean traverseCurrentAxis(int direction, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the document along the given navigation axis.
|
||||
*
|
||||
* @param direction The direction of traversal.
|
||||
* @param axis The axis along which to traverse.
|
||||
* @param sendEvent Whether to send an accessibility event to
|
||||
* announce the change.
|
||||
* @param contentDescription A description of the performed action.
|
||||
*/
|
||||
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
|
||||
String contentDescription) {
|
||||
WebViewCore webViewCore = mWebView.getWebViewCore();
|
||||
if (webViewCore == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessibilityEvent event = null;
|
||||
if (sendEvent) {
|
||||
event = getPartialyPopulatedAccessibilityEvent();
|
||||
// the text will be set upon receiving the selection string
|
||||
event.setContentDescription(contentDescription);
|
||||
}
|
||||
mScheduledEventStack.push(event);
|
||||
|
||||
// if the axis is the default let WebView handle the event which will
|
||||
// result in cursor ring movement and selection of its content
|
||||
if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the <code>selectionString</code> has changed.
|
||||
*/
|
||||
public void onSelectionStringChange(String selectionString) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Selection string: " + selectionString);
|
||||
}
|
||||
mIsLastSelectionStringNull = (selectionString == null);
|
||||
if (mScheduledEventStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AccessibilityEvent event = mScheduledEventStack.pop();
|
||||
if (event != null) {
|
||||
event.getText().add(selectionString);
|
||||
sendAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an {@link AccessibilityEvent}.
|
||||
*
|
||||
* @param event The event to send.
|
||||
*/
|
||||
private void sendAccessibilityEvent(AccessibilityEvent event) {
|
||||
if (DEBUG) {
|
||||
Log.d(LOG_TAG, "Dispatching: " + event);
|
||||
}
|
||||
// accessibility may be disabled while waiting for the selection string
|
||||
AccessibilityManager accessibilityManager =
|
||||
AccessibilityManager.getInstance(mWebView.getContext());
|
||||
if (accessibilityManager.isEnabled()) {
|
||||
accessibilityManager.sendAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An accessibility event whose members are populated except its
|
||||
* text and content description.
|
||||
*/
|
||||
private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
|
||||
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
|
||||
event.setClassName(mWebView.getClass().getName());
|
||||
event.setPackageName(mWebView.getContext().getPackageName());
|
||||
event.setEnabled(mWebView.getWebView().isEnabled());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the Web content key bindings are loaded.
|
||||
*/
|
||||
private void ensureWebContentKeyBindings() {
|
||||
if (sBindings.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String webContentKeyBindingsString = Settings.Secure.getString(
|
||||
mWebView.getContext().getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
|
||||
|
||||
SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
|
||||
semiColonSplitter.setString(webContentKeyBindingsString);
|
||||
|
||||
while (semiColonSplitter.hasNext()) {
|
||||
String bindingString = semiColonSplitter.next();
|
||||
if (TextUtils.isEmpty(bindingString)) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
|
||||
+ webContentKeyBindingsString);
|
||||
continue;
|
||||
}
|
||||
String[] keyValueArray = bindingString.split("=");
|
||||
if (keyValueArray.length != 2) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
|
||||
String[] actionStrings = keyValueArray[1].split(":");
|
||||
int[] actions = new int[actionStrings.length];
|
||||
for (int i = 0, count = actions.length; i < count; i++) {
|
||||
actions[i] = Integer.decode(actionStrings[i].trim());
|
||||
}
|
||||
sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnterActionKey(int keyCode) {
|
||||
return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_ENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a web content key-binding.
|
||||
*/
|
||||
private static final class AccessibilityWebContentKeyBinding {
|
||||
|
||||
private static final int MODIFIERS_OFFSET = 32;
|
||||
private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
|
||||
|
||||
private static final int KEY_CODE_OFFSET = 0;
|
||||
private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
|
||||
|
||||
private static final int ACTION_OFFSET = 24;
|
||||
private static final int ACTION_MASK = 0xFF000000;
|
||||
|
||||
private static final int FIRST_ARGUMENT_OFFSET = 16;
|
||||
private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
|
||||
|
||||
private static final int SECOND_ARGUMENT_OFFSET = 8;
|
||||
private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
|
||||
|
||||
private static final int THIRD_ARGUMENT_OFFSET = 0;
|
||||
private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
|
||||
|
||||
private final long mKeyCodeAndModifiers;
|
||||
|
||||
private final int [] mActionSequence;
|
||||
|
||||
/**
|
||||
* @return The key code of the binding key.
|
||||
*/
|
||||
public int getKeyCode() {
|
||||
return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The meta state of the binding key.
|
||||
*/
|
||||
public int getModifiers() {
|
||||
return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of actions in the key binding.
|
||||
*/
|
||||
public int getActionCount() {
|
||||
return mActionSequence.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The action for a given action <code>index</code>.
|
||||
*/
|
||||
public int getAction(int index) {
|
||||
return mActionSequence[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The action code for a given action <code>index</code>.
|
||||
*/
|
||||
public int getActionCode(int index) {
|
||||
return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The first argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getFirstArgument(int index) {
|
||||
return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The second argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getSecondArgument(int index) {
|
||||
return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The third argument for a given action <code>index</code>.
|
||||
*/
|
||||
public int getThirdArgument(int index) {
|
||||
return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param keyCodeAndModifiers The key for the binding (key and modifiers).
|
||||
* @param actionSequence The sequence of action for the binding.
|
||||
*/
|
||||
public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
|
||||
mKeyCodeAndModifiers = keyCodeAndModifiers;
|
||||
mActionSequence = actionSequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("modifiers: ");
|
||||
builder.append(getModifiers());
|
||||
builder.append(", keyCode: ");
|
||||
builder.append(getKeyCode());
|
||||
builder.append(", actions[");
|
||||
for (int i = 0, count = getActionCount(); i < count; i++) {
|
||||
builder.append("{actionCode");
|
||||
builder.append(i);
|
||||
builder.append(": ");
|
||||
builder.append(getActionCode(i));
|
||||
builder.append(", firstArgument: ");
|
||||
builder.append(getFirstArgument(i));
|
||||
builder.append(", secondArgument: ");
|
||||
builder.append(getSecondArgument(i));
|
||||
builder.append(", thirdArgument: ");
|
||||
builder.append(getThirdArgument(i));
|
||||
builder.append("}");
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,9 +60,7 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.security.KeyChain;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Selection;
|
||||
@@ -871,15 +869,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
private static final int MOTIONLESS_IGNORE = 3;
|
||||
private int mHeldMotionless;
|
||||
|
||||
// An instance for injecting accessibility in WebViews with disabled
|
||||
// JavaScript or ones for which no accessibility script exists
|
||||
// Lazily-instantiated instance for injecting accessibility.
|
||||
private AccessibilityInjector mAccessibilityInjector;
|
||||
|
||||
// flag indicating if accessibility script is injected so we
|
||||
// know to handle Shift and arrows natively first
|
||||
private boolean mAccessibilityScriptInjected;
|
||||
|
||||
|
||||
/**
|
||||
* How long the caret handle will last without being touched.
|
||||
*/
|
||||
@@ -1088,34 +1080,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
|
||||
private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
|
||||
|
||||
// constants for determining script injection strategy
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
|
||||
private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
|
||||
|
||||
// the alias via which accessibility JavaScript interface is exposed
|
||||
private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
|
||||
|
||||
// Template for JavaScript that injects a screen-reader.
|
||||
private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
|
||||
"javascript:(function() {" +
|
||||
" var chooser = document.createElement('script');" +
|
||||
" chooser.type = 'text/javascript';" +
|
||||
" chooser.src = '%1s';" +
|
||||
" document.getElementsByTagName('head')[0].appendChild(chooser);" +
|
||||
" })();";
|
||||
|
||||
// Regular expression that matches the "axs" URL parameter.
|
||||
// The value of 0 means the accessibility script is opted out
|
||||
// The value of 1 means the accessibility script is already injected
|
||||
private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
|
||||
|
||||
// TextToSpeech instance exposed to JavaScript to the injected screenreader.
|
||||
private TextToSpeech mTextToSpeech;
|
||||
|
||||
// variable to cache the above pattern in case accessibility is enabled.
|
||||
private Pattern mMatchAxsUrlParameterPattern;
|
||||
|
||||
/**
|
||||
* Max distance to overscroll by in pixels.
|
||||
* This how far content can be pulled beyond its normal bounds by the user.
|
||||
@@ -1634,40 +1598,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds accessibility APIs to JavaScript.
|
||||
*
|
||||
* Note: This method is responsible to performing the necessary
|
||||
* check if the accessibility APIs should be exposed.
|
||||
*/
|
||||
private void addAccessibilityApisToJavaScript() {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()
|
||||
&& getSettings().getJavaScriptEnabled()) {
|
||||
// exposing the TTS for now ...
|
||||
final Context ctx = mContext;
|
||||
if (ctx != null) {
|
||||
final String packageName = ctx.getPackageName();
|
||||
if (packageName != null) {
|
||||
mTextToSpeech = new TextToSpeech(ctx, null, null,
|
||||
packageName + ".**webview**", true);
|
||||
addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accessibility APIs from JavaScript.
|
||||
*/
|
||||
private void removeAccessibilityApisFromJavaScript() {
|
||||
// exposing the TTS for now ...
|
||||
if (mTextToSpeech != null) {
|
||||
removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
|
||||
mTextToSpeech.shutdown();
|
||||
mTextToSpeech = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||
info.setScrollable(isScrollableForAccessibility());
|
||||
@@ -1688,6 +1618,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
|
||||
}
|
||||
|
||||
private boolean isAccessibilityEnabled() {
|
||||
return AccessibilityManager.getInstance(mContext).isEnabled();
|
||||
}
|
||||
|
||||
private AccessibilityInjector getAccessibilityInjector() {
|
||||
if (mAccessibilityInjector == null) {
|
||||
mAccessibilityInjector = new AccessibilityInjector(this);
|
||||
}
|
||||
return mAccessibilityInjector;
|
||||
}
|
||||
|
||||
private boolean isScrollableForAccessibility() {
|
||||
return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
|
||||
- mWebView.getPaddingRight()
|
||||
@@ -3823,7 +3764,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
|
||||
// reset the flag since we set to true in if need after
|
||||
// loading is see onPageFinished(Url)
|
||||
mAccessibilityScriptInjected = false;
|
||||
if (isAccessibilityEnabled()) {
|
||||
getAccessibilityInjector().onPageStarted(url);
|
||||
}
|
||||
|
||||
// Don't start out editing.
|
||||
mIsEditingText = false;
|
||||
@@ -3835,114 +3778,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
*/
|
||||
/* package */ void onPageFinished(String url) {
|
||||
mZoomManager.onPageFinished(url);
|
||||
injectAccessibilityForUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method injects accessibility in the loaded document if accessibility
|
||||
* is enabled. If JavaScript is enabled we try to inject a URL specific script.
|
||||
* If no URL specific script is found or JavaScript is disabled we fallback to
|
||||
* the default {@link AccessibilityInjector} implementation.
|
||||
* </p>
|
||||
* If the URL has the "axs" paramter set to 1 it has already done the
|
||||
* script injection so we do nothing. If the parameter is set to 0
|
||||
* the URL opts out accessibility script injection so we fall back to
|
||||
* the default {@link AccessibilityInjector}.
|
||||
* </p>
|
||||
* Note: If the user has not opted-in the accessibility script injection no scripts
|
||||
* are injected rather the default {@link AccessibilityInjector} implementation
|
||||
* is used.
|
||||
*
|
||||
* @param url The URL loaded by this {@link WebView}.
|
||||
*/
|
||||
private void injectAccessibilityForUrl(String url) {
|
||||
if (mWebViewCore == null) {
|
||||
return;
|
||||
if (isAccessibilityEnabled()) {
|
||||
getAccessibilityInjector().onPageFinished(url);
|
||||
}
|
||||
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
|
||||
|
||||
if (!accessibilityManager.isEnabled()) {
|
||||
// it is possible that accessibility was turned off between reloads
|
||||
ensureAccessibilityScriptInjectorInstance(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getSettings().getJavaScriptEnabled()) {
|
||||
// no JS so we fallback to the basic buil-in support
|
||||
ensureAccessibilityScriptInjectorInstance(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// check the URL "axs" parameter to choose appropriate action
|
||||
int axsParameterValue = getAxsUrlParameterValue(url);
|
||||
if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
|
||||
boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
|
||||
.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
|
||||
if (onDeviceScriptInjectionEnabled) {
|
||||
ensureAccessibilityScriptInjectorInstance(false);
|
||||
// neither script injected nor script injection opted out => we inject
|
||||
mWebView.loadUrl(getScreenReaderInjectingJs());
|
||||
// TODO: Set this flag after successfull script injection. Maybe upon injection
|
||||
// the chooser should update the meta tag and we check it to declare success
|
||||
mAccessibilityScriptInjected = true;
|
||||
} else {
|
||||
// injection disabled so we fallback to the basic built-in support
|
||||
ensureAccessibilityScriptInjectorInstance(true);
|
||||
}
|
||||
} else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
|
||||
// injection opted out so we fallback to the basic buil-in support
|
||||
ensureAccessibilityScriptInjectorInstance(true);
|
||||
} else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
|
||||
ensureAccessibilityScriptInjectorInstance(false);
|
||||
// the URL provides accessibility but we still need to add our generic script
|
||||
mWebView.loadUrl(getScreenReaderInjectingJs());
|
||||
} else {
|
||||
Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
|
||||
*
|
||||
* @param present True to ensure an insance, false to ensure no instance.
|
||||
*/
|
||||
private void ensureAccessibilityScriptInjectorInstance(boolean present) {
|
||||
if (present) {
|
||||
if (mAccessibilityInjector == null) {
|
||||
mAccessibilityInjector = new AccessibilityInjector(this);
|
||||
}
|
||||
} else {
|
||||
mAccessibilityInjector = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets JavaScript that injects a screen-reader.
|
||||
*
|
||||
* @return The JavaScript snippet.
|
||||
*/
|
||||
private String getScreenReaderInjectingJs() {
|
||||
String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
|
||||
return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the "axs" URL parameter value.
|
||||
*
|
||||
* @param url A url to fetch the paramter from.
|
||||
* @return The parameter value if such, -1 otherwise.
|
||||
*/
|
||||
private int getAxsUrlParameterValue(String url) {
|
||||
if (mMatchAxsUrlParameterPattern == null) {
|
||||
mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
|
||||
}
|
||||
Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
|
||||
if (matcher.find()) {
|
||||
String keyValuePair = url.substring(matcher.start(), matcher.end());
|
||||
return Integer.parseInt(keyValuePair.split("=")[1]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// scale from content to view coordinates, and pin
|
||||
@@ -4900,30 +4739,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
return false;
|
||||
}
|
||||
|
||||
// accessibility support
|
||||
if (accessibilityScriptInjected()) {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
|
||||
// if an accessibility script is injected we delegate to it the key handling.
|
||||
// this script is a screen reader which is a fully fledged solution for blind
|
||||
// users to navigate in and interact with web pages.
|
||||
sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
|
||||
return true;
|
||||
} else {
|
||||
// Clean up if accessibility was disabled after loading the current URL.
|
||||
mAccessibilityScriptInjected = false;
|
||||
}
|
||||
} else if (mAccessibilityInjector != null) {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
|
||||
if (mAccessibilityInjector.onKeyEvent(event)) {
|
||||
// if an accessibility injector is present (no JavaScript enabled or the site
|
||||
// opts out injecting our JavaScript screen reader) we let it decide whether
|
||||
// to act on and consume the event.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Clean up if accessibility was disabled after loading the current URL.
|
||||
mAccessibilityInjector = null;
|
||||
}
|
||||
// See if the accessibility injector needs to handle this event.
|
||||
if (isAccessibilityEnabled()
|
||||
&& getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
|
||||
@@ -5027,30 +4846,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
return false;
|
||||
}
|
||||
|
||||
// accessibility support
|
||||
if (accessibilityScriptInjected()) {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
|
||||
// if an accessibility script is injected we delegate to it the key handling.
|
||||
// this script is a screen reader which is a fully fledged solution for blind
|
||||
// users to navigate in and interact with web pages.
|
||||
sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
|
||||
return true;
|
||||
} else {
|
||||
// Clean up if accessibility was disabled after loading the current URL.
|
||||
mAccessibilityScriptInjected = false;
|
||||
}
|
||||
} else if (mAccessibilityInjector != null) {
|
||||
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
|
||||
if (mAccessibilityInjector.onKeyEvent(event)) {
|
||||
// if an accessibility injector is present (no JavaScript enabled or the site
|
||||
// opts out injecting our JavaScript screen reader) we let it decide whether to
|
||||
// act on and consume the event.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Clean up if accessibility was disabled after loading the current URL.
|
||||
mAccessibilityInjector = null;
|
||||
}
|
||||
// See if the accessibility injector needs to handle this event.
|
||||
if (isAccessibilityEnabled()
|
||||
&& getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isEnterActionKey(keyCode)) {
|
||||
@@ -5351,7 +5150,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
public void onAttachedToWindow() {
|
||||
if (mWebView.hasWindowFocus()) setActive(true);
|
||||
|
||||
addAccessibilityApisToJavaScript();
|
||||
if (isAccessibilityEnabled()) {
|
||||
getAccessibilityInjector().addAccessibilityApisIfNecessary();
|
||||
}
|
||||
|
||||
updateHwAccelerated();
|
||||
}
|
||||
@@ -5362,7 +5163,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
mZoomManager.dismissZoomPicker();
|
||||
if (mWebView.hasWindowFocus()) setActive(false);
|
||||
|
||||
removeAccessibilityApisFromJavaScript();
|
||||
if (isAccessibilityEnabled()) {
|
||||
getAccessibilityInjector().removeAccessibilityApisIfNecessary();
|
||||
} else {
|
||||
// Ensure the injector is cleared if we're detaching from the window
|
||||
// and accessibility is disabled.
|
||||
mAccessibilityInjector = null;
|
||||
}
|
||||
|
||||
updateHwAccelerated();
|
||||
|
||||
if (mWebView.isHardwareAccelerated()) {
|
||||
@@ -7414,9 +7222,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
break;
|
||||
|
||||
case SELECTION_STRING_CHANGED:
|
||||
if (mAccessibilityInjector != null) {
|
||||
String selectionString = (String) msg.obj;
|
||||
mAccessibilityInjector.onSelectionStringChange(selectionString);
|
||||
if (isAccessibilityEnabled()) {
|
||||
getAccessibilityInjector()
|
||||
.handleSelectionChangedIfNecessary((String) msg.obj);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -7975,7 +7783,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
mIsBatchingTextChanges = false;
|
||||
}
|
||||
|
||||
private void sendBatchableInputMessage(int what, int arg1, int arg2,
|
||||
void sendBatchableInputMessage(int what, int arg1, int arg2,
|
||||
Object obj) {
|
||||
if (mWebViewCore == null) {
|
||||
return;
|
||||
@@ -8395,16 +8203,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|
||||
sendBatchableInputMessage(eventHubAction, direction, 0, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether accessibility script has been injected.
|
||||
*/
|
||||
private boolean accessibilityScriptInjected() {
|
||||
// TODO: Maybe the injected script should announce its presence in
|
||||
// the page meta-tag so the nativePageShouldHandleShiftAndArrows
|
||||
// will check that as one of the conditions it looks for
|
||||
return mAccessibilityScriptInjected;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link WebView#setBackgroundColor(int)}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user