1525823a7548d73c9ec9ba3ae84663ab286b20205alanv/* 2525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Copyright (C) 2010 The Android Open Source Project 3525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 4525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Licensed under the Apache License, Version 2.0 (the "License"); 5525823a7548d73c9ec9ba3ae84663ab286b20205alanv * you may not use this file except in compliance with the License. 6525823a7548d73c9ec9ba3ae84663ab286b20205alanv * You may obtain a copy of the License at 7525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 8525823a7548d73c9ec9ba3ae84663ab286b20205alanv * http://www.apache.org/licenses/LICENSE-2.0 9525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 10525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Unless required by applicable law or agreed to in writing, software 11525823a7548d73c9ec9ba3ae84663ab286b20205alanv * distributed under the License is distributed on an "AS IS" BASIS, 12525823a7548d73c9ec9ba3ae84663ab286b20205alanv * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13525823a7548d73c9ec9ba3ae84663ab286b20205alanv * See the License for the specific language governing permissions and 14525823a7548d73c9ec9ba3ae84663ab286b20205alanv * limitations under the License. 15525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 16525823a7548d73c9ec9ba3ae84663ab286b20205alanv 17525823a7548d73c9ec9ba3ae84663ab286b20205alanvpackage android.webkit; 18525823a7548d73c9ec9ba3ae84663ab286b20205alanv 196a62b77dfb95364e863b44662c13d6deec47f7d2alanvimport android.os.Bundle; 20525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.provider.Settings; 21525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.text.TextUtils; 22525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.text.TextUtils.SimpleStringSplitter; 23525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.util.Log; 24525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.view.KeyEvent; 25525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.view.accessibility.AccessibilityEvent; 26525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.view.accessibility.AccessibilityManager; 276a62b77dfb95364e863b44662c13d6deec47f7d2alanvimport android.view.accessibility.AccessibilityNodeInfo; 28525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport android.webkit.WebViewCore.EventHub; 29525823a7548d73c9ec9ba3ae84663ab286b20205alanv 30525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport java.util.ArrayList; 31525823a7548d73c9ec9ba3ae84663ab286b20205alanvimport java.util.Stack; 32525823a7548d73c9ec9ba3ae84663ab286b20205alanv 33525823a7548d73c9ec9ba3ae84663ab286b20205alanv/** 34525823a7548d73c9ec9ba3ae84663ab286b20205alanv * This class injects accessibility into WebViews with disabled JavaScript or 35525823a7548d73c9ec9ba3ae84663ab286b20205alanv * WebViews with enabled JavaScript but for which we have no accessibility 36525823a7548d73c9ec9ba3ae84663ab286b20205alanv * script to inject. 37525823a7548d73c9ec9ba3ae84663ab286b20205alanv * </p> 38525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Note: To avoid changes in the framework upon changing the available 39525823a7548d73c9ec9ba3ae84663ab286b20205alanv * navigation axis, or reordering the navigation axis, or changing 40525823a7548d73c9ec9ba3ae84663ab286b20205alanv * the key bindings, or defining sequence of actions to be bound to 41525823a7548d73c9ec9ba3ae84663ab286b20205alanv * a given key this class is navigation axis agnostic. It is only 42525823a7548d73c9ec9ba3ae84663ab286b20205alanv * aware of one navigation axis which is in fact the default behavior 43525823a7548d73c9ec9ba3ae84663ab286b20205alanv * of webViews while using the DPAD/TrackBall. 44525823a7548d73c9ec9ba3ae84663ab286b20205alanv * </p> 45525823a7548d73c9ec9ba3ae84663ab286b20205alanv * In general a key binding is a mapping from modifiers + key code to 46525823a7548d73c9ec9ba3ae84663ab286b20205alanv * a sequence of actions. For more detail how to specify key bindings refer to 47525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}. 48525823a7548d73c9ec9ba3ae84663ab286b20205alanv * </p> 49525823a7548d73c9ec9ba3ae84663ab286b20205alanv * The possible actions are invocations to 50525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #setCurrentAxis(int, boolean, String)}, or 51525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #traverseCurrentAxis(int, boolean, String)} 52525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #traverseGivenAxis(int, int, boolean, String)} 536a62b77dfb95364e863b44662c13d6deec47f7d2alanv * {@link #performAxisTransition(int, int, boolean, String)} 54525823a7548d73c9ec9ba3ae84663ab286b20205alanv * referred via the values of: 55525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #ACTION_SET_CURRENT_AXIS}, 56525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #ACTION_TRAVERSE_CURRENT_AXIS}, 57525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #ACTION_TRAVERSE_GIVEN_AXIS}, 58525823a7548d73c9ec9ba3ae84663ab286b20205alanv * {@link #ACTION_PERFORM_AXIS_TRANSITION}, 59525823a7548d73c9ec9ba3ae84663ab286b20205alanv * respectively. 60525823a7548d73c9ec9ba3ae84663ab286b20205alanv * The arguments for the action invocation are specified as offset 61525823a7548d73c9ec9ba3ae84663ab286b20205alanv * hexademical pairs. Note the last argument of the invocation 62525823a7548d73c9ec9ba3ae84663ab286b20205alanv * should NOT be specified in the binding as it is provided by 63525823a7548d73c9ec9ba3ae84663ab286b20205alanv * this class. For details about the key binding implementation 64525823a7548d73c9ec9ba3ae84663ab286b20205alanv * refer to {@link AccessibilityWebContentKeyBinding}. 65525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 66525823a7548d73c9ec9ba3ae84663ab286b20205alanvclass AccessibilityInjectorFallback { 67525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final String LOG_TAG = "AccessibilityInjector"; 68525823a7548d73c9ec9ba3ae84663ab286b20205alanv 69525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final boolean DEBUG = true; 70525823a7548d73c9ec9ba3ae84663ab286b20205alanv 71525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_SET_CURRENT_AXIS = 0; 72525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1; 73525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2; 74525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_PERFORM_AXIS_TRANSITION = 3; 75525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4; 76525823a7548d73c9ec9ba3ae84663ab286b20205alanv 776a62b77dfb95364e863b44662c13d6deec47f7d2alanv // WebView navigation axes from WebViewCore.h, plus an additional axis for 786a62b77dfb95364e863b44662c13d6deec47f7d2alanv // the default behavior. 796a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_CHARACTER = 0; 806a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_WORD = 1; 816a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_SENTENCE = 2; 826a62b77dfb95364e863b44662c13d6deec47f7d2alanv @SuppressWarnings("unused") 836a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_HEADING = 3; 846a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_SIBLING = 5; 856a62b77dfb95364e863b44662c13d6deec47f7d2alanv @SuppressWarnings("unused") 866a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5; 876a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_AXIS_DOCUMENT = 6; 88525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7; 89525823a7548d73c9ec9ba3ae84663ab286b20205alanv 906a62b77dfb95364e863b44662c13d6deec47f7d2alanv // WebView navigation directions from WebViewCore.h. 916a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_DIRECTION_BACKWARD = 0; 926a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static final int NAVIGATION_DIRECTION_FORWARD = 1; 936a62b77dfb95364e863b44662c13d6deec47f7d2alanv 94525823a7548d73c9ec9ba3ae84663ab286b20205alanv // these are the same for all instances so make them process wide 95525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static ArrayList<AccessibilityWebContentKeyBinding> sBindings = 96525823a7548d73c9ec9ba3ae84663ab286b20205alanv new ArrayList<AccessibilityWebContentKeyBinding>(); 97525823a7548d73c9ec9ba3ae84663ab286b20205alanv 98525823a7548d73c9ec9ba3ae84663ab286b20205alanv // handle to the WebViewClassic this injector is associated with. 99525823a7548d73c9ec9ba3ae84663ab286b20205alanv private final WebViewClassic mWebView; 1006a62b77dfb95364e863b44662c13d6deec47f7d2alanv private final WebView mWebViewInternal; 101525823a7548d73c9ec9ba3ae84663ab286b20205alanv 102525823a7548d73c9ec9ba3ae84663ab286b20205alanv // events scheduled for sending as soon as we receive the selected text 103525823a7548d73c9ec9ba3ae84663ab286b20205alanv private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>(); 104525823a7548d73c9ec9ba3ae84663ab286b20205alanv 105525823a7548d73c9ec9ba3ae84663ab286b20205alanv // the current traversal axis 106525823a7548d73c9ec9ba3ae84663ab286b20205alanv private int mCurrentAxis = 2; // sentence 107525823a7548d73c9ec9ba3ae84663ab286b20205alanv 108525823a7548d73c9ec9ba3ae84663ab286b20205alanv // we need to consume the up if we have handled the last down 109525823a7548d73c9ec9ba3ae84663ab286b20205alanv private boolean mLastDownEventHandled; 110525823a7548d73c9ec9ba3ae84663ab286b20205alanv 111525823a7548d73c9ec9ba3ae84663ab286b20205alanv // getting two empty selection strings in a row we let the WebView handle the event 112525823a7548d73c9ec9ba3ae84663ab286b20205alanv private boolean mIsLastSelectionStringNull; 113525823a7548d73c9ec9ba3ae84663ab286b20205alanv 114525823a7548d73c9ec9ba3ae84663ab286b20205alanv // keep track of last direction 115525823a7548d73c9ec9ba3ae84663ab286b20205alanv private int mLastDirection; 116525823a7548d73c9ec9ba3ae84663ab286b20205alanv 117525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 118525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Creates a new injector associated with a given {@link WebViewClassic}. 119525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 120525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param webView The associated WebViewClassic. 121525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 122525823a7548d73c9ec9ba3ae84663ab286b20205alanv public AccessibilityInjectorFallback(WebViewClassic webView) { 123525823a7548d73c9ec9ba3ae84663ab286b20205alanv mWebView = webView; 1246a62b77dfb95364e863b44662c13d6deec47f7d2alanv mWebViewInternal = mWebView.getWebView(); 125525823a7548d73c9ec9ba3ae84663ab286b20205alanv ensureWebContentKeyBindings(); 126525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 127525823a7548d73c9ec9ba3ae84663ab286b20205alanv 128525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 129525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Processes a key down <code>event</code>. 130525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 131525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @return True if the event was processed. 132525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 133525823a7548d73c9ec9ba3ae84663ab286b20205alanv public boolean onKeyEvent(KeyEvent event) { 134525823a7548d73c9ec9ba3ae84663ab286b20205alanv // We do not handle ENTER in any circumstances. 135525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (isEnterActionKey(event.getKeyCode())) { 136525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 137525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 138525823a7548d73c9ec9ba3ae84663ab286b20205alanv 139525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (event.getAction() == KeyEvent.ACTION_UP) { 140525823a7548d73c9ec9ba3ae84663ab286b20205alanv return mLastDownEventHandled; 141525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 142525823a7548d73c9ec9ba3ae84663ab286b20205alanv 143525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = false; 144525823a7548d73c9ec9ba3ae84663ab286b20205alanv 145525823a7548d73c9ec9ba3ae84663ab286b20205alanv AccessibilityWebContentKeyBinding binding = null; 146525823a7548d73c9ec9ba3ae84663ab286b20205alanv for (AccessibilityWebContentKeyBinding candidate : sBindings) { 147525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (event.getKeyCode() == candidate.getKeyCode() 148525823a7548d73c9ec9ba3ae84663ab286b20205alanv && event.hasModifiers(candidate.getModifiers())) { 149525823a7548d73c9ec9ba3ae84663ab286b20205alanv binding = candidate; 150525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 151525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 152525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 153525823a7548d73c9ec9ba3ae84663ab286b20205alanv 154525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (binding == null) { 155525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 156525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 157525823a7548d73c9ec9ba3ae84663ab286b20205alanv 158525823a7548d73c9ec9ba3ae84663ab286b20205alanv for (int i = 0, count = binding.getActionCount(); i < count; i++) { 159525823a7548d73c9ec9ba3ae84663ab286b20205alanv int actionCode = binding.getActionCode(i); 160525823a7548d73c9ec9ba3ae84663ab286b20205alanv String contentDescription = Integer.toHexString(binding.getAction(i)); 161525823a7548d73c9ec9ba3ae84663ab286b20205alanv switch (actionCode) { 162525823a7548d73c9ec9ba3ae84663ab286b20205alanv case ACTION_SET_CURRENT_AXIS: 163525823a7548d73c9ec9ba3ae84663ab286b20205alanv int axis = binding.getFirstArgument(i); 164525823a7548d73c9ec9ba3ae84663ab286b20205alanv boolean sendEvent = (binding.getSecondArgument(i) == 1); 165525823a7548d73c9ec9ba3ae84663ab286b20205alanv setCurrentAxis(axis, sendEvent, contentDescription); 166525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = true; 167525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 168525823a7548d73c9ec9ba3ae84663ab286b20205alanv case ACTION_TRAVERSE_CURRENT_AXIS: 169525823a7548d73c9ec9ba3ae84663ab286b20205alanv int direction = binding.getFirstArgument(i); 170525823a7548d73c9ec9ba3ae84663ab286b20205alanv // on second null selection string in same direction - WebView handles the event 171525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (direction == mLastDirection && mIsLastSelectionStringNull) { 172525823a7548d73c9ec9ba3ae84663ab286b20205alanv mIsLastSelectionStringNull = false; 173525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 174525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 175525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDirection = direction; 176525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendEvent = (binding.getSecondArgument(i) == 1); 177525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent, 178525823a7548d73c9ec9ba3ae84663ab286b20205alanv contentDescription); 179525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 180525823a7548d73c9ec9ba3ae84663ab286b20205alanv case ACTION_TRAVERSE_GIVEN_AXIS: 181525823a7548d73c9ec9ba3ae84663ab286b20205alanv direction = binding.getFirstArgument(i); 182525823a7548d73c9ec9ba3ae84663ab286b20205alanv // on second null selection string in same direction => WebView handle the event 183525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (direction == mLastDirection && mIsLastSelectionStringNull) { 184525823a7548d73c9ec9ba3ae84663ab286b20205alanv mIsLastSelectionStringNull = false; 185525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 186525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 187525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDirection = direction; 188525823a7548d73c9ec9ba3ae84663ab286b20205alanv axis = binding.getSecondArgument(i); 189525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendEvent = (binding.getThirdArgument(i) == 1); 190525823a7548d73c9ec9ba3ae84663ab286b20205alanv traverseGivenAxis(direction, axis, sendEvent, contentDescription); 191525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = true; 192525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 193525823a7548d73c9ec9ba3ae84663ab286b20205alanv case ACTION_PERFORM_AXIS_TRANSITION: 194525823a7548d73c9ec9ba3ae84663ab286b20205alanv int fromAxis = binding.getFirstArgument(i); 195525823a7548d73c9ec9ba3ae84663ab286b20205alanv int toAxis = binding.getSecondArgument(i); 196525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendEvent = (binding.getThirdArgument(i) == 1); 1976a62b77dfb95364e863b44662c13d6deec47f7d2alanv performAxisTransition(fromAxis, toAxis, sendEvent, contentDescription); 198525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = true; 199525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 200525823a7548d73c9ec9ba3ae84663ab286b20205alanv case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS: 201525823a7548d73c9ec9ba3ae84663ab286b20205alanv // This is a special case since we treat the default WebView navigation 202525823a7548d73c9ec9ba3ae84663ab286b20205alanv // behavior as one of the possible navigation axis the user can use. 203525823a7548d73c9ec9ba3ae84663ab286b20205alanv // If we are not on the default WebView navigation axis this is NOP. 204525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { 205525823a7548d73c9ec9ba3ae84663ab286b20205alanv // While WebVew handles navigation we do not get null selection 206525823a7548d73c9ec9ba3ae84663ab286b20205alanv // strings so do not check for that here as the cases above. 207525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDirection = binding.getFirstArgument(i); 208525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendEvent = (binding.getSecondArgument(i) == 1); 209525823a7548d73c9ec9ba3ae84663ab286b20205alanv traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR, 210525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendEvent, contentDescription); 211525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = false; 212525823a7548d73c9ec9ba3ae84663ab286b20205alanv } else { 213525823a7548d73c9ec9ba3ae84663ab286b20205alanv mLastDownEventHandled = true; 214525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 215525823a7548d73c9ec9ba3ae84663ab286b20205alanv break; 216525823a7548d73c9ec9ba3ae84663ab286b20205alanv default: 217525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.w(LOG_TAG, "Unknown action code: " + actionCode); 218525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 219525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 220525823a7548d73c9ec9ba3ae84663ab286b20205alanv 221525823a7548d73c9ec9ba3ae84663ab286b20205alanv return mLastDownEventHandled; 222525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 223525823a7548d73c9ec9ba3ae84663ab286b20205alanv 224525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 225525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Set the current navigation axis which will be used while 226525823a7548d73c9ec9ba3ae84663ab286b20205alanv * calling {@link #traverseCurrentAxis(int, boolean, String)}. 227525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 228525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param axis The axis to set. 229525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param sendEvent Whether to send an accessibility event to 230525823a7548d73c9ec9ba3ae84663ab286b20205alanv * announce the change. 231525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 232525823a7548d73c9ec9ba3ae84663ab286b20205alanv private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) { 233525823a7548d73c9ec9ba3ae84663ab286b20205alanv mCurrentAxis = axis; 234525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (sendEvent) { 2356a62b77dfb95364e863b44662c13d6deec47f7d2alanv final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent( 2366a62b77dfb95364e863b44662c13d6deec47f7d2alanv AccessibilityEvent.TYPE_ANNOUNCEMENT); 237525823a7548d73c9ec9ba3ae84663ab286b20205alanv event.getText().add(String.valueOf(axis)); 238525823a7548d73c9ec9ba3ae84663ab286b20205alanv event.setContentDescription(contentDescription); 239525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendAccessibilityEvent(event); 240525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 241525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 242525823a7548d73c9ec9ba3ae84663ab286b20205alanv 243525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 244525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Performs conditional transition one axis to another. 245525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 246525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param fromAxis The axis which must be the current for the transition to occur. 247525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param toAxis The axis to which to transition. 248525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param sendEvent Flag if to send an event to announce successful transition. 249525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param contentDescription A description of the performed action. 250525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 2516a62b77dfb95364e863b44662c13d6deec47f7d2alanv private void performAxisTransition(int fromAxis, int toAxis, boolean sendEvent, 252525823a7548d73c9ec9ba3ae84663ab286b20205alanv String contentDescription) { 253525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (mCurrentAxis == fromAxis) { 254525823a7548d73c9ec9ba3ae84663ab286b20205alanv setCurrentAxis(toAxis, sendEvent, contentDescription); 255525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 256525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 257525823a7548d73c9ec9ba3ae84663ab286b20205alanv 258525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 259525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Traverse the document along the current navigation axis. 260525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 261525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param direction The direction of traversal. 262525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param sendEvent Whether to send an accessibility event to 263525823a7548d73c9ec9ba3ae84663ab286b20205alanv * announce the change. 264525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param contentDescription A description of the performed action. 265525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @see #setCurrentAxis(int, boolean, String) 266525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 267525823a7548d73c9ec9ba3ae84663ab286b20205alanv private boolean traverseCurrentAxis(int direction, boolean sendEvent, 268525823a7548d73c9ec9ba3ae84663ab286b20205alanv String contentDescription) { 269525823a7548d73c9ec9ba3ae84663ab286b20205alanv return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription); 270525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 2716a62b77dfb95364e863b44662c13d6deec47f7d2alanv 2726a62b77dfb95364e863b44662c13d6deec47f7d2alanv boolean performAccessibilityAction(int action, Bundle arguments) { 2736a62b77dfb95364e863b44662c13d6deec47f7d2alanv switch (action) { 2746a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 2758bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { 2766a62b77dfb95364e863b44662c13d6deec47f7d2alanv final int direction = getDirectionForAction(action); 2776a62b77dfb95364e863b44662c13d6deec47f7d2alanv final int axis = getAxisForGranularity(arguments.getInt( 2786a62b77dfb95364e863b44662c13d6deec47f7d2alanv AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT)); 2796a62b77dfb95364e863b44662c13d6deec47f7d2alanv return traverseGivenAxis(direction, axis, true, null); 2808bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv } 2818bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: 2828bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: { 2838bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv final int direction = getDirectionForAction(action); 2848bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv // TODO: Add support for moving by object. 2858bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv final int axis = NAVIGATION_AXIS_SENTENCE; 2868bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv return traverseGivenAxis(direction, axis, true, null); 2878bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv } 2886a62b77dfb95364e863b44662c13d6deec47f7d2alanv default: 2896a62b77dfb95364e863b44662c13d6deec47f7d2alanv return false; 2906a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 2916a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 2926a62b77dfb95364e863b44662c13d6deec47f7d2alanv 2936a62b77dfb95364e863b44662c13d6deec47f7d2alanv /** 2946a62b77dfb95364e863b44662c13d6deec47f7d2alanv * Returns the {@link WebView}-defined direction for the given 2956a62b77dfb95364e863b44662c13d6deec47f7d2alanv * {@link AccessibilityNodeInfo}-defined action. 2966a62b77dfb95364e863b44662c13d6deec47f7d2alanv * 2976a62b77dfb95364e863b44662c13d6deec47f7d2alanv * @param action An accessibility action identifier. 2986a62b77dfb95364e863b44662c13d6deec47f7d2alanv * @return A web view navigation direction. 2996a62b77dfb95364e863b44662c13d6deec47f7d2alanv */ 3006a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static int getDirectionForAction(int action) { 3016a62b77dfb95364e863b44662c13d6deec47f7d2alanv switch (action) { 3028bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT: 3036a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 3046a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_DIRECTION_FORWARD; 3058bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: 3066a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: 3076a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_DIRECTION_BACKWARD; 3086a62b77dfb95364e863b44662c13d6deec47f7d2alanv default: 3096a62b77dfb95364e863b44662c13d6deec47f7d2alanv return -1; 3106a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 3116a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 3126a62b77dfb95364e863b44662c13d6deec47f7d2alanv 3136a62b77dfb95364e863b44662c13d6deec47f7d2alanv /** 3146a62b77dfb95364e863b44662c13d6deec47f7d2alanv * Returns the {@link WebView}-defined axis for the given 3156a62b77dfb95364e863b44662c13d6deec47f7d2alanv * {@link AccessibilityNodeInfo}-defined granularity. 3166a62b77dfb95364e863b44662c13d6deec47f7d2alanv * 3176a62b77dfb95364e863b44662c13d6deec47f7d2alanv * @param granularity An accessibility granularity identifier. 3186a62b77dfb95364e863b44662c13d6deec47f7d2alanv * @return A web view navigation axis. 3196a62b77dfb95364e863b44662c13d6deec47f7d2alanv */ 3206a62b77dfb95364e863b44662c13d6deec47f7d2alanv private static int getAxisForGranularity(int granularity) { 3216a62b77dfb95364e863b44662c13d6deec47f7d2alanv switch (granularity) { 3226a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: 3236a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_AXIS_CHARACTER; 3246a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: 3256a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_AXIS_WORD; 3266a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: 3276a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_AXIS_SENTENCE; 3286a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: 3298bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv // TODO: This should map to object once we implement it. 3308bab6de6e2c3af2d6fac0ebd06bcdb71a5d65b03alanv return NAVIGATION_AXIS_SENTENCE; 3316a62b77dfb95364e863b44662c13d6deec47f7d2alanv case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: 3326a62b77dfb95364e863b44662c13d6deec47f7d2alanv return NAVIGATION_AXIS_DOCUMENT; 3336a62b77dfb95364e863b44662c13d6deec47f7d2alanv default: 3346a62b77dfb95364e863b44662c13d6deec47f7d2alanv return -1; 3356a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 3366a62b77dfb95364e863b44662c13d6deec47f7d2alanv } 337525823a7548d73c9ec9ba3ae84663ab286b20205alanv 338525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 339525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Traverse the document along the given navigation axis. 340525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 341525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param direction The direction of traversal. 342525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param axis The axis along which to traverse. 343525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param sendEvent Whether to send an accessibility event to 344525823a7548d73c9ec9ba3ae84663ab286b20205alanv * announce the change. 345525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param contentDescription A description of the performed action. 346525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 347525823a7548d73c9ec9ba3ae84663ab286b20205alanv private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent, 348525823a7548d73c9ec9ba3ae84663ab286b20205alanv String contentDescription) { 349525823a7548d73c9ec9ba3ae84663ab286b20205alanv WebViewCore webViewCore = mWebView.getWebViewCore(); 350525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (webViewCore == null) { 351525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 352525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 353525823a7548d73c9ec9ba3ae84663ab286b20205alanv 354525823a7548d73c9ec9ba3ae84663ab286b20205alanv AccessibilityEvent event = null; 355525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (sendEvent) { 3566a62b77dfb95364e863b44662c13d6deec47f7d2alanv event = getPartialyPopulatedAccessibilityEvent( 3576a62b77dfb95364e863b44662c13d6deec47f7d2alanv AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY); 358525823a7548d73c9ec9ba3ae84663ab286b20205alanv // the text will be set upon receiving the selection string 359525823a7548d73c9ec9ba3ae84663ab286b20205alanv event.setContentDescription(contentDescription); 360525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 361525823a7548d73c9ec9ba3ae84663ab286b20205alanv mScheduledEventStack.push(event); 362525823a7548d73c9ec9ba3ae84663ab286b20205alanv 363525823a7548d73c9ec9ba3ae84663ab286b20205alanv // if the axis is the default let WebView handle the event which will 364525823a7548d73c9ec9ba3ae84663ab286b20205alanv // result in cursor ring movement and selection of its content 365525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) { 366525823a7548d73c9ec9ba3ae84663ab286b20205alanv return false; 367525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 368525823a7548d73c9ec9ba3ae84663ab286b20205alanv 369525823a7548d73c9ec9ba3ae84663ab286b20205alanv webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis); 370525823a7548d73c9ec9ba3ae84663ab286b20205alanv return true; 371525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 372525823a7548d73c9ec9ba3ae84663ab286b20205alanv 373525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 374525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Called when the <code>selectionString</code> has changed. 375525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 376525823a7548d73c9ec9ba3ae84663ab286b20205alanv public void onSelectionStringChange(String selectionString) { 377525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (DEBUG) { 378525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.d(LOG_TAG, "Selection string: " + selectionString); 379525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 380525823a7548d73c9ec9ba3ae84663ab286b20205alanv mIsLastSelectionStringNull = (selectionString == null); 381525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (mScheduledEventStack.isEmpty()) { 382525823a7548d73c9ec9ba3ae84663ab286b20205alanv return; 383525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 384525823a7548d73c9ec9ba3ae84663ab286b20205alanv AccessibilityEvent event = mScheduledEventStack.pop(); 3856a62b77dfb95364e863b44662c13d6deec47f7d2alanv if ((event != null) && (selectionString != null)) { 386525823a7548d73c9ec9ba3ae84663ab286b20205alanv event.getText().add(selectionString); 3876a62b77dfb95364e863b44662c13d6deec47f7d2alanv event.setFromIndex(0); 3886a62b77dfb95364e863b44662c13d6deec47f7d2alanv event.setToIndex(selectionString.length()); 389525823a7548d73c9ec9ba3ae84663ab286b20205alanv sendAccessibilityEvent(event); 390525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 391525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 392525823a7548d73c9ec9ba3ae84663ab286b20205alanv 393525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 394525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Sends an {@link AccessibilityEvent}. 395525823a7548d73c9ec9ba3ae84663ab286b20205alanv * 396525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param event The event to send. 397525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 398525823a7548d73c9ec9ba3ae84663ab286b20205alanv private void sendAccessibilityEvent(AccessibilityEvent event) { 399525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (DEBUG) { 400525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.d(LOG_TAG, "Dispatching: " + event); 401525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 402525823a7548d73c9ec9ba3ae84663ab286b20205alanv // accessibility may be disabled while waiting for the selection string 403525823a7548d73c9ec9ba3ae84663ab286b20205alanv AccessibilityManager accessibilityManager = 404525823a7548d73c9ec9ba3ae84663ab286b20205alanv AccessibilityManager.getInstance(mWebView.getContext()); 405525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (accessibilityManager.isEnabled()) { 406525823a7548d73c9ec9ba3ae84663ab286b20205alanv accessibilityManager.sendAccessibilityEvent(event); 407525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 408525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 409525823a7548d73c9ec9ba3ae84663ab286b20205alanv 410525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 411525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @return An accessibility event whose members are populated except its 412525823a7548d73c9ec9ba3ae84663ab286b20205alanv * text and content description. 413525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 4146a62b77dfb95364e863b44662c13d6deec47f7d2alanv private AccessibilityEvent getPartialyPopulatedAccessibilityEvent(int eventType) { 4156a62b77dfb95364e863b44662c13d6deec47f7d2alanv AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 4166a62b77dfb95364e863b44662c13d6deec47f7d2alanv mWebViewInternal.onInitializeAccessibilityEvent(event); 417525823a7548d73c9ec9ba3ae84663ab286b20205alanv return event; 418525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 419525823a7548d73c9ec9ba3ae84663ab286b20205alanv 420525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 421525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Ensures that the Web content key bindings are loaded. 422525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 423525823a7548d73c9ec9ba3ae84663ab286b20205alanv private void ensureWebContentKeyBindings() { 424525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (sBindings.size() > 0) { 425525823a7548d73c9ec9ba3ae84663ab286b20205alanv return; 426525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 427525823a7548d73c9ec9ba3ae84663ab286b20205alanv 428525823a7548d73c9ec9ba3ae84663ab286b20205alanv String webContentKeyBindingsString = Settings.Secure.getString( 429525823a7548d73c9ec9ba3ae84663ab286b20205alanv mWebView.getContext().getContentResolver(), 430525823a7548d73c9ec9ba3ae84663ab286b20205alanv Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS); 431525823a7548d73c9ec9ba3ae84663ab286b20205alanv 432525823a7548d73c9ec9ba3ae84663ab286b20205alanv SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';'); 433525823a7548d73c9ec9ba3ae84663ab286b20205alanv semiColonSplitter.setString(webContentKeyBindingsString); 434525823a7548d73c9ec9ba3ae84663ab286b20205alanv 435525823a7548d73c9ec9ba3ae84663ab286b20205alanv while (semiColonSplitter.hasNext()) { 436525823a7548d73c9ec9ba3ae84663ab286b20205alanv String bindingString = semiColonSplitter.next(); 437525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (TextUtils.isEmpty(bindingString)) { 438525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " 439525823a7548d73c9ec9ba3ae84663ab286b20205alanv + webContentKeyBindingsString); 440525823a7548d73c9ec9ba3ae84663ab286b20205alanv continue; 441525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 442525823a7548d73c9ec9ba3ae84663ab286b20205alanv String[] keyValueArray = bindingString.split("="); 443525823a7548d73c9ec9ba3ae84663ab286b20205alanv if (keyValueArray.length != 2) { 444525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString); 445525823a7548d73c9ec9ba3ae84663ab286b20205alanv continue; 446525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 447525823a7548d73c9ec9ba3ae84663ab286b20205alanv try { 448525823a7548d73c9ec9ba3ae84663ab286b20205alanv long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim()); 449525823a7548d73c9ec9ba3ae84663ab286b20205alanv String[] actionStrings = keyValueArray[1].split(":"); 450525823a7548d73c9ec9ba3ae84663ab286b20205alanv int[] actions = new int[actionStrings.length]; 451525823a7548d73c9ec9ba3ae84663ab286b20205alanv for (int i = 0, count = actions.length; i < count; i++) { 452525823a7548d73c9ec9ba3ae84663ab286b20205alanv actions[i] = Integer.decode(actionStrings[i].trim()); 453525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 454525823a7548d73c9ec9ba3ae84663ab286b20205alanv sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions)); 455525823a7548d73c9ec9ba3ae84663ab286b20205alanv } catch (NumberFormatException nfe) { 456525823a7548d73c9ec9ba3ae84663ab286b20205alanv Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString); 457525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 458525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 459525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 460525823a7548d73c9ec9ba3ae84663ab286b20205alanv 461525823a7548d73c9ec9ba3ae84663ab286b20205alanv private boolean isEnterActionKey(int keyCode) { 462525823a7548d73c9ec9ba3ae84663ab286b20205alanv return keyCode == KeyEvent.KEYCODE_DPAD_CENTER 463525823a7548d73c9ec9ba3ae84663ab286b20205alanv || keyCode == KeyEvent.KEYCODE_ENTER 464525823a7548d73c9ec9ba3ae84663ab286b20205alanv || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER; 465525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 466525823a7548d73c9ec9ba3ae84663ab286b20205alanv 467525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 468525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Represents a web content key-binding. 469525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 470525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final class AccessibilityWebContentKeyBinding { 471525823a7548d73c9ec9ba3ae84663ab286b20205alanv 472525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int MODIFIERS_OFFSET = 32; 473525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L; 474525823a7548d73c9ec9ba3ae84663ab286b20205alanv 475525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int KEY_CODE_OFFSET = 0; 476525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL; 477525823a7548d73c9ec9ba3ae84663ab286b20205alanv 478525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_OFFSET = 24; 479525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int ACTION_MASK = 0xFF000000; 480525823a7548d73c9ec9ba3ae84663ab286b20205alanv 481525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int FIRST_ARGUMENT_OFFSET = 16; 482525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int FIRST_ARGUMENT_MASK = 0x00FF0000; 483525823a7548d73c9ec9ba3ae84663ab286b20205alanv 484525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int SECOND_ARGUMENT_OFFSET = 8; 485525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int SECOND_ARGUMENT_MASK = 0x0000FF00; 486525823a7548d73c9ec9ba3ae84663ab286b20205alanv 487525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int THIRD_ARGUMENT_OFFSET = 0; 488525823a7548d73c9ec9ba3ae84663ab286b20205alanv private static final int THIRD_ARGUMENT_MASK = 0x000000FF; 489525823a7548d73c9ec9ba3ae84663ab286b20205alanv 490525823a7548d73c9ec9ba3ae84663ab286b20205alanv private final long mKeyCodeAndModifiers; 491525823a7548d73c9ec9ba3ae84663ab286b20205alanv 492525823a7548d73c9ec9ba3ae84663ab286b20205alanv private final int [] mActionSequence; 493525823a7548d73c9ec9ba3ae84663ab286b20205alanv 494525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 495525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @return The key code of the binding key. 496525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 497525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getKeyCode() { 498525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET); 499525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 500525823a7548d73c9ec9ba3ae84663ab286b20205alanv 501525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 502525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @return The meta state of the binding key. 503525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 504525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getModifiers() { 505525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET); 506525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 507525823a7548d73c9ec9ba3ae84663ab286b20205alanv 508525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 509525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @return The number of actions in the key binding. 510525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 511525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getActionCount() { 512525823a7548d73c9ec9ba3ae84663ab286b20205alanv return mActionSequence.length; 513525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 514525823a7548d73c9ec9ba3ae84663ab286b20205alanv 515525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 516525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param index The action for a given action <code>index</code>. 517525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 518525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getAction(int index) { 519525823a7548d73c9ec9ba3ae84663ab286b20205alanv return mActionSequence[index]; 520525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 521525823a7548d73c9ec9ba3ae84663ab286b20205alanv 522525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 523525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param index The action code for a given action <code>index</code>. 524525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 525525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getActionCode(int index) { 526525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET; 527525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 528525823a7548d73c9ec9ba3ae84663ab286b20205alanv 529525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 530525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param index The first argument for a given action <code>index</code>. 531525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 532525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getFirstArgument(int index) { 533525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET; 534525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 535525823a7548d73c9ec9ba3ae84663ab286b20205alanv 536525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 537525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param index The second argument for a given action <code>index</code>. 538525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 539525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getSecondArgument(int index) { 540525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET; 541525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 542525823a7548d73c9ec9ba3ae84663ab286b20205alanv 543525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 544525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param index The third argument for a given action <code>index</code>. 545525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 546525823a7548d73c9ec9ba3ae84663ab286b20205alanv public int getThirdArgument(int index) { 547525823a7548d73c9ec9ba3ae84663ab286b20205alanv return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET; 548525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 549525823a7548d73c9ec9ba3ae84663ab286b20205alanv 550525823a7548d73c9ec9ba3ae84663ab286b20205alanv /** 551525823a7548d73c9ec9ba3ae84663ab286b20205alanv * Creates a new instance. 552525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param keyCodeAndModifiers The key for the binding (key and modifiers). 553525823a7548d73c9ec9ba3ae84663ab286b20205alanv * @param actionSequence The sequence of action for the binding. 554525823a7548d73c9ec9ba3ae84663ab286b20205alanv */ 555525823a7548d73c9ec9ba3ae84663ab286b20205alanv public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) { 556525823a7548d73c9ec9ba3ae84663ab286b20205alanv mKeyCodeAndModifiers = keyCodeAndModifiers; 557525823a7548d73c9ec9ba3ae84663ab286b20205alanv mActionSequence = actionSequence; 558525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 559525823a7548d73c9ec9ba3ae84663ab286b20205alanv 560525823a7548d73c9ec9ba3ae84663ab286b20205alanv @Override 561525823a7548d73c9ec9ba3ae84663ab286b20205alanv public String toString() { 562525823a7548d73c9ec9ba3ae84663ab286b20205alanv StringBuilder builder = new StringBuilder(); 563525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append("modifiers: "); 564525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getModifiers()); 565525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(", keyCode: "); 566525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getKeyCode()); 567525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(", actions["); 568525823a7548d73c9ec9ba3ae84663ab286b20205alanv for (int i = 0, count = getActionCount(); i < count; i++) { 569525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append("{actionCode"); 570525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(i); 571525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(": "); 572525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getActionCode(i)); 573525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(", firstArgument: "); 574525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getFirstArgument(i)); 575525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(", secondArgument: "); 576525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getSecondArgument(i)); 577525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(", thirdArgument: "); 578525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append(getThirdArgument(i)); 579525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append("}"); 580525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 581525823a7548d73c9ec9ba3ae84663ab286b20205alanv builder.append("]"); 582525823a7548d73c9ec9ba3ae84663ab286b20205alanv return builder.toString(); 583525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 584525823a7548d73c9ec9ba3ae84663ab286b20205alanv } 585525823a7548d73c9ec9ba3ae84663ab286b20205alanv} 586