summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java60
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java443
-rw-r--r--core/java/android/webkit/CallbackProxy.java13
-rw-r--r--core/java/android/webkit/WebView.java138
-rw-r--r--core/java/android/webkit/WebViewCore.java22
-rw-r--r--core/tests/coretests/AndroidManifest.xml8
-rw-r--r--core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java945
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml28
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java41
9 files changed, 1623 insertions, 75 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 46bde373b9b3..e1c84effa255 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -34,7 +34,10 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
-import android.os.*;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Config;
@@ -2527,6 +2530,60 @@ public final class Settings {
"enabled_accessibility_services";
/**
+ * If injection of accessibility enhancing JavaScript scripts
+ * is enabled.
+ * <p>
+ * Note: Accessibility injecting scripts are served by the
+ * Google infrastructure and enable users with disabilities to
+ * efficiantly navigate in and explore web content.
+ * </p>
+ * <p>
+ * This property represents a boolean value.
+ * </p>
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SCRIPT_INJECTION =
+ "accessibility_script_injection";
+
+ /**
+ * Key bindings for navigation in built-in accessibility support for web content.
+ * <p>
+ * Note: These key bindings are for the built-in accessibility navigation for
+ * web content which is used as a fall back solution if JavaScript in a WebView
+ * is not enabled or the user has not opted-in script injection from Google.
+ * </p>
+ * <p>
+ * The bindings are separated by semi-colon. A binding is a mapping from
+ * a key to a sequence of actions (for more details look at
+ * android.webkit.AccessibilityInjector). A key is represented as the hexademical
+ * string representation of an integer obtained from a meta state (optional) shifted
+ * sixteen times left and bitwise ored with a key code. An action is represented
+ * as a hexademical string representation of an integer where the first two digits
+ * are navigation action index, the second, the third, and the fourth digit pairs
+ * represent the action arguments. The separate actions in a binding are colon
+ * separated. The key and the action sequence it maps to are separated by equals.
+ * </p>
+ * <p>
+ * For example, the binding below maps the DPAD right button to traverse the
+ * current navigation axis once without firing an accessibility event and to
+ * perform the same traversal again but to fire an event:
+ * <code>
+ * 0x16=0x01000100:0x01000101;
+ * </code>
+ * </p>
+ * <p>
+ * The goal of this binding is to enable dynamic rebinding of keys to
+ * navigation actions for web content without requiring a framework change.
+ * </p>
+ * <p>
+ * This property represents a string value.
+ * </p>
+ * @hide
+ */
+ public static final String ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS =
+ "accessibility_web_content_key_bindings";
+
+ /**
* Setting to always use the default text-to-speech settings regardless
* of the application settings.
* 1 = override application settings,
@@ -3497,6 +3554,7 @@ public final class Settings {
PARENTAL_CONTROL_REDIRECT_URL,
USB_MASS_STORAGE_ENABLED,
ACCESSIBILITY_ENABLED,
+ ACCESSIBILITY_SCRIPT_INJECTION,
BACKUP_AUTO_RESTORE,
ENABLED_ACCESSIBILITY_SERVICES,
TTS_USE_DEFAULTS,
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 49ddc19be178..ba16c8a553d0 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -16,27 +16,95 @@
package android.webkit;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Log;
+import android.util.SparseArray;
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;
+
/**
* This class injects accessibility into WebViews with disabled JavaScript or
* WebViews with enabled JavaScript but for which we have no accessibility
* script to inject.
+ * </p>
+ * 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.
+ * </p>
+ * In general a key binding is a mapping from meta state + 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}.
+ * </p>
+ * 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}.
*/
class AccessibilityInjector {
+ private static final String LOG_TAG = "AccessibilityInjector";
+
+ private static final boolean DEBUG = true;
+
+ 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;
- // Handle to the WebView this injector is associated with.
+ // the default WebView behavior abstracted as a navigation axis
+ private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
+
+ // these are the same for all instances so make them process wide
+ private static SparseArray<AccessibilityWebContentKeyBinding> sBindings =
+ new SparseArray<AccessibilityWebContentKeyBinding>();
+
+ // handle to the WebView this injector is associated with.
private final WebView mWebView;
+ // events scheduled for sending as soon as we receive the selected text
+ private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+
+ // the current traversal axis
+ private int mCurrentAxis = 2; // sentence
+
+ // 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;
+
/**
- * Creates a new injector associated with a given VwebView.
+ * Creates a new injector associated with a given {@link WebView}.
*
* @param webView The associated WebView.
*/
public AccessibilityInjector(WebView webView) {
mWebView = webView;
+ ensureWebContentKeyBindings();
}
/**
@@ -45,55 +113,372 @@ class AccessibilityInjector {
* @return True if the event was processed.
*/
public boolean onKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return mLastDownEventHandled;
+ }
+
+ mLastDownEventHandled = false;
- // as a proof of concept let us do the simplest example
+ int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE |
+ event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE;
- if (event.getAction() != KeyEvent.ACTION_UP) {
+ AccessibilityWebContentKeyBinding binding = sBindings.get(key);
+ if (binding == null) {
return false;
}
- int keyCode = event.getKeyCode();
+ for (int i = 0, count = binding.getActionCount(); i < count; i++) {
+ int actionCode = binding.getActionCode(i);
+ String contentDescription = Integer.toHexString(binding.getAction(i));
+ switch (actionCode) {
+ case ACTION_SET_CURRENT_AXIS:
+ int axis = binding.getFirstArgument(i);
+ boolean sendEvent = (binding.getSecondArgument(i) == 1);
+ setCurrentAxis(axis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ case ACTION_TRAVERSE_CURRENT_AXIS:
+ int direction = binding.getFirstArgument(i);
+ // on second null selection string in same direction => WebView handle the event
+ if (direction == mLastDirection && mIsLastSelectionStringNull) {
+ mLastDirection = direction;
+ mIsLastSelectionStringNull = false;
+ return false;
+ }
+ mLastDirection = direction;
+ sendEvent = (binding.getSecondArgument(i) == 1);
+ mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
+ contentDescription);
+ break;
+ case ACTION_TRAVERSE_GIVEN_AXIS:
+ direction = binding.getFirstArgument(i);
+ // on second null selection string in same direction => WebView handle the event
+ if (direction == mLastDirection && mIsLastSelectionStringNull) {
+ mLastDirection = direction;
+ mIsLastSelectionStringNull = false;
+ return false;
+ }
+ mLastDirection = direction;
+ axis = binding.getSecondArgument(i);
+ sendEvent = (binding.getThirdArgument(i) == 1);
+ traverseGivenAxis(direction, axis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ case ACTION_PERFORM_AXIS_TRANSITION:
+ int fromAxis = binding.getFirstArgument(i);
+ int toAxis = binding.getSecondArgument(i);
+ sendEvent = (binding.getThirdArgument(i) == 1);
+ prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ default:
+ Log.w(LOG_TAG, "Unknown action code: " + actionCode);
+ }
+ }
+
+ return mLastDownEventHandled;
+ }
- switch (keyCode) {
- case KeyEvent.KEYCODE_N:
- modifySelection("extend", "forward", "sentence");
- break;
- case KeyEvent.KEYCODE_P:
- modifySelection("extend", "backward", "sentence");
- break;
+ /**
+ * Set the current navigation axis which will be used while
+ * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+ *
+ * @param axis The axis to set.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ */
+ private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
+ mCurrentAxis = axis;
+ if (sendEvent) {
+ AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
+ event.getText().add(String.valueOf(axis));
+ event.setContentDescription(contentDescription);
+ sendAccessibilityEvent(event);
}
+ }
- return false;
+ /**
+ * Performs conditional transition one axis to another.
+ *
+ * @param fromAxis The axis which must be the current for the transition to occur.
+ * @param toAxis The axis to which to transition.
+ * @param sendEvent Flag if to send an event to announce successful transition.
+ * @param contentDescription A description of the performed action.
+ */
+ private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
+ String contentDescription) {
+ if (mCurrentAxis == fromAxis) {
+ setCurrentAxis(toAxis, sendEvent, contentDescription);
+ }
+ }
+
+ /**
+ * Traverse the document along the current navigation axis.
+ *
+ * @param direction The direction of traversal.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ * @param contentDescription A description of the performed action.
+ * @see #setCurrentAxis(int, boolean, String)
+ */
+ private boolean traverseCurrentAxis(int direction, boolean sendEvent,
+ String contentDescription) {
+ return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
+ }
+
+ /**
+ * Traverse the document along the given navigation axis.
+ *
+ * @param direction The direction of traversal.
+ * @param axis The axis along which to traverse.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ * @param contentDescription A description of the performed action.
+ */
+ private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
+ String contentDescription) {
+ // if the axis is the default let WebView handle the event
+ if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
+ return false;
+ }
+ WebViewCore webViewCore = mWebView.getWebViewCore();
+ if (webViewCore != null) {
+ AccessibilityEvent event = null;
+ if (sendEvent) {
+ event = getPartialyPopulatedAccessibilityEvent();
+ // the text will be set upon receiving the selection string
+ event.setContentDescription(contentDescription);
+ }
+ mScheduledEventStack.push(event);
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
+ }
+ return true;
}
/**
* Called when the <code>selectionString</code> has changed.
*/
public void onSelectionStringChange(String selectionString) {
- // put the selection string in an AccessibilityEvent and send it
- AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- event.getText().add(selectionString);
- mWebView.sendAccessibilityEventUnchecked(event);
+ mIsLastSelectionStringNull = (selectionString == null);
+ AccessibilityEvent event = mScheduledEventStack.pop();
+ if (event != null) {
+ event.getText().add(selectionString);
+ sendAccessibilityEvent(event);
+ }
}
/**
- * Modifies the current selection.
+ * Sends an {@link AccessibilityEvent}.
*
- * @param alter Specifies how to alter the selection.
- * @param direction The direction in which to alter the selection.
- * @param granularity The granularity of the selection modification.
+ * @param event The event to send.
*/
- private void modifySelection(String alter, String direction, String granularity) {
- WebViewCore webViewCore = mWebView.getWebViewCore();
+ private void sendAccessibilityEvent(AccessibilityEvent event) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Dispatching: " + event);
+ }
+ AccessibilityManager.getInstance(mWebView.getContext()).sendAccessibilityEvent(event);
+ }
- if (webViewCore == null) {
+ /**
+ * @return An accessibility event whose members are populated except its
+ * text and content description.
+ */
+ private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
+ AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ event.setClassName(mWebView.getClass().getName());
+ event.setPackageName(mWebView.getContext().getPackageName());
+ event.setEnabled(mWebView.isEnabled());
+ return event;
+ }
+
+ /**
+ * Ensures that the Web content key bindings are loaded.
+ */
+ private void ensureWebContentKeyBindings() {
+ if (sBindings.size() > 0) {
return;
}
- WebViewCore.ModifySelectionData data = new WebViewCore.ModifySelectionData();
- data.mAlter = alter;
- data.mDirection = direction;
- data.mGranularity = granularity;
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, data);
+ String webContentKeyBindingsString = Settings.Secure.getString(
+ mWebView.getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+
+ SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
+ semiColonSplitter.setString(webContentKeyBindingsString);
+
+ ArrayList<AccessibilityWebContentKeyBinding> bindings =
+ new ArrayList<AccessibilityWebContentKeyBinding>();
+
+ while (semiColonSplitter.hasNext()) {
+ String bindingString = semiColonSplitter.next();
+ if (TextUtils.isEmpty(bindingString)) {
+ Log.e(LOG_TAG, "Malformed Web content key binding: "
+ + webContentKeyBindingsString);
+ continue;
+ }
+ String[] keyValueArray = bindingString.split("=");
+ if (keyValueArray.length != 2) {
+ Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " +
+ bindingString);
+ continue;
+ }
+ try {
+ SimpleStringSplitter colonSplitter = new SimpleStringSplitter(':');//remove
+ int key = Integer.decode(keyValueArray[0].trim());
+ String[] actionStrings = keyValueArray[1].split(":");
+ int[] actions = new int[actionStrings.length];
+ for (int i = 0, count = actions.length; i < count; i++) {
+ actions[i] = Integer.decode(actionStrings[i].trim());
+ }
+
+ bindings.add(new AccessibilityWebContentKeyBinding(key, actions));
+ } catch (NumberFormatException nfe) {
+ Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
+ }
+ }
+
+ for (AccessibilityWebContentKeyBinding binding : bindings) {
+ sBindings.put(binding.getKey(), binding);
+ }
+ }
+
+ /**
+ * Represents a web content key-binding.
+ */
+ private class AccessibilityWebContentKeyBinding {
+
+ private static final int OFFSET_META_STATE = 0x00000010;
+
+ private static final int MASK_META_STATE = 0xFFFF0000;
+
+ private static final int OFFSET_KEY_CODE = 0x00000000;
+
+ private static final int MASK_KEY_CODE = 0x0000FFFF;
+
+ private static final int OFFSET_ACTION = 0x00000018;
+
+ private static final int MASK_ACTION = 0xFF000000;
+
+ private static final int OFFSET_FIRST_ARGUMENT = 0x00000010;
+
+ private static final int MASK_FIRST_ARGUMENT = 0x00FF0000;
+
+ private static final int OFFSET_SECOND_ARGUMENT = 0x00000008;
+
+ private static final int MASK_SECOND_ARGUMENT = 0x0000FF00;
+
+ private static final int OFFSET_THIRD_ARGUMENT = 0x00000000;
+
+ private static final int MASK_THIRD_ARGUMENT = 0x000000FF;
+
+ private int mKey;
+
+ private int [] mActionSequence;
+
+ /**
+ * @return The binding key with key code and meta state.
+ *
+ * @see #MASK_KEY_CODE
+ * @see #MASK_META_STATE
+ * @see #OFFSET_KEY_CODE
+ * @see #OFFSET_META_STATE
+ */
+ public int getKey() {
+ return mKey;
+ }
+
+ /**
+ * @return The key code of the binding key.
+ */
+ public int getKeyCode() {
+ return (mKey & MASK_KEY_CODE) >> OFFSET_KEY_CODE;
+ }
+
+ /**
+ * @return The meta state of the binding key.
+ */
+ public int getMetaState() {
+ return (mKey & MASK_META_STATE) >> OFFSET_META_STATE;
+ }
+
+ /**
+ * @return The number of actions in the key binding.
+ */
+ public int getActionCount() {
+ return mActionSequence.length;
+ }
+
+ /**
+ * @param index The action for a given action <code>index</code>.
+ */
+ public int getAction(int index) {
+ return mActionSequence[index];
+ }
+
+ /**
+ * @param index The action code for a given action <code>index</code>.
+ */
+ public int getActionCode(int index) {
+ return (mActionSequence[index] & MASK_ACTION) >> OFFSET_ACTION;
+ }
+
+ /**
+ * @param index The first argument for a given action <code>index</code>.
+ */
+ public int getFirstArgument(int index) {
+ return (mActionSequence[index] & MASK_FIRST_ARGUMENT) >> OFFSET_FIRST_ARGUMENT;
+ }
+
+ /**
+ * @param index The second argument for a given action <code>index</code>.
+ */
+ public int getSecondArgument(int index) {
+ return (mActionSequence[index] & MASK_SECOND_ARGUMENT) >> OFFSET_SECOND_ARGUMENT;
+ }
+
+ /**
+ * @param index The third argument for a given action <code>index</code>.
+ */
+ public int getThirdArgument(int index) {
+ return (mActionSequence[index] & MASK_THIRD_ARGUMENT) >> OFFSET_THIRD_ARGUMENT;
+ }
+
+ /**
+ * Creates a new instance.
+ * @param key The key for the binding (key and meta state)
+ * @param actionSequence The sequence of action for the binding.
+ * @see #getKey()
+ */
+ public AccessibilityWebContentKeyBinding(int key, int[] actionSequence) {
+ mKey = key;
+ mActionSequence = actionSequence;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("key: ");
+ builder.append(getKey());
+ builder.append(", metaState: ");
+ builder.append(getMetaState());
+ builder.append(", keyCode: ");
+ builder.append(getKeyCode());
+ builder.append(", actions[");
+ for (int i = 0, count = getActionCount(); i < count; i++) {
+ builder.append("{actionCode");
+ builder.append(i);
+ builder.append(": ");
+ builder.append(getActionCode(i));
+ builder.append(", firstArgument: ");
+ builder.append(getFirstArgument(i));
+ builder.append(", secondArgument: ");
+ builder.append(getSecondArgument(i));
+ builder.append(", thirdArgument: ");
+ builder.append(getThirdArgument(i));
+ builder.append("}");
+ }
+ builder.append("]");
+ return builder.toString();
+ }
}
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 1b5651b324c8..b00f88cbf85e 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -256,17 +256,10 @@ class CallbackProxy extends Handler {
// 32-bit reads and writes.
switch (msg.what) {
case PAGE_STARTED:
- // every time we start a new page, we want to reset the
- // WebView certificate:
- // if the new site is secure, we will reload it and get a
- // new certificate set;
- // if the new site is not secure, the certificate must be
- // null, and that will be the case
- mWebView.setCertificate(null);
+ String startedUrl = msg.getData().getString("url");
+ mWebView.onPageStarted(startedUrl);
if (mWebViewClient != null) {
- mWebViewClient.onPageStarted(mWebView,
- msg.getData().getString("url"),
- (Bitmap) msg.obj);
+ mWebViewClient.onPageStarted(mWebView, startedUrl, (Bitmap) msg.obj);
}
break;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a3a4c43cd961..d4acff892211 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -49,6 +49,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.text.Selection;
import android.text.Spannable;
@@ -101,6 +102,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* <p>A View that displays web pages. This class is the basis upon which you
@@ -535,6 +538,10 @@ public class WebView extends AbsoluteLayout
// JavaScript or ones for which no accessibility script exists
private AccessibilityInjector mAccessibilityInjector;
+ // flag indicating if accessibility script is injected so we
+ // know to handle Shift and arrows natively first
+ private boolean mAccessibilityScriptInjected;
+
// the color used to highlight the touch rectangles
private static final int mHightlightColor = 0x33000000;
// the round corner for the highlight path
@@ -694,6 +701,11 @@ public class WebView extends AbsoluteLayout
private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
+ // 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;
+
// the alias via which accessibility JavaScript interface is exposed
private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
@@ -707,6 +719,14 @@ public class WebView extends AbsoluteLayout
" document.getElementsByTagName('head')[0].appendChild(chooser);" +
" })();";
+ // Regular expression that matches the "axs" URL parameter.
+ // The value of 0 means the accessibility script is opted out
+ // The value of 1 means the accessibility script is already injected
+ private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
+
+ // variable to cache the above pattern in case accessibility is enabled.
+ private Pattern mMatchAxsUrlParameterPattern;
+
// Used to match key downs and key ups
private boolean mGotKeyDown;
@@ -2947,6 +2967,23 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Called by CallbackProxy when the page starts loading.
+ * @param url The URL of the page which has started loading.
+ */
+ /* package */ void onPageStarted(String url) {
+ // every time we start a new page, we want to reset the
+ // WebView certificate: if the new site is secure, we
+ // will reload it and get a new certificate set;
+ // if the new site is not secure, the certificate must be
+ // null, and that will be the case
+ setCertificate(null);
+
+ // reset the flag since we set to true in if need after
+ // loading is see onPageFinished(Url)
+ mAccessibilityScriptInjected = false;
+ }
+
+ /**
* Called by CallbackProxy when the page finishes loading.
* @param url The URL of the page which has finished loading.
*/
@@ -2971,23 +3008,93 @@ public class WebView extends AbsoluteLayout
* is enabled. If JavaScript is enabled we try to inject a URL specific script.
* If no URL specific script is found or JavaScript is disabled we fallback to
* the default {@link AccessibilityInjector} implementation.
+ * </p>
+ * If the URL has the "axs" paramter set to 1 it has already done the
+ * script injection so we do nothing. If the parameter is set to 0
+ * the URL opts out accessibility script injection so we fall back to
+ * the default {@link AccessibilityInjector}.
+ * </p>
+ * Note: If the user has not opted-in the accessibility script injection no scripts
+ * are injected rather the default {@link AccessibilityInjector} implementation
+ * is used.
*
* @param url The URL loaded by this {@link WebView}.
*/
private void injectAccessibilityForUrl(String url) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- if (getSettings().getJavaScriptEnabled()) {
+ AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
+
+ if (!accessibilityManager.isEnabled()) {
+ // it is possible that accessibility was turned off between reloads
+ ensureAccessibilityScriptInjectorInstance(false);
+ return;
+ }
+
+ if (!getSettings().getJavaScriptEnabled()) {
+ // no JS so we fallback to the basic buil-in support
+ ensureAccessibilityScriptInjectorInstance(true);
+ return;
+ }
+
+ // check the URL "axs" parameter to choose appropriate action
+ int axsParameterValue = getAxsUrlParameterValue(url);
+ if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
+ boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
+ .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
+ if (onDeviceScriptInjectionEnabled) {
+ ensureAccessibilityScriptInjectorInstance(false);
+ // neither script injected nor script injection opted out => we inject
loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
- } else if (mAccessibilityInjector == null) {
- mAccessibilityInjector = new AccessibilityInjector(this);
+ // TODO: Set this flag after successfull script injection. Maybe upon injection
+ // the chooser should update the meta tag and we check it to declare success
+ mAccessibilityScriptInjected = true;
+ } else {
+ // injection disabled so we fallback to the basic built-in support
+ ensureAccessibilityScriptInjectorInstance(true);
}
+ } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
+ // injection opted out so we fallback to the basic buil-in support
+ ensureAccessibilityScriptInjectorInstance(true);
+ } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
+ ensureAccessibilityScriptInjectorInstance(false);
+ // the URL provides accessibility but we still need to add our generic script
+ loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT);
+ } else {
+ Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
+ }
+ }
+
+ /**
+ * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
+ *
+ * @param present True to ensure an insance, false to ensure no instance.
+ */
+ private void ensureAccessibilityScriptInjectorInstance(boolean present) {
+ if (present && mAccessibilityInjector == null) {
+ mAccessibilityInjector = new AccessibilityInjector(this);
} else {
- // it is possible that accessibility was turned off between reloads
mAccessibilityInjector = null;
}
}
/**
+ * Gets the "axs" URL parameter value.
+ *
+ * @param url A url to fetch the paramter from.
+ * @return The parameter value if such, -1 otherwise.
+ */
+ private int getAxsUrlParameterValue(String url) {
+ if (mMatchAxsUrlParameterPattern == null) {
+ mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
+ }
+ Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
+ if (matcher.find()) {
+ String keyValuePair = url.substring(matcher.start(), matcher.end());
+ return Integer.parseInt(keyValuePair.split("=")[1]);
+ }
+ return -1;
+ }
+
+ /**
* The URL of a page that sent a message to scroll the title bar off screen.
*
* Many mobile sites tell the page to scroll to (0,1) in order to scroll the
@@ -3964,7 +4071,7 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
- if (nativePageShouldHandleShiftAndArrows()) {
+ if (pageShouldHandleShiftAndArrows()) {
mShiftIsPressed = true;
} else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
setUpSelect();
@@ -3984,7 +4091,7 @@ public class WebView extends AbsoluteLayout
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
switchOutDrawHistory();
- if (nativePageShouldHandleShiftAndArrows()) {
+ if (pageShouldHandleShiftAndArrows()) {
letPageHandleNavKey(keyCode, event.getEventTime(), true);
return true;
}
@@ -4118,7 +4225,7 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
- if (nativePageShouldHandleShiftAndArrows()) {
+ if (pageShouldHandleShiftAndArrows()) {
mShiftIsPressed = false;
} else if (copySelection()) {
selectionDone();
@@ -4128,7 +4235,7 @@ public class WebView extends AbsoluteLayout
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
- if (nativePageShouldHandleShiftAndArrows()) {
+ if (pageShouldHandleShiftAndArrows()) {
letPageHandleNavKey(keyCode, event.getEventTime(), false);
return true;
}
@@ -5550,7 +5657,8 @@ public class WebView extends AbsoluteLayout
}
return false; // let common code in onKeyUp at it
}
- if (mMapTrackballToArrowKeys && mShiftIsPressed == false) {
+ if ((mMapTrackballToArrowKeys && mShiftIsPressed == false) ||
+ (mAccessibilityInjector != null || mAccessibilityScriptInjected)) {
if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
return false;
}
@@ -7288,6 +7396,16 @@ public class WebView extends AbsoluteLayout
}
/**
+ * @return If the page should receive Shift and arrows.
+ */
+ private boolean pageShouldHandleShiftAndArrows() {
+ // TODO: Maybe the injected script should announce its presence in
+ // the page meta-tag so the nativePageShouldHandleShiftAndArrows
+ // will check that as one of the conditions it looks for
+ return (nativePageShouldHandleShiftAndArrows() || mAccessibilityScriptInjected);
+ }
+
+ /**
* Set the background color. It's white by default. Pass
* zero to make the view transparent.
* @param color the ARGB color described by Color.java
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 60ddf0808feb..56d62962f313 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -572,13 +572,12 @@ final class WebViewCore {
/**
* Modifies the current selection.
*
- * @param alter Specifies how to alter the selection.
* @param direction The direction in which to alter the selection.
* @param granularity The granularity of the selection modification.
*
* @return The selection string.
*/
- private native String nativeModifySelection(String alter, String direction, String granularity);
+ private native String nativeModifySelection(int direction, int granularity);
// EventHub for processing messages
private final EventHub mEventHub;
@@ -724,12 +723,6 @@ final class WebViewCore {
boolean mRemember;
}
- static class ModifySelectionData {
- String mAlter;
- String mDirection;
- String mGranularity;
- }
-
static final String[] HandlerDebugString = {
"REVEAL_SELECTION", // 96
"REQUEST_LABEL", // 97
@@ -1270,16 +1263,9 @@ final class WebViewCore {
break;
case MODIFY_SELECTION:
- ModifySelectionData modifySelectionData =
- (ModifySelectionData) msg.obj;
- String selectionString = nativeModifySelection(
- modifySelectionData.mAlter,
- modifySelectionData.mDirection,
- modifySelectionData.mGranularity);
-
- mWebView.mPrivateHandler.obtainMessage(
- WebView.SELECTION_STRING_CHANGED, selectionString)
- .sendToTarget();
+ String selectionString = nativeModifySelection(msg.arg1, msg.arg2);
+ mWebView.mPrivateHandler.obtainMessage(WebView.SELECTION_STRING_CHANGED,
+ selectionString).sendToTarget();
break;
case LISTBOX_CHOICES:
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index f09421bca74f..e449ddb9160a 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1022,7 +1022,13 @@
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
-
+
+ <service android:name="android.webkit.AccessibilityInjectorTest$MockAccessibilityService">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ </intent-filter>
+ </service>
+
<activity android:name="android.widget.RadioGroupActivity" android:label="RadioGroupActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
new file mode 100644
index 000000000000..9c9d9fef752c
--- /dev/null
+++ b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
@@ -0,0 +1,945 @@
+/*
+ * Copyright (C) 2010 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+/**
+ * This is a test for the behavior of the {@link AccessibilityInjector}
+ * which is used by {@link WebView} to provide basic accessibility support
+ * in case JavaScript is disabled.
+ * </p>
+ * Note: This test works against the generated {@link AccessibilityEvent}s
+ * to so it also checks if the test for announcing navigation axis and
+ * status messages as appropriate.
+ */
+public class AccessibilityInjectorTest extends AndroidTestCase {
+
+ /** The timeout to wait for the expected selection. */
+ private static final long TIMEOUT_WAIT_FOR_SELECTION_STRING = 1000;
+
+ /** The timeout to wait for accessibility and the mock service to be enabled. */
+ private static final long TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE = 500;
+
+ /** The count of tests to detect when to shut down the service. */
+ private static final int TEST_CASE_COUNT = 8;
+
+ /** The meta state for pressed left ALT. */
+ private static final int META_STATE_ALT_LEFT_ON = KeyEvent.META_ALT_ON
+ | KeyEvent.META_ALT_LEFT_ON;
+
+ /** The value for not specified selection string since null is a valid value. */
+ private static final String SELECTION_STRING_UNKNOWN = "Unknown";
+
+ /** Lock for locking the test. */
+ private static final Object sTestLock = new Object();
+
+ /** Handle to the test for use by the mock service. */
+ private static AccessibilityInjectorTest sInstance;
+
+ /** Flag indicating if the accessibility service is ready to receive events. */
+ private static boolean sIsAccessibilityServiceReady;
+
+ /** The count of executed tests to detect when to toggle accessibility and the service. */
+ private static int sExecutedTestCount;
+
+ /** Worker thread with a handler to perform non test thread processing. */
+ private Worker mWorker;
+
+ /** Handle to the {@link WebView} to load data in. */
+ private WebView mWebView;
+
+ /** The received selection string for assertion checking. */
+ private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mWorker = new Worker();
+ sInstance = this;
+ if (sExecutedTestCount == 0) {
+ // until JUnit4 comes to play with @BeforeTest
+ disableAccessibilityAndMockAccessibilityService();
+ enableAccessibilityAndMockAccessibilityService();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mWorker != null) {
+ mWorker.stop();
+ }
+ if (sExecutedTestCount == TEST_CASE_COUNT) {
+ // until JUnit4 comes to play with @AfterTest
+ disableAccessibilityAndMockAccessibilityService();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests navigation by character.
+ */
+ @LargeTest
+ public void testNavigationByCharacter() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<p>" +
+ "a <b>b</b> c" +
+ "</p>" +
+ "<p>" +
+ "d" +
+ "<input>e</input>" +
+ "</p>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("1"); // expect the word navigation axis
+
+ // change navigation axis to character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("0"); // expect the character navigation axis
+
+ // go to the first character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("a");
+
+ // go to the second character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<b>b</b>");
+
+ // go to the third character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("c");
+
+ // go to the fourth character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("d");
+
+ // go to the fifth character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("e");
+
+ // try to go past the last character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the fourth character (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("d");
+
+ // go to the third character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("c");
+
+ // go to the second character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<b>b</b>");
+
+ // go to the first character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("a");
+
+ // try to go before the first character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the second character (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<b>b</b>");
+ }
+
+ /**
+ * Tests navigation by word.
+ */
+ @LargeTest
+ public void testNavigationByWord() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<p>" +
+ "This is <b>a</b> sentence" +
+ "</p>" +
+ "<p>" +
+ " scattered " +
+ "<input>all</input>" +
+ " over " +
+ "</p>" +
+ "<div>" +
+ "<button>the place.</button>" +
+ "</div>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("1"); // expect the word navigation axis
+
+ // go to the first word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This");
+
+ // go to the second word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("is");
+
+ // go to the third word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<b>a</b>");
+
+ // go to the fourth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("sentence");
+
+ // go to the fifth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("scattered");
+
+ // go to the sixth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("all");
+
+ // go to the seventh word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("over");
+
+ // go to the eight word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("the");
+
+ // go to the ninth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("place");
+
+ // NOTE: WebKit selection returns the dot as a word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(".");
+
+ // try to go past the last word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the last word (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("place");
+
+ // go to the eight word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("the");
+
+ // go to the seventh word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("over");
+
+ // go to the sixth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("all");
+
+ // go to the fifth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("scattered");
+
+ // go to the fourth word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("sentence");
+
+ // go to the third word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<b>a</b>");
+
+ // go to the second word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("is");
+
+ // go to the first word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("This");
+
+ // try to go before the first word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the second word (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("is");
+ }
+
+ /**
+ * Tests navigation by sentence.
+ */
+ @LargeTest
+ public void testNavigationBySentence() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<div>" +
+ "<p>" +
+ "This is the first sentence of the first paragraph and has an <b>inline bold tag</b>." +
+ "This is the second sentence of the first paragraph." +
+ "</p>" +
+ "<h1>This is a heading</h1>" +
+ "<p>" +
+ "This is the first sentence of the second paragraph." +
+ "This is the second sentence of the second paragraph." +
+ "</p>" +
+ "</div>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // Sentence axis is the default
+
+ // go to the first sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is the first sentence of the first paragraph and has an "
+ + "<b>inline bold tag</b>.");
+
+ // go to the second sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is the second sentence of the first paragraph.");
+
+ // go to the third sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is a heading");
+
+ // go to the fourth sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is the first sentence of the second paragraph.");
+
+ // go to the fifth sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is the second sentence of the second paragraph.");
+
+ // try to go past the last sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the fourth sentence (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("This is the first sentence of the second paragraph.");
+
+ // go to the third sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("This is a heading");
+
+ // go to the second sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("This is the second sentence of the first paragraph.");
+
+ // go to the first sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("This is the first sentence of the first paragraph and has an "
+ + "<b>inline bold tag</b>.");
+
+ // try to go before the first sentence
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the second sentence (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("This is the second sentence of the first paragraph.");
+ }
+
+ /**
+ * Tests navigation by heading.
+ */
+ @LargeTest
+ public void testNavigationByHeading() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<h1>Heading one</h1>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<h2>Heading two</h2>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<h3>Heading three</h3>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<h4>Heading four</h4>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<h5>Heading five</h5>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<h6>Heading six</h6>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("3"); // expect the heading navigation axis
+
+ // go to the first heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h1>Heading one</h1>");
+
+ // go to the second heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h2>Heading two</h2>");
+
+ // go to the third heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h3>Heading three</h3>");
+
+ // go to the fourth heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h4>Heading four</h4>");
+
+ // go to the fifth heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h5>Heading five</h5>");
+
+ // go to the sixth heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h6>Heading six</h6>");
+
+ // try to go past the last heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the fifth heading (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h5>Heading five</h5>");
+
+ // go to the fourth heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h4>Heading four</h4>");
+
+ // go to the third heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h3>Heading three</h3>");
+
+ // go to the second heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h2>Heading two</h2>");
+
+ // go to the first heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h1>Heading one</h1>");
+
+ // try to go before the first heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the second heading (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h2>Heading two</h2>");
+ }
+
+ /**
+ * Tests navigation by sibling.
+ */
+ @LargeTest
+ public void testNavigationBySibing() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<h1>Heading one</h1>" +
+ "<p>" +
+ "This is some text" +
+ "</p>" +
+ "<div>" +
+ "<button>Input</button>" +
+ "</div>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("3"); // expect the heading navigation axis
+
+ // change navigation axis to sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("4"); // expect the sibling navigation axis
+
+ // change navigation axis to parent/first child
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("5"); // expect the parent/first child navigation axis
+
+ // go to the first child of the body
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<h1>Heading one</h1>");
+
+ // change navigation axis to sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("4"); // expect the sibling navigation axis
+
+ // go to the next sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<p>This is some text</p>");
+
+ // go to the next sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<div><button>Input</button></div>");
+
+ // try to go past the last sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the previous sibling (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<p>This is some text</p>");
+
+ // go to the previous sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<h1>Heading one</h1>");
+
+ // try to go before the previous sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the next sibling (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<p>This is some text</p>");
+ }
+
+ /**
+ * Tests navigation by parent/first child.
+ */
+ @LargeTest
+ public void testNavigationByParentFirstChild() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<div>" +
+ "<button>Input</button>" +
+ "</div>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to document
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("6"); // expect the document navigation axis
+
+ // change navigation axis to parent/first child
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("5"); // expect the parent/first child navigation axis
+
+ // go to the first child
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<div><button>Input</button></div>");
+
+ // go to the first child
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<button>Input</button>");
+
+ // try to go to the first child of a leaf element
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString(null);
+
+ // go to the parent (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<div><button>Input</button></div>");
+
+ // go to the parent
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<body><div><button>Input</button></div></body>");
+
+ // try to go to the body parent
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString(null);
+
+ // go to the first child (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<div><button>Input</button></div>");
+ }
+
+ /**
+ * Tests navigation by document.
+ */
+ @LargeTest
+ public void testNavigationByDocument() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<button>Click</button>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to document
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("6"); // expect the document navigation axis
+
+ // go to the bottom of the document
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("Click");
+
+ // go to the top of the document (reverse)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
+ assertSelectionString("<body><button>Click</button></body>");
+
+ // go to the bottom of the document (reverse again)
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("Click");
+ }
+
+ /**
+ * Tests the sync between the text navigation and navigation by DOM elements.
+ */
+ @LargeTest
+ public void testSyncBetweenTextAndDomNodeNavigation() throws Exception {
+ // a bit ugly but helps detect beginning and end of all tests so accessibility
+ // and the mock service are not toggled on every test (expensive)
+ sExecutedTestCount++;
+
+ String html =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<head>" +
+ "</head>" +
+ "<body>" +
+ "<p>" +
+ "First" +
+ "</p>" +
+ "<button>Second</button>" +
+ "<p>" +
+ "Third" +
+ "</p>" +
+ "</body>" +
+ "</html>";
+
+ WebView webView = createWebVewWithHtml(html);
+
+ // change navigation axis to word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("1"); // expect the word navigation axis
+
+ // go to the first word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("First");
+
+ // change navigation axis to heading
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("3"); // expect the heading navigation axis
+
+ // change navigation axis to sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("4"); // expect the sibling navigation axis
+
+ // go to the next sibling
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("<button>Second</button>");
+
+ // change navigation axis to character
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("0"); // expect the character navigation axis
+
+ // change navigation axis to word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
+ assertSelectionString("1"); // expect the word navigation axis
+
+ // go to the next word
+ sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
+ assertSelectionString("Third");
+ }
+
+ /**
+ * Enable accessibility and the mock accessibility service.
+ */
+ private void enableAccessibilityAndMockAccessibilityService() {
+ // make sure the manager is instantiated so the system initializes it
+ AccessibilityManager.getInstance(getContext());
+
+ // enable accessibility and the mock accessibility service
+ Settings.Secure.putInt(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 1);
+ String enabledServices = new ComponentName(getContext().getPackageName(),
+ MockAccessibilityService.class.getName()).flattenToShortString();
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
+
+ // poll within a timeout and let be interrupted in case of success
+ long incrementStep = TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE / 5;
+ long start = SystemClock.uptimeMillis();
+ while (SystemClock.uptimeMillis() - start < TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE &&
+ !sIsAccessibilityServiceReady) {
+ synchronized (sTestLock) {
+ try {
+ sTestLock.wait(incrementStep);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+
+ if (!sIsAccessibilityServiceReady) {
+ throw new IllegalStateException("MockAccessibilityService not ready. Did you add " +
+ "tests and forgot to update AccessibilityInjectorTest#TEST_CASE_COUNT?");
+ }
+ }
+
+ @Override
+ protected void scrubClass(Class<?> testCaseClass) {
+ /* do nothing - avoid superclass behavior */
+ }
+
+ /**
+ * Disables accessibility and the mock accessibility service.
+ */
+ private void disableAccessibilityAndMockAccessibilityService() {
+ // disable accessibility and the mock accessibility service
+ Settings.Secure.putInt(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ }
+
+ /**
+ * Asserts the next <code>expectedSelectionString</code> to be received.
+ */
+ private void assertSelectionString(String expectedSelectionString) {
+ assertTrue("MockAccessibilityService not ready", sIsAccessibilityServiceReady);
+
+ long incrementStep = TIMEOUT_WAIT_FOR_SELECTION_STRING / 5;
+ long start = SystemClock.uptimeMillis();
+ while (SystemClock.uptimeMillis() - start < TIMEOUT_WAIT_FOR_SELECTION_STRING &&
+ sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
+ synchronized (sTestLock) {
+ try {
+ sTestLock.wait(incrementStep);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ try {
+ if (sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
+ fail("No selection string received. Expected: " + expectedSelectionString);
+ }
+ assertEquals(expectedSelectionString, sReceivedSelectionString);
+ } finally {
+ sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
+ }
+ }
+
+ /**
+ * Sends a {@link KeyEvent} (up and down) to the {@link WebView}.
+ *
+ * @param keyCode The event key code.
+ */
+ private void sendKeyEvent(WebView webView, int keyCode, int metaState) {
+ webView.onKeyDown(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 1, metaState));
+ webView.onKeyUp(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 1, metaState));
+ }
+
+ /**
+ * Creates a {@link WebView} with with a given HTML content.
+ *
+ * @param html The HTML content;
+ * @return The created view.
+ */
+ private WebView createWebVewWithHtml(final String html) {
+ mWorker.getHandler().post(new Runnable() {
+ public void run() {
+ mWebView = new WebView(getContext());
+ mWebView.loadData(html, "text/html", "utf-8");
+ mWebView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ mWorker.getHandler().post(new Runnable() {
+ public void run() {
+ synchronized (sTestLock) {
+ sTestLock.notifyAll();
+ }
+ }
+ });
+ }
+ });
+ }
+ });
+ synchronized (sTestLock) {
+ try {
+ sTestLock.wait();
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ return mWebView;
+ }
+
+ /**
+ * This is a worker thread responsible for creating the {@link WebView}.
+ */
+ private class Worker implements Runnable {
+ private final Object mWorkerLock = new Object();
+ private Handler mHandler;
+
+ public Worker() {
+ new Thread(this).start();
+ synchronized (mWorkerLock) {
+ while (mHandler == null) {
+ try {
+ mWorkerLock.wait();
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ public void run() {
+ synchronized (mWorkerLock) {
+ Looper.prepare();
+ mHandler = new Handler();
+ mWorkerLock.notifyAll();
+ }
+ Looper.loop();
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ public void stop() {
+ mHandler.getLooper().quit();
+ }
+ }
+
+ /**
+ * Mock accessibility service to receive the accessibility events
+ * with the current {@link WebView} selection.
+ */
+ public static class MockAccessibilityService extends AccessibilityService {
+ private boolean mIsServiceInfoSet;
+
+ @Override
+ protected void onServiceConnected() {
+ if (mIsServiceInfoSet) {
+ return;
+ }
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPE_VIEW_SELECTED;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+ setServiceInfo(info);
+ mIsServiceInfoSet = true;
+
+ sIsAccessibilityServiceReady = true;
+
+ if (sInstance == null) {
+ return;
+ }
+ synchronized (sTestLock) {
+ sTestLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (sInstance == null) {
+ return;
+ }
+ if (!event.getText().isEmpty()) {
+ CharSequence text = event.getText().get(0);
+ sReceivedSelectionString = (text != null) ? text.toString() : null;
+ }
+ synchronized (sTestLock) {
+ sTestLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ sIsAccessibilityServiceReady = false;
+ return false;
+ }
+ }
+}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 2ae34e33ae05..43bb26ab37a4 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -74,4 +74,32 @@
<bool name="def_vibrate_in_silent">true</bool>
<bool name="def_use_ptp_interface">false</bool>
+
+ <!-- Default for Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION -->
+ <bool name="def_accessibility_script_injection">false</bool>
+
+ <!-- Default for Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS -->
+ <string name="def_accessibility_web_content_key_bindings">
+ <!-- DPAD/Trackball UP maps to traverse previous on current axis and send an event. -->
+ 0x13=0x01000100;
+ <!-- DPAD/Trackball DOWN maps to traverse next on current axis and send an event. -->
+ 0x14=0x01010100;
+ <!-- DPAD/Trackball LEFT maps to action in non-android default navigation axis. -->
+ 0x15=0x04000000;
+ <!-- DPAD/Trackball RIGHT maps to no action in non-android default navigation axis. -->
+ 0x16=0x04000000;
+ <!-- Left Alt+DPAD/Trackball UP transitions from an axis to another and sends an event. -->
+ <!-- Axis transitions: 2 -> 7; 1 -> 2; 0 -> 1; 3 -> 0; 4 -> 0; 5 -> 0; 6 -> 0; -->
+ 0x120013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;
+ <!-- Left Alt+DPAD/Trackball DOWN transitions from an axis to another and sends an event. -->
+ <!-- Axis transitions: 1 -> 0; 2 -> 1; 7 -> 2; 3 -> 7; 4 -> 7; 5 -> 7; 6 -> 7; -->
+ 0x120014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;
+ <!-- Left Alt+DPAD/Trackball LEFT transitions from an axis to another and sends an event. -->
+ <!-- Axis transitions: 4 -> 3; 5 -> 4; 6 -> 5; 0 -> 6; 1 -> 6; 2 -> 6; 7 -> 6; -->
+ 0x120015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;
+ <!-- Left Alt+DPAD/Trackball RIGHT transitions from an axis to another and sends an event. -->
+ <!-- Axis transitions: 5 -> 6; 4 -> 5; 3 -> 4; 2 -> 3; 7 -> 3; 1 -> 3; 0 -> 3; -->
+ 0x120016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;
+ </string>
+
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index c1ad1ca10dbe..8eb3fe69bf98 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -21,7 +21,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
@@ -35,10 +34,7 @@ import android.os.SystemProperties;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Config;
import android.util.Log;
-import android.util.Xml;
import com.android.internal.content.PackageHelper;
import com.android.internal.telephony.RILConstants;
@@ -64,7 +60,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 57;
+ private static final int DATABASE_VERSION = 58;
private Context mContext;
@@ -734,6 +730,33 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
upgradeVersion = 57;
}
+
+ if (upgradeVersion == 57) {
+ /*
+ * New settings to:
+ * 1. Enable injection of accessibility scripts in WebViews.
+ * 2. Define the key bindings for traversing web content in WebViews.
+ */
+ db.beginTransaction();
+ SQLiteStatement stmt = null;
+ try {
+ stmt = db.compileStatement("INSERT INTO secure(name,value)"
+ + " VALUES(?,?);");
+ loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ R.bool.def_accessibility_script_injection);
+ stmt.close();
+ stmt = db.compileStatement("INSERT INTO secure(name,value)"
+ + " VALUES(?,?);");
+ loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+ R.string.def_accessibility_web_content_key_bindings);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ if (stmt != null) stmt.close();
+ }
+ upgradeVersion = 58;
+ }
+
// *** Remember to update DATABASE_VERSION above!
if (upgradeVersion != currentVersion) {
@@ -876,7 +899,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
String cls = parser.getAttributeValue(null, "class");
String shortcutStr = parser.getAttributeValue(null, "shortcut");
- int shortcutValue = (int) shortcutStr.charAt(0);
+ int shortcutValue = shortcutStr.charAt(0);
if (TextUtils.isEmpty(shortcutStr)) {
Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
}
@@ -1187,6 +1210,12 @@ public class DatabaseHelper extends SQLiteOpenHelper {
loadBooleanSetting(stmt, Settings.Secure.MOUNT_UMS_NOTIFY_ENABLED,
R.bool.def_mount_ums_notify_enabled);
+
+ loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+ R.bool.def_accessibility_script_injection);
+
+ loadStringSetting(stmt, Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
+ R.string.def_accessibility_web_content_key_bindings);
} finally {
if (stmt != null) stmt.close();
}