Loading core/java/android/provider/Settings.java +59 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -2526,6 +2529,60 @@ public final class Settings { public static final String ENABLED_ACCESSIBILITY_SERVICES = "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. Loading Loading @@ -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, Loading core/java/android/webkit/AccessibilityInjector.java +414 −29 Original line number Diff line number Diff line Loading @@ -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; // 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. // 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(); } /** Loading @@ -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; } // as a proof of concept let us do the simplest example mLastDownEventHandled = false; if (event.getAction() != KeyEvent.ACTION_UP) { int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE | event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE; AccessibilityWebContentKeyBinding binding = sBindings.get(key); if (binding == null) { return false; } int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_N: modifySelection("extend", "forward", "sentence"); 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 KeyEvent.KEYCODE_P: modifySelection("extend", "backward", "sentence"); 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; } /** * 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); } } /** * 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); mIsLastSelectionStringNull = (selectionString == null); AccessibilityEvent event = mScheduledEventStack.pop(); if (event != null) { event.getText().add(selectionString); mWebView.sendAccessibilityEventUnchecked(event); 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); } /** * @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; } if (webViewCore == null) { /** * 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(); } } } core/java/android/webkit/CallbackProxy.java +3 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading
core/java/android/provider/Settings.java +59 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -2526,6 +2529,60 @@ public final class Settings { public static final String ENABLED_ACCESSIBILITY_SERVICES = "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. Loading Loading @@ -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, Loading
core/java/android/webkit/AccessibilityInjector.java +414 −29 Original line number Diff line number Diff line Loading @@ -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; // 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. // 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(); } /** Loading @@ -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; } // as a proof of concept let us do the simplest example mLastDownEventHandled = false; if (event.getAction() != KeyEvent.ACTION_UP) { int key = event.getMetaState() << AccessibilityWebContentKeyBinding.OFFSET_META_STATE | event.getKeyCode() << AccessibilityWebContentKeyBinding.OFFSET_KEY_CODE; AccessibilityWebContentKeyBinding binding = sBindings.get(key); if (binding == null) { return false; } int keyCode = event.getKeyCode(); switch (keyCode) { case KeyEvent.KEYCODE_N: modifySelection("extend", "forward", "sentence"); 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 KeyEvent.KEYCODE_P: modifySelection("extend", "backward", "sentence"); 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; } /** * 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); } } /** * 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); mIsLastSelectionStringNull = (selectionString == null); AccessibilityEvent event = mScheduledEventStack.pop(); if (event != null) { event.getText().add(selectionString); mWebView.sendAccessibilityEventUnchecked(event); 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); } /** * @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; } if (webViewCore == null) { /** * 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(); } } }
core/java/android/webkit/CallbackProxy.java +3 −10 Original line number Diff line number Diff line Loading @@ -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; Loading