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