blob: db6630531d01793b92ab323afe1f25730c5fea7e [file] [log] [blame]
Svetoslav Ganovda355512010-05-12 22:04:44 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070019import android.provider.Settings;
20import android.text.TextUtils;
21import android.text.TextUtils.SimpleStringSplitter;
22import android.util.Log;
Svetoslav Ganovda355512010-05-12 22:04:44 -070023import android.view.KeyEvent;
24import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070025import android.view.accessibility.AccessibilityManager;
Svetoslav Ganovda355512010-05-12 22:04:44 -070026import android.webkit.WebViewCore.EventHub;
27
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070028import java.util.ArrayList;
29import java.util.Stack;
30
Svetoslav Ganovda355512010-05-12 22:04:44 -070031/**
32 * This class injects accessibility into WebViews with disabled JavaScript or
33 * WebViews with enabled JavaScript but for which we have no accessibility
34 * script to inject.
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070035 * </p>
36 * Note: To avoid changes in the framework upon changing the available
37 * navigation axis, or reordering the navigation axis, or changing
38 * the key bindings, or defining sequence of actions to be bound to
39 * a given key this class is navigation axis agnostic. It is only
40 * aware of one navigation axis which is in fact the default behavior
41 * of webViews while using the DPAD/TrackBall.
42 * </p>
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -080043 * In general a key binding is a mapping from modifiers + key code to
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070044 * a sequence of actions. For more detail how to specify key bindings refer to
45 * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
46 * </p>
47 * The possible actions are invocations to
48 * {@link #setCurrentAxis(int, boolean, String)}, or
49 * {@link #traverseCurrentAxis(int, boolean, String)}
50 * {@link #traverseGivenAxis(int, int, boolean, String)}
51 * {@link #prefromAxisTransition(int, int, boolean, String)}
52 * referred via the values of:
53 * {@link #ACTION_SET_CURRENT_AXIS},
54 * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
55 * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
56 * {@link #ACTION_PERFORM_AXIS_TRANSITION},
57 * respectively.
58 * The arguments for the action invocation are specified as offset
59 * hexademical pairs. Note the last argument of the invocation
60 * should NOT be specified in the binding as it is provided by
61 * this class. For details about the key binding implementation
62 * refer to {@link AccessibilityWebContentKeyBinding}.
Svetoslav Ganovda355512010-05-12 22:04:44 -070063 */
64class AccessibilityInjector {
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070065 private static final String LOG_TAG = "AccessibilityInjector";
Svetoslav Ganovda355512010-05-12 22:04:44 -070066
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070067 private static final boolean DEBUG = true;
68
69 private static final int ACTION_SET_CURRENT_AXIS = 0;
70 private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
71 private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
72 private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
Svetoslav Ganovc93fb652011-01-05 18:52:05 -080073 private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070074
75 // the default WebView behavior abstracted as a navigation axis
76 private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
77
78 // these are the same for all instances so make them process wide
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -080079 private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
80 new ArrayList<AccessibilityWebContentKeyBinding>();
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070081
82 // handle to the WebView this injector is associated with.
Svetoslav Ganovda355512010-05-12 22:04:44 -070083 private final WebView mWebView;
84
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -070085 // events scheduled for sending as soon as we receive the selected text
86 private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
87
88 // the current traversal axis
89 private int mCurrentAxis = 2; // sentence
90
91 // we need to consume the up if we have handled the last down
92 private boolean mLastDownEventHandled;
93
94 // getting two empty selection strings in a row we let the WebView handle the event
95 private boolean mIsLastSelectionStringNull;
96
97 // keep track of last direction
98 private int mLastDirection;
99
Svetoslav Ganovda355512010-05-12 22:04:44 -0700100 /**
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700101 * Creates a new injector associated with a given {@link WebView}.
Svetoslav Ganovda355512010-05-12 22:04:44 -0700102 *
103 * @param webView The associated WebView.
104 */
105 public AccessibilityInjector(WebView webView) {
106 mWebView = webView;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700107 ensureWebContentKeyBindings();
Svetoslav Ganovda355512010-05-12 22:04:44 -0700108 }
109
110 /**
111 * Processes a key down <code>event</code>.
112 *
113 * @return True if the event was processed.
114 */
115 public boolean onKeyEvent(KeyEvent event) {
Svetoslav Ganov9504f572011-01-14 11:38:17 -0800116 // We do not handle ENTER in any circumstances.
117 if (isEnterActionKey(event.getKeyCode())) {
118 return false;
119 }
120
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700121 if (event.getAction() == KeyEvent.ACTION_UP) {
122 return mLastDownEventHandled;
123 }
Svetoslav Ganovda355512010-05-12 22:04:44 -0700124
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700125 mLastDownEventHandled = false;
Svetoslav Ganovda355512010-05-12 22:04:44 -0700126
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800127 AccessibilityWebContentKeyBinding binding = null;
128 for (AccessibilityWebContentKeyBinding candidate : sBindings) {
129 if (event.getKeyCode() == candidate.getKeyCode()
130 && event.hasModifiers(candidate.getModifiers())) {
131 binding = candidate;
132 break;
133 }
134 }
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700135
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700136 if (binding == null) {
Svetoslav Ganovda355512010-05-12 22:04:44 -0700137 return false;
138 }
139
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700140 for (int i = 0, count = binding.getActionCount(); i < count; i++) {
141 int actionCode = binding.getActionCode(i);
142 String contentDescription = Integer.toHexString(binding.getAction(i));
143 switch (actionCode) {
144 case ACTION_SET_CURRENT_AXIS:
145 int axis = binding.getFirstArgument(i);
146 boolean sendEvent = (binding.getSecondArgument(i) == 1);
147 setCurrentAxis(axis, sendEvent, contentDescription);
148 mLastDownEventHandled = true;
149 break;
150 case ACTION_TRAVERSE_CURRENT_AXIS:
151 int direction = binding.getFirstArgument(i);
Svetoslav Ganov12bed782011-01-03 14:14:50 -0800152 // on second null selection string in same direction - WebView handles the event
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700153 if (direction == mLastDirection && mIsLastSelectionStringNull) {
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700154 mIsLastSelectionStringNull = false;
155 return false;
156 }
157 mLastDirection = direction;
158 sendEvent = (binding.getSecondArgument(i) == 1);
159 mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
160 contentDescription);
161 break;
162 case ACTION_TRAVERSE_GIVEN_AXIS:
163 direction = binding.getFirstArgument(i);
164 // on second null selection string in same direction => WebView handle the event
165 if (direction == mLastDirection && mIsLastSelectionStringNull) {
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700166 mIsLastSelectionStringNull = false;
167 return false;
168 }
169 mLastDirection = direction;
170 axis = binding.getSecondArgument(i);
171 sendEvent = (binding.getThirdArgument(i) == 1);
172 traverseGivenAxis(direction, axis, sendEvent, contentDescription);
173 mLastDownEventHandled = true;
174 break;
175 case ACTION_PERFORM_AXIS_TRANSITION:
176 int fromAxis = binding.getFirstArgument(i);
177 int toAxis = binding.getSecondArgument(i);
178 sendEvent = (binding.getThirdArgument(i) == 1);
179 prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
180 mLastDownEventHandled = true;
181 break;
Svetoslav Ganovc93fb652011-01-05 18:52:05 -0800182 case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
183 // This is a special case since we treat the default WebView navigation
184 // behavior as one of the possible navigation axis the user can use.
185 // If we are not on the default WebView navigation axis this is NOP.
186 if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
187 // While WebVew handles navigation we do not get null selection
188 // strings so do not check for that here as the cases above.
189 mLastDirection = binding.getFirstArgument(i);
190 sendEvent = (binding.getSecondArgument(i) == 1);
191 traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
192 sendEvent, contentDescription);
193 mLastDownEventHandled = false;
194 } else {
195 mLastDownEventHandled = true;
196 }
197 break;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700198 default:
199 Log.w(LOG_TAG, "Unknown action code: " + actionCode);
200 }
Svetoslav Ganovda355512010-05-12 22:04:44 -0700201 }
202
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700203 return mLastDownEventHandled;
204 }
205
206 /**
207 * Set the current navigation axis which will be used while
208 * calling {@link #traverseCurrentAxis(int, boolean, String)}.
209 *
210 * @param axis The axis to set.
211 * @param sendEvent Whether to send an accessibility event to
212 * announce the change.
213 */
214 private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
215 mCurrentAxis = axis;
216 if (sendEvent) {
217 AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
218 event.getText().add(String.valueOf(axis));
219 event.setContentDescription(contentDescription);
220 sendAccessibilityEvent(event);
221 }
222 }
223
224 /**
225 * Performs conditional transition one axis to another.
226 *
227 * @param fromAxis The axis which must be the current for the transition to occur.
228 * @param toAxis The axis to which to transition.
229 * @param sendEvent Flag if to send an event to announce successful transition.
230 * @param contentDescription A description of the performed action.
231 */
232 private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
233 String contentDescription) {
234 if (mCurrentAxis == fromAxis) {
235 setCurrentAxis(toAxis, sendEvent, contentDescription);
236 }
237 }
238
239 /**
240 * Traverse the document along the current navigation axis.
241 *
242 * @param direction The direction of traversal.
243 * @param sendEvent Whether to send an accessibility event to
244 * announce the change.
245 * @param contentDescription A description of the performed action.
246 * @see #setCurrentAxis(int, boolean, String)
247 */
248 private boolean traverseCurrentAxis(int direction, boolean sendEvent,
249 String contentDescription) {
250 return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
251 }
252
253 /**
254 * Traverse the document along the given navigation axis.
255 *
256 * @param direction The direction of traversal.
257 * @param axis The axis along which to traverse.
258 * @param sendEvent Whether to send an accessibility event to
259 * announce the change.
260 * @param contentDescription A description of the performed action.
261 */
262 private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
263 String contentDescription) {
Svetoslav Ganovc93fb652011-01-05 18:52:05 -0800264 WebViewCore webViewCore = mWebView.getWebViewCore();
265 if (webViewCore == null) {
266 return false;
267 }
268
269 AccessibilityEvent event = null;
270 if (sendEvent) {
271 event = getPartialyPopulatedAccessibilityEvent();
272 // the text will be set upon receiving the selection string
273 event.setContentDescription(contentDescription);
274 }
275 mScheduledEventStack.push(event);
276
277 // if the axis is the default let WebView handle the event which will
278 // result in cursor ring movement and selection of its content
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700279 if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
280 return false;
281 }
Svetoslav Ganovc93fb652011-01-05 18:52:05 -0800282
283 webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700284 return true;
Svetoslav Ganovda355512010-05-12 22:04:44 -0700285 }
286
287 /**
288 * Called when the <code>selectionString</code> has changed.
289 */
290 public void onSelectionStringChange(String selectionString) {
Svetoslav Ganov12bed782011-01-03 14:14:50 -0800291 if (DEBUG) {
292 Log.d(LOG_TAG, "Selection string: " + selectionString);
293 }
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700294 mIsLastSelectionStringNull = (selectionString == null);
Svetoslav Ganov12bed782011-01-03 14:14:50 -0800295 if (mScheduledEventStack.isEmpty()) {
296 return;
297 }
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700298 AccessibilityEvent event = mScheduledEventStack.pop();
299 if (event != null) {
300 event.getText().add(selectionString);
301 sendAccessibilityEvent(event);
302 }
Svetoslav Ganovda355512010-05-12 22:04:44 -0700303 }
304
305 /**
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700306 * Sends an {@link AccessibilityEvent}.
Svetoslav Ganovda355512010-05-12 22:04:44 -0700307 *
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700308 * @param event The event to send.
Svetoslav Ganovda355512010-05-12 22:04:44 -0700309 */
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700310 private void sendAccessibilityEvent(AccessibilityEvent event) {
311 if (DEBUG) {
312 Log.d(LOG_TAG, "Dispatching: " + event);
313 }
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800314 // accessibility may be disabled while waiting for the selection string
315 AccessibilityManager accessibilityManager =
316 AccessibilityManager.getInstance(mWebView.getContext());
317 if (accessibilityManager.isEnabled()) {
318 accessibilityManager.sendAccessibilityEvent(event);
319 }
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700320 }
Svetoslav Ganovda355512010-05-12 22:04:44 -0700321
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700322 /**
323 * @return An accessibility event whose members are populated except its
324 * text and content description.
325 */
326 private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
327 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
328 event.setClassName(mWebView.getClass().getName());
329 event.setPackageName(mWebView.getContext().getPackageName());
330 event.setEnabled(mWebView.isEnabled());
331 return event;
332 }
333
334 /**
335 * Ensures that the Web content key bindings are loaded.
336 */
337 private void ensureWebContentKeyBindings() {
338 if (sBindings.size() > 0) {
Svetoslav Ganovda355512010-05-12 22:04:44 -0700339 return;
340 }
341
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700342 String webContentKeyBindingsString = Settings.Secure.getString(
343 mWebView.getContext().getContentResolver(),
344 Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
345
346 SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
347 semiColonSplitter.setString(webContentKeyBindingsString);
348
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700349 while (semiColonSplitter.hasNext()) {
350 String bindingString = semiColonSplitter.next();
351 if (TextUtils.isEmpty(bindingString)) {
Svetoslav Ganov12bed782011-01-03 14:14:50 -0800352 Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700353 + webContentKeyBindingsString);
354 continue;
355 }
356 String[] keyValueArray = bindingString.split("=");
357 if (keyValueArray.length != 2) {
Svetoslav Ganov12bed782011-01-03 14:14:50 -0800358 Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700359 continue;
360 }
361 try {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800362 long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700363 String[] actionStrings = keyValueArray[1].split(":");
364 int[] actions = new int[actionStrings.length];
365 for (int i = 0, count = actions.length; i < count; i++) {
366 actions[i] = Integer.decode(actionStrings[i].trim());
367 }
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800368 sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700369 } catch (NumberFormatException nfe) {
370 Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
371 }
372 }
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700373 }
374
Svetoslav Ganov9504f572011-01-14 11:38:17 -0800375 private boolean isEnterActionKey(int keyCode) {
376 return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
377 || keyCode == KeyEvent.KEYCODE_ENTER
378 || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
379 }
380
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700381 /**
382 * Represents a web content key-binding.
383 */
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800384 private static final class AccessibilityWebContentKeyBinding {
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700385
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800386 private static final int MODIFIERS_OFFSET = 32;
387 private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700388
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800389 private static final int KEY_CODE_OFFSET = 0;
390 private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700391
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800392 private static final int ACTION_OFFSET = 24;
393 private static final int ACTION_MASK = 0xFF000000;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700394
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800395 private static final int FIRST_ARGUMENT_OFFSET = 16;
396 private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700397
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800398 private static final int SECOND_ARGUMENT_OFFSET = 8;
399 private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700400
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800401 private static final int THIRD_ARGUMENT_OFFSET = 0;
402 private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700403
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800404 private final long mKeyCodeAndModifiers;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700405
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800406 private final int [] mActionSequence;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700407
408 /**
409 * @return The key code of the binding key.
410 */
411 public int getKeyCode() {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800412 return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700413 }
414
415 /**
416 * @return The meta state of the binding key.
417 */
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800418 public int getModifiers() {
419 return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700420 }
421
422 /**
423 * @return The number of actions in the key binding.
424 */
425 public int getActionCount() {
426 return mActionSequence.length;
427 }
428
429 /**
430 * @param index The action for a given action <code>index</code>.
431 */
432 public int getAction(int index) {
433 return mActionSequence[index];
434 }
435
436 /**
437 * @param index The action code for a given action <code>index</code>.
438 */
439 public int getActionCode(int index) {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800440 return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700441 }
442
443 /**
444 * @param index The first argument for a given action <code>index</code>.
445 */
446 public int getFirstArgument(int index) {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800447 return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700448 }
449
450 /**
451 * @param index The second argument for a given action <code>index</code>.
452 */
453 public int getSecondArgument(int index) {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800454 return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700455 }
456
457 /**
458 * @param index The third argument for a given action <code>index</code>.
459 */
460 public int getThirdArgument(int index) {
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800461 return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700462 }
463
464 /**
465 * Creates a new instance.
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800466 * @param keyCodeAndModifiers The key for the binding (key and modifiers).
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700467 * @param actionSequence The sequence of action for the binding.
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700468 */
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800469 public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
470 mKeyCodeAndModifiers = keyCodeAndModifiers;
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700471 mActionSequence = actionSequence;
472 }
473
474 @Override
475 public String toString() {
476 StringBuilder builder = new StringBuilder();
Svetoslav Ganovb01c3d22011-01-11 14:19:17 -0800477 builder.append("modifiers: ");
478 builder.append(getModifiers());
Svetoslav Ganov585f13f8d2010-08-10 07:59:15 -0700479 builder.append(", keyCode: ");
480 builder.append(getKeyCode());
481 builder.append(", actions[");
482 for (int i = 0, count = getActionCount(); i < count; i++) {
483 builder.append("{actionCode");
484 builder.append(i);
485 builder.append(": ");
486 builder.append(getActionCode(i));
487 builder.append(", firstArgument: ");
488 builder.append(getFirstArgument(i));
489 builder.append(", secondArgument: ");
490 builder.append(getSecondArgument(i));
491 builder.append(", thirdArgument: ");
492 builder.append(getThirdArgument(i));
493 builder.append("}");
494 }
495 builder.append("]");
496 return builder.toString();
497 }
Svetoslav Ganovda355512010-05-12 22:04:44 -0700498 }
499}