This source file includes following definitions.
- onInitializeAccessibilityNodeInfo
- supportsAccessibilityAction
- performAccessibilityAction
- addAccessibilityApis
- removeAccessibilityApis
- sendActionToAndroidVox
- performAction
- getResultAndClear
- clearResultLocked
- waitForResultTimedLocked
- SuppressWarnings
- onResult
package org.chromium.content.browser.accessibility;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
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.Locale;
import java.util.concurrent.atomic.AtomicInteger;
class JellyBeanAccessibilityInjector extends AccessibilityInjector {
private CallbackHandler mCallback;
private JSONObject mAccessibilityJSONObject;
private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal";
private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE =
"cvox.AndroidVox.performAction('%1s')";
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;
}
boolean actionSuccessful = sendActionToAndroidVox(action, arguments);
if (actionSuccessful) mContentViewCore.showImeIfNeeded();
return actionSuccessful;
}
@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);
}
}
@Override
protected void removeAccessibilityApis() {
super.removeAccessibilityApis();
if (mCallback != null) {
mContentViewCore.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE);
mCallback = null;
}
}
private boolean sendActionToAndroidVox(int action, Bundle arguments) {
if (mCallback == null) return false;
if (mAccessibilityJSONObject == null) {
mAccessibilityJSONObject = new JSONObject();
} else {
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(Locale.US, ACCESSIBILITY_ANDROIDVOX_TEMPLATE,
jsonString);
return mCallback.performAction(mContentViewCore, jsCode);
}
private static class CallbackHandler {
private static final String JAVASCRIPT_ACTION_TEMPLATE =
"(function() {" +
" retVal = false;" +
" try {" +
" retVal = %s;" +
" } catch (e) {" +
" retVal = false;" +
" }" +
" %s.onResult(%d, retVal);" +
"})()";
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;
}
private boolean performAction(ContentViewCore contentView, String code) {
final int resultId = mResultIdCounter.getAndIncrement();
final String js = String.format(Locale.US, JAVASCRIPT_ACTION_TEMPLATE, code,
mInterfaceName, resultId);
contentView.evaluateJavaScript(js, null);
return getResultAndClear(resultId);
}
private boolean getResultAndClear(int resultId) {
synchronized (mResultLock) {
final boolean success = waitForResultTimedLocked(resultId);
final boolean result = success ? mResult : false;
clearResultLocked();
return result;
}
}
private void clearResultLocked() {
mResultId = -1;
mResult = false;
}
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) {
}
}
}
@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();
}
}
}
}