summaryrefslogtreecommitdiffstats
path: root/content/public/android
diff options
context:
space:
mode:
authordtrainor@chromium.org <dtrainor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-02 19:11:09 +0000
committerdtrainor@chromium.org <dtrainor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-08-02 19:11:09 +0000
commitf6250339e1bd5b6b5c2bbd4a103a5087d28c0284 (patch)
tree1b6c6d534bb86ff364603afd7650dc75ba468e74 /content/public/android
parentcfeb8ee8b397d98a3adad4df410be4e09852b6a1 (diff)
downloadchromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.zip
chromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.tar.gz
chromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.tar.bz2
Start upstreaming accessibility.
BUG=http://crbug.com/138218 Review URL: https://chromiumcodereview.appspot.com/10832104 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149667 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/public/android')
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentView.java105
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java99
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java375
3 files changed, 570 insertions, 9 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentView.java b/content/public/android/java/src/org/chromium/content/browser/ContentView.java
index d8501ec..6e796fa 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentView.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentView.java
@@ -7,13 +7,18 @@ package org.chromium.content.browser;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.DownloadListener;
import android.widget.FrameLayout;
+import org.chromium.content.browser.ContentViewCore;
+
/**
* The containing view for {@link ContentViewCore} that exists in the Android UI hierarchy and
* exposes the various {@link View} functionality to it.
@@ -94,21 +99,55 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
private ContentViewCore mContentViewCore;
- public ContentView(Context context, int nativeWebContents, int personality) {
- this(context, nativeWebContents, null, android.R.attr.webViewStyle, personality);
+ /**
+ * Creates an instance of a ContentView.
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param nativeWebContents A pointer to the native web contents.
+ * @param personality One of {@link #PERSONALITY_CHROME} or {@link #PERSONALITY_VIEW}.
+ * @return A ContentView instance.
+ */
+ public static ContentView newInstance(Context context, int nativeWebContents, int personality) {
+ return newInstance(context, nativeWebContents, null, android.R.attr.webViewStyle,
+ personality);
}
- public ContentView(Context context, int nativeWebContents, AttributeSet attrs) {
+ /**
+ * Creates an instance of a ContentView.
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param nativeWebContents A pointer to the native web contents.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @return A ContentView instance.
+ */
+ public static ContentView newInstance(Context context, int nativeWebContents,
+ AttributeSet attrs) {
// TODO(klobag): use the WebViewStyle as the default style for now. It enables scrollbar.
// When ContentView is moved to framework, we can define its own style in the res.
- this(context, nativeWebContents, attrs, android.R.attr.webViewStyle);
+ return newInstance(context, nativeWebContents, attrs, android.R.attr.webViewStyle);
+ }
+
+ /**
+ * Creates an instance of a ContentView.
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param nativeWebContents A pointer to the native web contents.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyle The default style to apply to this view.
+ * @return A ContentView instance.
+ */
+ public static ContentView newInstance(Context context, int nativeWebContents,
+ AttributeSet attrs, int defStyle) {
+ return newInstance(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW);
}
- public ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle) {
- this(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW);
+ private static ContentView newInstance(Context context, int nativeWebContents,
+ AttributeSet attrs, int defStyle, int personality) {
+ // TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16.
+ return new ContentView(context, nativeWebContents, attrs, defStyle, personality);
}
- private ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle,
+ protected ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle,
int personality) {
super(context, attrs, defStyle);
@@ -320,6 +359,34 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
}
/**
+ * This method should be called when the containing activity is paused.
+ **/
+ public void onActivityPause() {
+ mContentViewCore.onActivityPause();
+ }
+
+ /**
+ * This method should be called when the containing activity is resumed.
+ **/
+ public void onActivityResume() {
+ mContentViewCore.onActivityResume();
+ }
+
+ /**
+ * To be called when the ContentView is shown.
+ **/
+ public void onShow() {
+ mContentViewCore.onShow();
+ }
+
+ /**
+ * To be called when the ContentView is hidden.
+ **/
+ public void onHide() {
+ mContentViewCore.onHide();
+ }
+
+ /**
* Return the ContentSettings object used to control the settings for this
* WebView.
*
@@ -363,6 +430,30 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal
return super.awakenScrollBars();
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ mContentViewCore.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ mContentViewCore.onInitializeAccessibilityEvent(event);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mContentViewCore.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mContentViewCore.onDetachedFromWindow();
+ }
+
void updateMultiTouchZoomSupport() {
mContentViewCore.updateMultiTouchZoomSupport();
}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index 06c035f..f1c48e9 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -7,6 +7,7 @@ package org.chromium.content.browser;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
@@ -14,6 +15,8 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.DownloadListener;
import org.chromium.base.CalledByNative;
@@ -25,6 +28,7 @@ import org.chromium.content.browser.ZoomManager;
import org.chromium.content.common.CleanupReference;
import org.chromium.content.common.TraceEvent;
+import org.chromium.content.browser.accessibility.AccessibilityInjector;
import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
/**
@@ -178,6 +182,9 @@ public class ContentViewCore implements MotionEventDelegate {
// over DownloadListener.
private ContentViewDownloadDelegate mDownloadDelegate;
+ // The AccessibilityInjector that handles loading Accessibility scripts into the web page.
+ private final AccessibilityInjector mAccessibilityInjector;
+
/**
* Enable multi-process ContentView. This should be called by the application before
* constructing any ContentView instances. If enabled, ContentView will run renderers in
@@ -235,6 +242,9 @@ public class ContentViewCore implements MotionEventDelegate {
AndroidBrowserProcess.initContentViewProcess(
context, AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS);
+ mAccessibilityInjector = AccessibilityInjector.newInstance(this);
+ mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
+
initialize(context, nativeWebContents, personality);
}
@@ -368,6 +378,7 @@ public class ContentViewCore implements MotionEventDelegate {
* omnibox can report suggestions correctly.
*/
public void loadUrlWithoutUrlSanitization(String url, int pageTransition) {
+ mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
if (mNativeContentViewCore != 0) {
if (isPersonalityView()) {
nativeLoadUrlWithoutUrlSanitizationWithUserAgentOverride(
@@ -484,6 +495,7 @@ public class ContentViewCore implements MotionEventDelegate {
* Reload the current page.
*/
public void reload() {
+ mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore);
}
@@ -605,19 +617,35 @@ public class ContentViewCore implements MotionEventDelegate {
}
/**
- * This method should be called when the containing activity is paused
+ * This method should be called when the containing activity is paused.
*/
public void onActivityPause() {
TraceEvent.begin();
hidePopupDialog();
+ setAccessibilityState(false);
TraceEvent.end();
}
/**
- * Called when the ContentView is hidden.
+ * This method should be called when the containing activity is resumed.
+ */
+ public void onActivityResume() {
+ setAccessibilityState(true);
+ }
+
+ /**
+ * To be called when the ContentView is shown.
+ */
+ public void onShow() {
+ setAccessibilityState(true);
+ }
+
+ /**
+ * To be called when the ContentView is hidden.
*/
public void onHide() {
hidePopupDialog();
+ setAccessibilityState(false);
}
/**
@@ -645,6 +673,22 @@ public class ContentViewCore implements MotionEventDelegate {
SelectPopupDialog.hide(this);
}
+ /**
+ * @see View#onAttachedToWindow()
+ */
+ @SuppressWarnings("javadoc")
+ protected void onAttachedToWindow() {
+ setAccessibilityState(true);
+ }
+
+ /**
+ * @see View#onDetachedFromWindow()
+ */
+ @SuppressWarnings("javadoc")
+ protected void onDetachedFromWindow() {
+ setAccessibilityState(false);
+ }
+
// End FrameLayout overrides.
/**
@@ -1012,6 +1056,57 @@ public class ContentViewCore implements MotionEventDelegate {
getContentViewClient().onStartContentIntent(getContext(), contentUrl);
}
+ /**
+ * Determines whether or not this ContentViewCore can handle this accessibility action.
+ * @param action The action to perform.
+ * @return Whether or not this action is supported.
+ */
+ public boolean supportsAccessibilityAction(int action) {
+ return mAccessibilityInjector.supportsAccessibilityAction(action);
+ }
+
+ /**
+ * Attempts to perform an accessibility action on the web content. If the accessibility action
+ * cannot be processed, it returns {@code null}, allowing the caller to know to call the
+ * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value.
+ * Otherwise the return value from this method should be used.
+ * @param action The action to perform.
+ * @param arguments Optional action arguments.
+ * @return Whether the action was performed or {@code null} if the call should be delegated to
+ * the super {@link View} class.
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
+ return mAccessibilityInjector.performAccessibilityAction(action, arguments);
+ }
+
+ return false;
+ }
+
+ /**
+ * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
+
+ // TODO(dtrainor): Upstream accessibility scrolling event information once that data is
+ // available in ContentViewCore. Currently internal scrolling variables aren't upstreamed.
+ }
+
+ /**
+ * @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
+ */
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(this.getClass().getName());
+ }
+
+ /**
+ * Enable or disable accessibility features.
+ */
+ public void setAccessibilityState(boolean state) {
+ mAccessibilityInjector.setScriptEnabled(state);
+ }
+
// The following methods are implemented at native side.
/**
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java
new file mode 100644
index 0000000..9127ae4
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java
@@ -0,0 +1,375 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser.accessibility;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.chromium.content.browser.ContentViewCore;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Responsible for accessibility injection and management of a {@link ContentViewCore}.
+ */
+public class AccessibilityInjector {
+ // The ContentView this injector is responsible for managing.
+ protected ContentViewCore mContentViewCore;
+
+ // The Java objects that are exposed to JavaScript
+ private TextToSpeechWrapper mTextToSpeech;
+ private VibratorWrapper mVibrator;
+
+ // Lazily loaded helper objects.
+ private AccessibilityManager mAccessibilityManager;
+
+ // Whether or not we should be injecting the script.
+ protected boolean mInjectedScriptEnabled;
+ protected boolean mScriptInjected;
+
+ // 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;
+ private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
+ private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE_2 = "accessibility2";
+
+ // Template for JavaScript that injects a screen-reader.
+ private static final String ACCESSIBILITY_SCREEN_READER_URL =
+ "https://ssl.gstatic.com/accessibility/javascript/android/chromeandroidvox.js";
+
+ private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
+ "(function() {" +
+ " var chooser = document.createElement('script');" +
+ " chooser.type = 'text/javascript';" +
+ " chooser.src = '%1s';" +
+ " document.getElementsByTagName('head')[0].appendChild(chooser);" +
+ " })();";
+
+ // JavaScript call to turn ChromeVox on or off.
+ private static final String TOGGLE_CHROME_VOX_JAVASCRIPT =
+ "(function() {" +
+ " if (typeof cvox !== 'undefined') {" +
+ " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%1s);" +
+ " }" +
+ " })();";
+
+ /**
+ * Returns an instance of the {@link AccessibilityInjector} based on the SDK version.
+ * @param view The ContentViewCore that this AccessibilityInjector manages.
+ * @return An instance of a {@link AccessibilityInjector}.
+ */
+ public static AccessibilityInjector newInstance(ContentViewCore view) {
+ // TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16.
+ return new AccessibilityInjector(view);
+ }
+
+ /**
+ * Creates an instance of the IceCreamSandwichAccessibilityInjector.
+ * @param view The ContentViewCore that this AccessibilityInjector manages.
+ */
+ protected AccessibilityInjector(ContentViewCore view) {
+ mContentViewCore = view;
+ }
+
+ /**
+ * Injects a <script> tag into the current web site that pulls in the ChromeVox script for
+ * accessibility support. Only injects if accessibility is turned on by
+ * {@link AccessibilityManager#isEnabled()}, accessibility script injection is turned on, and
+ * javascript is enabled on this page.
+ *
+ * @see AccessibilityManager#isEnabled()
+ */
+ public void injectAccessibilityScriptIntoPage() {
+ if (!accessibilityIsAvailable()) return;
+
+ int axsParameterValue = getAxsUrlParameterValue();
+ if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
+ try {
+ Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
+ field.setAccessible(true);
+ String ACCESSIBILITY_SCRIPT_INJECTION = (String) field.get(null);
+
+ boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(
+ mContentViewCore.getContext().getContentResolver(),
+ ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
+ String js = getScreenReaderInjectingJs();
+
+ if (onDeviceScriptInjectionEnabled && js != null && mContentViewCore.isAlive()) {
+ addOrRemoveAccessibilityApisIfNecessary();
+ mContentViewCore.evaluateJavaScript(js);
+ mInjectedScriptEnabled = true;
+ mScriptInjected = true;
+ }
+ } catch (NoSuchFieldException ex) {
+ } catch (IllegalArgumentException ex) {
+ } catch (IllegalAccessException ex) {
+ }
+ }
+ }
+
+ /**
+ * Handles adding or removing accessibility related Java objects ({@link TextToSpeech} and
+ * {@link Vibrator}) interfaces from Javascript. This method should be called at a time when it
+ * is safe to add or remove these interfaces, specifically when the {@link ContentView} is first
+ * initialized or right before the {@link ContentView} is about to navigate to a URL or reload.
+ * <p>
+ * If this method is called at other times, the interfaces might not be correctly removed,
+ * meaning that Javascript can still access these Java objects that may have been already
+ * shut down.
+ */
+ public void addOrRemoveAccessibilityApisIfNecessary() {
+ if (accessibilityIsAvailable()) {
+ addAccessibilityApis();
+ } else {
+ removeAccessibilityApis();
+ }
+ }
+
+ /**
+ * Checks whether or not touch to explore is enabled on the system.
+ */
+ public boolean accessibilityIsAvailable() {
+ return getAccessibilityManager().isEnabled() &&
+ mContentViewCore.getContentSettings() != null &&
+ mContentViewCore.getContentSettings().getJavaScriptEnabled();
+ }
+
+ /**
+ * Sets whether or not the script is enabled. If the script is disabled, we also stop any
+ * we output that is occurring.
+ * @param enabled Whether or not to enable the script.
+ */
+ public void setScriptEnabled(boolean enabled) {
+ if (!accessibilityIsAvailable() || mInjectedScriptEnabled == enabled) return;
+
+ mInjectedScriptEnabled = enabled;
+ if (mContentViewCore.isAlive()) {
+ String js = String.format(TOGGLE_CHROME_VOX_JAVASCRIPT, Boolean.toString(
+ mInjectedScriptEnabled));
+ mContentViewCore.evaluateJavaScript(js);
+
+ if (!mInjectedScriptEnabled) {
+ // Stop any TTS/Vibration right now.
+ onPageLostFocus();
+ }
+ }
+ }
+
+ /**
+ * Notifies this handler that a page load has started, which means we should mark the
+ * accessibility script as not being injected. This way we can properly ignore incoming
+ * accessibility gesture events.
+ */
+ public void onPageLoadStarted() {
+ mScriptInjected = false;
+ }
+
+ /**
+ * Stop any notifications that are currently going on (e.g. Text-to-Speech).
+ */
+ public void onPageLostFocus() {
+ if (mContentViewCore.isAlive()) {
+ if (mTextToSpeech != null) mTextToSpeech.stop();
+ if (mVibrator != null) mVibrator.cancel();
+ }
+ }
+
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with the actions and movement granularity
+ * levels supported by this {@link AccessibilityInjector}.
+ * <p>
+ * If an action identifier is added in this method, this {@link AccessibilityInjector} should
+ * also return {@code true} from {@link #supportsAccessibilityAction(int)}.
+ * </p>
+ *
+ * @param info The info to initialize.
+ * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { }
+
+ /**
+ * Returns {@code true} if this {@link AccessibilityInjector} should handle the specified
+ * action.
+ *
+ * @param action An accessibility action identifier.
+ * @return {@code true} if this {@link AccessibilityInjector} should handle the specified
+ * action.
+ */
+ public boolean supportsAccessibilityAction(int action) {
+ return false;
+ }
+
+ /**
+ * Performs the specified accessibility action.
+ *
+ * @param action The identifier of the action to perform.
+ * @param arguments The action arguments, or {@code null} if no arguments.
+ * @return {@code true} if the action was successful.
+ * @see View#performAccessibilityAction(int, Bundle)
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ return false;
+ }
+
+ protected void addAccessibilityApis() {
+ Context context = mContentViewCore.getContext();
+ if (context != null) {
+ // Enabled, we should try to add if we have to.
+ if (mTextToSpeech == null) {
+ mTextToSpeech = new TextToSpeechWrapper(context);
+ mContentViewCore.addJavascriptInterface(mTextToSpeech,
+ ALIAS_ACCESSIBILITY_JS_INTERFACE, false);
+ }
+
+ if (mVibrator == null) {
+ mVibrator = new VibratorWrapper(context);
+ mContentViewCore.addJavascriptInterface(mVibrator,
+ ALIAS_ACCESSIBILITY_JS_INTERFACE_2, false);
+ }
+ }
+ }
+
+ protected void removeAccessibilityApis() {
+ if (mTextToSpeech != null) {
+ mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
+ mTextToSpeech.stop();
+ mTextToSpeech.shutdownInternal();
+ mTextToSpeech = null;
+ }
+
+ if (mVibrator != null) {
+ mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE_2);
+ mVibrator.cancel();
+ mVibrator = null;
+ }
+ }
+
+ private int getAxsUrlParameterValue() {
+ if (mContentViewCore.getUrl() == null) return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
+
+ try {
+ List<NameValuePair> params = URLEncodedUtils.parse(new URI(mContentViewCore.getUrl()),
+ null);
+
+ for (NameValuePair param : params) {
+ if ("axs".equals(param.getName())) {
+ return Integer.parseInt(param.getValue());
+ }
+ }
+ } catch (URISyntaxException ex) {
+ } catch (NumberFormatException ex) {
+ } catch (IllegalArgumentException ex) {
+ }
+
+ return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
+ }
+
+ private String getScreenReaderInjectingJs() {
+ return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE,
+ ACCESSIBILITY_SCREEN_READER_URL);
+ }
+
+ private AccessibilityManager getAccessibilityManager() {
+ if (mAccessibilityManager == null) {
+ mAccessibilityManager = (AccessibilityManager) mContentViewCore.getContext().
+ getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ return mAccessibilityManager;
+ }
+
+ /**
+ * Used to protect how long JavaScript can vibrate for. This isn't a good comprehensive
+ * protection, just used to cover mistakes and protect against long vibrate durations/repeats.
+ *
+ * Also only exposes methods we *want* to expose, no others for the class.
+ */
+ private static class VibratorWrapper {
+ private static final long MAX_VIBRATE_DURATION_MS = 5000;
+
+ private Vibrator mVibrator;
+
+ public VibratorWrapper(Context context) {
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean hasVibrator() {
+ return mVibrator.hasVibrator();
+ }
+
+ @SuppressWarnings("unused")
+ public void vibrate(long milliseconds) {
+ milliseconds = Math.min(milliseconds, MAX_VIBRATE_DURATION_MS);
+ mVibrator.vibrate(milliseconds);
+ }
+
+ @SuppressWarnings("unused")
+ public void vibrate(long[] pattern, int repeat) {
+ for (int i = 0; i < pattern.length; ++i) {
+ pattern[i] = Math.min(pattern[i], MAX_VIBRATE_DURATION_MS);
+ }
+
+ repeat = -1;
+
+ mVibrator.vibrate(pattern, repeat);
+ }
+
+ @SuppressWarnings("unused")
+ public void cancel() {
+ mVibrator.cancel();
+ }
+ }
+
+ /**
+ * Used to protect the TextToSpeech class, only exposing the methods we want to expose.
+ */
+ private static class TextToSpeechWrapper {
+ private TextToSpeech mTextToSpeech;
+
+ public TextToSpeechWrapper(Context context) {
+ mTextToSpeech = new TextToSpeech(context, null, null);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean isSpeaking() {
+ return mTextToSpeech.isSpeaking();
+ }
+
+ @SuppressWarnings("unused")
+ public int speak(String text, int queueMode, HashMap<String, String> params) {
+ return mTextToSpeech.speak(text, queueMode, params);
+ }
+
+ @SuppressWarnings("unused")
+ public int stop() {
+ return mTextToSpeech.stop();
+ }
+
+ @SuppressWarnings("unused")
+ protected void shutdownInternal() {
+ mTextToSpeech.shutdown();
+ }
+ }
+}