Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/accessibility/JellyBeanAccessibilityInjector.java

Issue 10854070: Upstream JellyBean Accessibility Support (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: content/public/android/java/src/org/chromium/content/browser/accessibility/JellyBeanAccessibilityInjector.java
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/JellyBeanAccessibilityInjector.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/JellyBeanAccessibilityInjector.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f0ad61b03943dfa342b4a1fbb8dc09a70522458
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/JellyBeanAccessibilityInjector.java
@@ -0,0 +1,253 @@
+// 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.Bundle;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.JavascriptInterface;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Handles injecting accessibility Javascript and related Javascript -> Java APIs for JB and newer
+ * devices.
+ */
+class JellyBeanAccessibilityInjector extends AccessibilityInjector {
+ private CallbackHandler mCallback;
+ private JSONObject mAccessibilityJSONObject;
+
+ private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal";
+
+ // Template for JavaScript that performs AndroidVox actions.
+ private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE =
+ "cvox.AndroidVox.performAction('%1s')";
+
+ /**
+ * Constructs an instance of the JellyBeanAccessibilityInjector.
+ * @param view The ContentViewCore that this AccessibilityInjector manages.
+ */
+ protected JellyBeanAccessibilityInjector(ContentViewCore view) {
+ super(view);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH |
+ AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+ info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+ info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+ info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ info.setClickable(true);
+ }
+
+ @Override
+ public boolean supportsAccessibilityAction(int action) {
+ if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ||
+ action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY ||
+ action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ||
+ action == AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT ||
+ action == AccessibilityNodeInfo.ACTION_CLICK) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (!accessibilityIsAvailable() || !mContentViewCore.isAlive() ||
+ !mInjectedScriptEnabled || !mScriptInjected) {
+ return false;
+ }
+
+ return sendActionToAndroidVox(action, arguments);
+ }
+
+ @Override
+ protected void addAccessibilityApis() {
+ super.addAccessibilityApis();
+
+ Context context = mContentViewCore.getContext();
+ if (context != null && mCallback == null) {
+ mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE);
+ mContentViewCore.addJavascriptInterface(
+ mCallback, ALIAS_TRAVERSAL_JS_INTERFACE, true);
+ }
+ }
+
+ @Override
+ protected void removeAccessibilityApis() {
+ super.removeAccessibilityApis();
+
+ if (mCallback != null) {
+ mContentViewCore.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE);
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Packs an accessibility action into a JSON object and sends it to AndroidVox.
+ *
+ * @param action The action identifier.
+ * @param arguments The action arguments, if applicable.
+ * @return The result of the action.
+ */
+ private boolean sendActionToAndroidVox(int action, Bundle arguments) {
+ if (mCallback == null) return false;
+ if (mAccessibilityJSONObject == null) {
+ mAccessibilityJSONObject = new JSONObject();
+ } else {
+ // Remove all keys from the object.
+ final Iterator<?> keys = mAccessibilityJSONObject.keys();
+ while (keys.hasNext()) {
+ keys.next();
+ keys.remove();
+ }
+ }
+
+ try {
+ mAccessibilityJSONObject.accumulate("action", action);
+ if (arguments != null) {
+ if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY ||
+ action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY) {
+ final int granularity = arguments.getInt(AccessibilityNodeInfo.
+ ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+ mAccessibilityJSONObject.accumulate("granularity", granularity);
+ } else if (action == AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT ||
+ action == AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT) {
+ final String element = arguments.getString(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
+ mAccessibilityJSONObject.accumulate("element", element);
+ }
+ }
+ } catch (JSONException ex) {
+ return false;
+ }
+
+ final String jsonString = mAccessibilityJSONObject.toString();
+ final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString);
+ return mCallback.performAction(mContentViewCore, jsCode);
+ }
+
+ private static class CallbackHandler {
+ private static final String JAVASCRIPT_ACTION_TEMPLATE =
+ "(function() { %s.onResult(%d, %s); })()";
+
+ // Time in milliseconds to wait for a result before failing.
+ private static final long RESULT_TIMEOUT = 5000;
+
+ private final AtomicInteger mResultIdCounter = new AtomicInteger();
+ private final Object mResultLock = new Object();
+ private final String mInterfaceName;
+
+ private boolean mResult = false;
+ private long mResultId = -1;
+
+ private CallbackHandler(String interfaceName) {
+ mInterfaceName = interfaceName;
+ }
+
+ /**
+ * Performs an action and attempts to wait for a result.
+ *
+ * @param contentView The ContentViewCore to perform the action on.
+ * @param code Javascript code that evaluates to a result.
+ * @return The result of the action.
+ */
+ private boolean performAction(ContentViewCore contentView, String code) {
+ final int resultId = mResultIdCounter.getAndIncrement();
+ final String js = String.format(JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId,
+ code);
+ contentView.evaluateJavaScript(js);
+
+ return getResultAndClear(resultId);
+ }
+
+ /**
+ * Gets the result of a request to perform an accessibility action.
+ *
+ * @param resultId The result id to match the result with the request.
+ * @return The result of the request.
+ */
+ private boolean getResultAndClear(int resultId) {
+ synchronized (mResultLock) {
+ final boolean success = waitForResultTimedLocked(resultId);
+ final boolean result = success ? mResult : false;
+ clearResultLocked();
+ return result;
+ }
+ }
+
+ /**
+ * Clears the result state.
+ */
+ private void clearResultLocked() {
+ mResultId = -1;
+ mResult = false;
+ }
+
+ /**
+ * Waits up to a given bound for a result of a request and returns it.
+ *
+ * @param resultId The result id to match the result with the request.
+ * @return Whether the result was received.
+ */
+ private boolean waitForResultTimedLocked(int resultId) {
+ long waitTimeMillis = RESULT_TIMEOUT;
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ try {
+ if (mResultId == resultId) return true;
+ if (mResultId > resultId) return false;
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ waitTimeMillis = RESULT_TIMEOUT - elapsedTimeMillis;
+ if (waitTimeMillis <= 0) return false;
+ mResultLock.wait(waitTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+
+ /**
+ * Callback exposed to JavaScript. Handles returning the result of a
+ * request to a waiting (or potentially timed out) thread.
+ *
+ * @param id The result id of the request as a {@link String}.
+ * @param result The result o fa request as a {@link String}.
+ */
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public void onResult(String id, String result) {
+ final long resultId;
+ try {
+ resultId = Long.parseLong(id);
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ synchronized (mResultLock) {
+ if (resultId > mResultId) {
+ mResult = Boolean.parseBoolean(result);
+ mResultId = resultId;
+ }
+ mResultLock.notifyAll();
+ }
+ }
+ }
+}
« no previous file with comments | « content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698