From 525823a7548d73c9ec9ba3ae84663ab286b20205 Mon Sep 17 00:00:00 2001
From: alanv
Date: Wed, 16 May 2012 20:01:51 -0700
Subject: [PATCH] Refactor WebView accessibility code into a separate class.
Bug: 5932640
Change-Id: I52ab57f51c5904cbcf56307f17f406b2a30d6b91
---
.../android/webkit/AccessibilityInjector.java | 683 +++++++-----------
.../webkit/AccessibilityInjectorFallback.java | 499 +++++++++++++
core/java/android/webkit/WebViewClassic.java | 282 +-------
3 files changed, 806 insertions(+), 658 deletions(-)
create mode 100644 core/java/android/webkit/AccessibilityInjectorFallback.java
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 11bd815c685d5..915e2dc22fb06 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -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.
- *
- * 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.
- *
- * 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}.
- *
- * 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 sBindings =
- new ArrayList();
+ // 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 mScheduledEventStack = new Stack();
+ // 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 event.
- *
- * @return True if the event was processed.
+ * Attempts to load scripting interfaces for accessibility.
+ *
+ * This should be called when the window is attached.
+ *
*/
- 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.
+ *
+ * This should be called when the window is detached.
+ *
+ */
+ 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