blob: 6469b350a288ad02815ff63d014529d8aad4304c [file] [log] [blame]
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001/*
2 * Copyright (C) 2011 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.view.accessibility;
18
19import android.accessibilityservice.IAccessibilityServiceConnection;
20import android.graphics.Rect;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.RemoteException;
24import android.text.TextUtils;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070025import android.util.SparseIntArray;
26import android.view.View;
27
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -070028import java.util.Collections;
29import java.util.List;
30
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070031/**
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070032 * This class represents a node of the window content as well as actions that
33 * can be requested from its source. From the point of view of an
34 * {@link android.accessibilityservice.AccessibilityService} a window content is
35 * presented as tree of accessibility node info which may or may not map one-to-one
36 * to the view hierarchy. In other words, a custom view is free to report itself as
37 * a tree of accessibility node info.
38 * </p>
39 * <p>
40 * Once an accessibility node info is delivered to an accessibility service it is
41 * made immutable and calling a state mutation method generates an error.
42 * </p>
43 * <p>
44 * Please refer to {@link android.accessibilityservice.AccessibilityService} for
45 * details about how to obtain a handle to window content as a tree of accessibility
46 * node info as well as familiarizing with the security model.
47 * </p>
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070048 *
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -070049 * @see android.accessibilityservice.AccessibilityService
50 * @see AccessibilityEvent
51 * @see AccessibilityManager
Svetoslav Ganov8643aa02011-04-20 12:12:33 -070052 */
53public class AccessibilityNodeInfo implements Parcelable {
54
55 private static final boolean DEBUG = false;
56
57 // Actions.
58
59 /**
60 * Action that focuses the node.
61 */
62 public static final int ACTION_FOCUS = 0x00000001;
63
64 /**
65 * Action that unfocuses the node.
66 */
67 public static final int ACTION_CLEAR_FOCUS = 0x00000002;
68
69 /**
70 * Action that selects the node.
71 */
72 public static final int ACTION_SELECT = 0x00000004;
73
74 /**
75 * Action that unselects the node.
76 */
77 public static final int ACTION_CLEAR_SELECTION = 0x00000008;
78
79 // Boolean attributes.
80
81 private static final int PROPERTY_CHECKABLE = 0x00000001;
82
83 private static final int PROPERTY_CHECKED = 0x00000002;
84
85 private static final int PROPERTY_FOCUSABLE = 0x00000004;
86
87 private static final int PROPERTY_FOCUSED = 0x00000008;
88
89 private static final int PROPERTY_SELECTED = 0x00000010;
90
91 private static final int PROPERTY_CLICKABLE = 0x00000020;
92
93 private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
94
95 private static final int PROPERTY_ENABLED = 0x00000080;
96
97 private static final int PROPERTY_PASSWORD = 0x00000100;
98
Svetoslav Ganova0156172011-06-26 17:55:44 -070099 private static final int PROPERTY_SCROLLABLE = 0x00000200;
100
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700101 // Housekeeping.
102 private static final int MAX_POOL_SIZE = 50;
103 private static final Object sPoolLock = new Object();
104 private static AccessibilityNodeInfo sPool;
105 private static int sPoolSize;
106 private AccessibilityNodeInfo mNext;
107 private boolean mIsInPool;
108 private boolean mSealed;
109
110 // Data.
Svetoslav Ganov9210ccb2011-06-08 19:42:51 -0700111 private int mAccessibilityViewId = View.NO_ID;
112 private int mAccessibilityWindowId = View.NO_ID;
113 private int mParentAccessibilityViewId = View.NO_ID;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700114 private int mBooleanProperties;
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700115 private final Rect mBoundsInParent = new Rect();
116 private final Rect mBoundsInScreen = new Rect();
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700117
118 private CharSequence mPackageName;
119 private CharSequence mClassName;
120 private CharSequence mText;
121 private CharSequence mContentDescription;
122
Svetoslav Ganov35bfede2011-07-14 17:57:06 -0700123 private SparseIntArray mChildAccessibilityIds = new SparseIntArray();
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700124 private int mActions;
125
126 private IAccessibilityServiceConnection mConnection;
127
128 /**
129 * Hide constructor from clients.
130 */
131 private AccessibilityNodeInfo() {
132 /* do nothing */
133 }
134
135 /**
136 * Sets the source.
137 *
138 * @param source The info source.
139 */
140 public void setSource(View source) {
141 enforceNotSealed();
142 mAccessibilityViewId = source.getAccessibilityViewId();
143 mAccessibilityWindowId = source.getAccessibilityWindowId();
144 }
145
146 /**
147 * Gets the id of the window from which the info comes from.
148 *
149 * @return The window id.
150 */
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700151 public int getWindowId() {
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700152 return mAccessibilityWindowId;
153 }
154
155 /**
156 * Gets the number of children.
157 *
158 * @return The child count.
159 */
160 public int getChildCount() {
161 return mChildAccessibilityIds.size();
162 }
163
164 /**
165 * Get the child at given index.
166 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700167 * <strong>Note:</strong> It is a client responsibility to recycle the
168 * received info by calling {@link AccessibilityNodeInfo#recycle()}
169 * to avoid creating of multiple instances.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700170 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700171 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700172 * @param index The child index.
173 * @return The child node.
174 *
175 * @throws IllegalStateException If called outside of an AccessibilityService.
176 *
177 */
178 public AccessibilityNodeInfo getChild(int index) {
179 enforceSealed();
180 final int childAccessibilityViewId = mChildAccessibilityIds.get(index);
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700181 if (!canPerformRequestOverConnection(childAccessibilityViewId)) {
182 return null;
183 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700184 try {
185 return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId,
186 childAccessibilityViewId);
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700187 } catch (RemoteException re) {
188 /* ignore*/
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700189 }
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700190 return null;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700191 }
192
193 /**
194 * Adds a child.
195 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700196 * <strong>Note:</strong> Cannot be called from an
197 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700198 * This class is made immutable before being delivered to an AccessibilityService.
199 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700200 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700201 * @param child The child.
202 *
203 * @throws IllegalStateException If called from an AccessibilityService.
204 */
205 public void addChild(View child) {
206 enforceNotSealed();
207 final int childAccessibilityViewId = child.getAccessibilityViewId();
208 final int index = mChildAccessibilityIds.size();
209 mChildAccessibilityIds.put(index, childAccessibilityViewId);
210 }
211
212 /**
213 * Gets the actions that can be performed on the node.
214 *
215 * @return The bit mask of with actions.
216 *
217 * @see AccessibilityNodeInfo#ACTION_FOCUS
218 * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
219 * @see AccessibilityNodeInfo#ACTION_SELECT
220 * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
221 */
222 public int getActions() {
223 return mActions;
224 }
225
226 /**
227 * Adds an action that can be performed on the node.
228 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700229 * <strong>Note:</strong> Cannot be called from an
230 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700231 * This class is made immutable before being delivered to an AccessibilityService.
232 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700233 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700234 * @param action The action.
235 *
236 * @throws IllegalStateException If called from an AccessibilityService.
237 */
238 public void addAction(int action) {
239 enforceNotSealed();
240 mActions |= action;
241 }
242
243 /**
244 * Performs an action on the node.
245 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700246 * <strong>Note:</strong> An action can be performed only if the request is made
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700247 * from an {@link android.accessibilityservice.AccessibilityService}.
248 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700249 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700250 * @param action The action to perform.
251 * @return True if the action was performed.
252 *
253 * @throws IllegalStateException If called outside of an AccessibilityService.
254 */
255 public boolean performAction(int action) {
256 enforceSealed();
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700257 if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
258 return false;
259 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700260 try {
261 return mConnection.performAccessibilityAction(mAccessibilityWindowId,
262 mAccessibilityViewId, action);
263 } catch (RemoteException e) {
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700264 /* ignore */
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700265 }
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700266 return false;
267 }
268
269 /**
270 * Finds {@link AccessibilityNodeInfo}s by text. The match is case
Svetoslav Ganov86398bd2011-06-21 17:38:43 -0700271 * insensitive containment. The search is relative to this info i.e.
272 * this info is the root of the traversed tree.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700273 * <p>
274 * <strong>Note:</strong> It is a client responsibility to recycle the
275 * received info by calling {@link AccessibilityNodeInfo#recycle()}
276 * to avoid creating of multiple instances.
277 * </p>
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700278 *
279 * @param text The searched text.
280 * @return A list of node info.
281 */
282 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
283 enforceSealed();
284 if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
Svetoslav Ganov86398bd2011-06-21 17:38:43 -0700285 return Collections.emptyList();
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700286 }
287 try {
288 return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId,
289 mAccessibilityViewId);
290 } catch (RemoteException e) {
291 /* ignore */
292 }
293 return Collections.emptyList();
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700294 }
295
296 /**
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700297 * Gets the parent.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700298 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700299 * <strong>Note:</strong> It is a client responsibility to recycle the
300 * received info by calling {@link AccessibilityNodeInfo#recycle()}
301 * to avoid creating of multiple instances.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700302 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700303 *
Svetoslav Ganov00aabf72011-07-21 11:35:03 -0700304 * @return The parent.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700305 */
306 public AccessibilityNodeInfo getParent() {
307 enforceSealed();
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700308 if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700309 return null;
310 }
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700311 try {
312 return mConnection.findAccessibilityNodeInfoByAccessibilityId(
313 mAccessibilityWindowId, mParentAccessibilityViewId);
314 } catch (RemoteException e) {
315 /* ignore */
316 }
317 return null;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700318 }
319
320 /**
321 * Sets the parent.
322 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700323 * <strong>Note:</strong> Cannot be called from an
324 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700325 * This class is made immutable before being delivered to an AccessibilityService.
326 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700327 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700328 * @param parent The parent.
329 *
330 * @throws IllegalStateException If called from an AccessibilityService.
331 */
332 public void setParent(View parent) {
333 enforceNotSealed();
334 mParentAccessibilityViewId = parent.getAccessibilityViewId();
335 }
336
337 /**
338 * Gets the node bounds in parent coordinates.
339 *
340 * @param outBounds The output node bounds.
341 */
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700342 public void getBoundsInParent(Rect outBounds) {
343 outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
344 mBoundsInParent.right, mBoundsInParent.bottom);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700345 }
346
347 /**
348 * Sets the node bounds in parent coordinates.
349 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700350 * <strong>Note:</strong> Cannot be called from an
351 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700352 * This class is made immutable before being delivered to an AccessibilityService.
353 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700354 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700355 * @param bounds The node bounds.
356 *
357 * @throws IllegalStateException If called from an AccessibilityService.
358 */
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700359 public void setBoundsInParent(Rect bounds) {
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700360 enforceNotSealed();
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700361 mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
362 }
363
364 /**
365 * Gets the node bounds in screen coordinates.
366 *
367 * @param outBounds The output node bounds.
368 */
369 public void getBoundsInScreen(Rect outBounds) {
370 outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top,
371 mBoundsInScreen.right, mBoundsInScreen.bottom);
372 }
373
374 /**
375 * Sets the node bounds in screen coordinates.
376 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700377 * <strong>Note:</strong> Cannot be called from an
378 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700379 * This class is made immutable before being delivered to an AccessibilityService.
380 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700381 *
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700382 * @param bounds The node bounds.
383 *
384 * @throws IllegalStateException If called from an AccessibilityService.
385 */
386 public void setBoundsInScreen(Rect bounds) {
387 enforceNotSealed();
388 mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700389 }
390
391 /**
392 * Gets whether this node is checkable.
393 *
394 * @return True if the node is checkable.
395 */
396 public boolean isCheckable() {
397 return getBooleanProperty(PROPERTY_CHECKABLE);
398 }
399
400 /**
401 * Sets whether this node is checkable.
402 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700403 * <strong>Note:</strong> Cannot be called from an
404 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700405 * This class is made immutable before being delivered to an AccessibilityService.
406 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700407 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700408 * @param checkable True if the node is checkable.
409 *
410 * @throws IllegalStateException If called from an AccessibilityService.
411 */
412 public void setCheckable(boolean checkable) {
413 setBooleanProperty(PROPERTY_CHECKABLE, checkable);
414 }
415
416 /**
417 * Gets whether this node is checked.
418 *
419 * @return True if the node is checked.
420 */
421 public boolean isChecked() {
422 return getBooleanProperty(PROPERTY_CHECKED);
423 }
424
425 /**
426 * Sets whether this node is checked.
427 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700428 * <strong>Note:</strong> Cannot be called from an
429 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700430 * This class is made immutable before being delivered to an AccessibilityService.
431 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700432 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700433 * @param checked True if the node is checked.
434 *
435 * @throws IllegalStateException If called from an AccessibilityService.
436 */
437 public void setChecked(boolean checked) {
438 setBooleanProperty(PROPERTY_CHECKED, checked);
439 }
440
441 /**
442 * Gets whether this node is focusable.
443 *
444 * @return True if the node is focusable.
445 */
446 public boolean isFocusable() {
447 return getBooleanProperty(PROPERTY_FOCUSABLE);
448 }
449
450 /**
451 * Sets whether this node is focusable.
452 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700453 * <strong>Note:</strong> Cannot be called from an
454 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700455 * This class is made immutable before being delivered to an AccessibilityService.
456 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700457 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700458 * @param focusable True if the node is focusable.
459 *
460 * @throws IllegalStateException If called from an AccessibilityService.
461 */
462 public void setFocusable(boolean focusable) {
463 setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
464 }
465
466 /**
467 * Gets whether this node is focused.
468 *
469 * @return True if the node is focused.
470 */
471 public boolean isFocused() {
472 return getBooleanProperty(PROPERTY_FOCUSED);
473 }
474
475 /**
476 * Sets whether this node is focused.
477 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700478 * <strong>Note:</strong> Cannot be called from an
479 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700480 * This class is made immutable before being delivered to an AccessibilityService.
481 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700482 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700483 * @param focused True if the node is focused.
484 *
485 * @throws IllegalStateException If called from an AccessibilityService.
486 */
487 public void setFocused(boolean focused) {
488 setBooleanProperty(PROPERTY_FOCUSED, focused);
489 }
490
491 /**
492 * Gets whether this node is selected.
493 *
494 * @return True if the node is selected.
495 */
496 public boolean isSelected() {
497 return getBooleanProperty(PROPERTY_SELECTED);
498 }
499
500 /**
501 * Sets whether this node is selected.
502 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700503 * <strong>Note:</strong> Cannot be called from an
504 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700505 * This class is made immutable before being delivered to an AccessibilityService.
506 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700507 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700508 * @param selected True if the node is selected.
509 *
510 * @throws IllegalStateException If called from an AccessibilityService.
511 */
512 public void setSelected(boolean selected) {
513 setBooleanProperty(PROPERTY_SELECTED, selected);
514 }
515
516 /**
517 * Gets whether this node is clickable.
518 *
519 * @return True if the node is clickable.
520 */
521 public boolean isClickable() {
522 return getBooleanProperty(PROPERTY_CLICKABLE);
523 }
524
525 /**
526 * Sets whether this node is clickable.
527 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700528 * <strong>Note:</strong> Cannot be called from an
529 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700530 * This class is made immutable before being delivered to an AccessibilityService.
531 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700532 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700533 * @param clickable True if the node is clickable.
534 *
535 * @throws IllegalStateException If called from an AccessibilityService.
536 */
537 public void setClickable(boolean clickable) {
538 setBooleanProperty(PROPERTY_CLICKABLE, clickable);
539 }
540
541 /**
542 * Gets whether this node is long clickable.
543 *
544 * @return True if the node is long clickable.
545 */
546 public boolean isLongClickable() {
547 return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
548 }
549
550 /**
551 * Sets whether this node is long clickable.
552 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700553 * <strong>Note:</strong> Cannot be called from an
554 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700555 * This class is made immutable before being delivered to an AccessibilityService.
556 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700557 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700558 * @param longClickable True if the node is long clickable.
559 *
560 * @throws IllegalStateException If called from an AccessibilityService.
561 */
562 public void setLongClickable(boolean longClickable) {
563 setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
564 }
565
566 /**
567 * Gets whether this node is enabled.
568 *
569 * @return True if the node is enabled.
570 */
571 public boolean isEnabled() {
572 return getBooleanProperty(PROPERTY_ENABLED);
573 }
574
575 /**
576 * Sets whether this node is enabled.
577 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700578 * <strong>Note:</strong> Cannot be called from an
579 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700580 * This class is made immutable before being delivered to an AccessibilityService.
581 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700582 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700583 * @param enabled True if the node is enabled.
584 *
585 * @throws IllegalStateException If called from an AccessibilityService.
586 */
587 public void setEnabled(boolean enabled) {
588 setBooleanProperty(PROPERTY_ENABLED, enabled);
589 }
590
591 /**
592 * Gets whether this node is a password.
593 *
594 * @return True if the node is a password.
595 */
596 public boolean isPassword() {
597 return getBooleanProperty(PROPERTY_PASSWORD);
598 }
599
600 /**
601 * Sets whether this node is a password.
602 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700603 * <strong>Note:</strong> Cannot be called from an
604 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700605 * This class is made immutable before being delivered to an AccessibilityService.
606 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700607 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700608 * @param password True if the node is a password.
609 *
610 * @throws IllegalStateException If called from an AccessibilityService.
611 */
612 public void setPassword(boolean password) {
613 setBooleanProperty(PROPERTY_PASSWORD, password);
614 }
615
616 /**
Svetoslav Ganova0156172011-06-26 17:55:44 -0700617 * Gets if the node is scrollable.
618 *
619 * @return True if the node is scrollable, false otherwise.
620 */
621 public boolean isScrollable() {
622 return getBooleanProperty(PROPERTY_SCROLLABLE);
623 }
624
625 /**
626 * Sets if the node is scrollable.
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700627 * <p>
628 * <strong>Note:</strong> Cannot be called from an
629 * {@link android.accessibilityservice.AccessibilityService}.
630 * This class is made immutable before being delivered to an AccessibilityService.
631 * </p>
Svetoslav Ganova0156172011-06-26 17:55:44 -0700632 *
633 * @param scrollable True if the node is scrollable, false otherwise.
634 *
635 * @throws IllegalStateException If called from an AccessibilityService.
636 */
637 public void setScrollable(boolean scrollable) {
638 enforceNotSealed();
639 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
640 }
641
642 /**
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700643 * Gets the package this node comes from.
644 *
645 * @return The package name.
646 */
647 public CharSequence getPackageName() {
648 return mPackageName;
649 }
650
651 /**
652 * Sets the package this node comes from.
653 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700654 * <strong>Note:</strong> Cannot be called from an
655 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700656 * This class is made immutable before being delivered to an AccessibilityService.
657 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700658 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700659 * @param packageName The package name.
660 *
661 * @throws IllegalStateException If called from an AccessibilityService.
662 */
663 public void setPackageName(CharSequence packageName) {
664 enforceNotSealed();
665 mPackageName = packageName;
666 }
667
668 /**
669 * Gets the class this node comes from.
670 *
671 * @return The class name.
672 */
673 public CharSequence getClassName() {
674 return mClassName;
675 }
676
677 /**
678 * Sets the class this node comes from.
679 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700680 * <strong>Note:</strong> Cannot be called from an
681 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700682 * This class is made immutable before being delivered to an AccessibilityService.
683 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700684 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700685 * @param className The class name.
686 *
687 * @throws IllegalStateException If called from an AccessibilityService.
688 */
689 public void setClassName(CharSequence className) {
690 enforceNotSealed();
691 mClassName = className;
692 }
693
694 /**
695 * Gets the text of this node.
696 *
697 * @return The text.
698 */
699 public CharSequence getText() {
700 return mText;
701 }
702
703 /**
704 * Sets the text of this node.
705 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700706 * <strong>Note:</strong> Cannot be called from an
707 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700708 * This class is made immutable before being delivered to an AccessibilityService.
709 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700710 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700711 * @param text The text.
712 *
713 * @throws IllegalStateException If called from an AccessibilityService.
714 */
715 public void setText(CharSequence text) {
716 enforceNotSealed();
717 mText = text;
718 }
719
720 /**
721 * Gets the content description of this node.
722 *
723 * @return The content description.
724 */
725 public CharSequence getContentDescription() {
726 return mContentDescription;
727 }
728
729 /**
730 * Sets the content description of this node.
731 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700732 * <strong>Note:</strong> Cannot be called from an
733 * {@link android.accessibilityservice.AccessibilityService}.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700734 * This class is made immutable before being delivered to an AccessibilityService.
735 * </p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700736 *
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700737 * @param contentDescription The content description.
738 *
739 * @throws IllegalStateException If called from an AccessibilityService.
740 */
741 public void setContentDescription(CharSequence contentDescription) {
742 enforceNotSealed();
743 mContentDescription = contentDescription;
744 }
745
746 /**
747 * Gets the value of a boolean property.
748 *
749 * @param property The property.
750 * @return The value.
751 */
752 private boolean getBooleanProperty(int property) {
753 return (mBooleanProperties & property) != 0;
754 }
755
756 /**
757 * Sets a boolean property.
758 *
759 * @param property The property.
760 * @param value The value.
761 *
762 * @throws IllegalStateException If called from an AccessibilityService.
763 */
764 private void setBooleanProperty(int property, boolean value) {
765 enforceNotSealed();
766 if (value) {
767 mBooleanProperties |= property;
768 } else {
769 mBooleanProperties &= ~property;
770 }
771 }
772
773 /**
774 * Sets the connection for interacting with the system.
775 *
776 * @param connection The client token.
777 *
778 * @hide
779 */
780 public final void setConnection(IAccessibilityServiceConnection connection) {
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700781 enforceNotSealed();
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700782 mConnection = connection;
783 }
784
785 /**
786 * {@inheritDoc}
787 */
788 public int describeContents() {
789 return 0;
790 }
791
792 /**
793 * Sets if this instance is sealed.
794 *
795 * @param sealed Whether is sealed.
796 *
797 * @hide
798 */
799 public void setSealed(boolean sealed) {
800 mSealed = sealed;
801 }
802
803 /**
804 * Gets if this instance is sealed.
805 *
806 * @return Whether is sealed.
807 *
808 * @hide
809 */
810 public boolean isSealed() {
811 return mSealed;
812 }
813
814 /**
815 * Enforces that this instance is sealed.
816 *
817 * @throws IllegalStateException If this instance is not sealed.
818 *
819 * @hide
820 */
821 protected void enforceSealed() {
822 if (!isSealed()) {
823 throw new IllegalStateException("Cannot perform this "
824 + "action on a not sealed instance.");
825 }
826 }
827
828 /**
829 * Enforces that this instance is not sealed.
830 *
831 * @throws IllegalStateException If this instance is sealed.
832 *
833 * @hide
834 */
835 protected void enforceNotSealed() {
836 if (isSealed()) {
837 throw new IllegalStateException("Cannot perform this "
838 + "action on an sealed instance.");
839 }
840 }
841
842 /**
843 * Returns a cached instance if such is available otherwise a new one
844 * and sets the source.
845 *
846 * @return An instance.
847 *
848 * @see #setSource(View)
849 */
850 public static AccessibilityNodeInfo obtain(View source) {
851 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
852 info.setSource(source);
853 return info;
854 }
855
856 /**
857 * Returns a cached instance if such is available otherwise a new one.
858 *
859 * @return An instance.
860 */
861 public static AccessibilityNodeInfo obtain() {
862 synchronized (sPoolLock) {
863 if (sPool != null) {
864 AccessibilityNodeInfo info = sPool;
865 sPool = sPool.mNext;
866 sPoolSize--;
867 info.mNext = null;
868 info.mIsInPool = false;
Svetoslav Ganov9210ccb2011-06-08 19:42:51 -0700869 return info;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700870 }
871 return new AccessibilityNodeInfo();
872 }
873 }
874
875 /**
Svetoslav Ganov35bfede2011-07-14 17:57:06 -0700876 * Returns a cached instance if such is available or a new one is
877 * create. The returned instance is initialized from the given
878 * <code>info</code>.
879 *
880 * @param info The other info.
881 * @return An instance.
882 */
883 public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) {
884 AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain();
885 infoClone.init(info);
886 return infoClone;
887 }
888
889 /**
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700890 * Return an instance back to be reused.
891 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700892 * <strong>Note:</strong> You must not touch the object after calling this function.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700893 *
894 * @throws IllegalStateException If the info is already recycled.
895 */
896 public void recycle() {
897 if (mIsInPool) {
898 throw new IllegalStateException("Info already recycled!");
899 }
900 clear();
901 synchronized (sPoolLock) {
902 if (sPoolSize <= MAX_POOL_SIZE) {
903 mNext = sPool;
904 sPool = this;
905 mIsInPool = true;
906 sPoolSize++;
907 }
908 }
909 }
910
911 /**
912 * {@inheritDoc}
913 * <p>
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700914 * <strong>Note:</strong> After the instance is written to a parcel it
915 * is recycled. You must not touch the object after calling this function.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700916 * </p>
917 */
918 public void writeToParcel(Parcel parcel, int flags) {
919 if (mConnection == null) {
920 parcel.writeInt(0);
921 } else {
922 parcel.writeInt(1);
923 parcel.writeStrongBinder(mConnection.asBinder());
924 }
925 parcel.writeInt(isSealed() ? 1 : 0);
926 parcel.writeInt(mAccessibilityViewId);
927 parcel.writeInt(mAccessibilityWindowId);
928 parcel.writeInt(mParentAccessibilityViewId);
929
930 SparseIntArray childIds = mChildAccessibilityIds;
931 final int childIdsSize = childIds.size();
932 parcel.writeInt(childIdsSize);
933 for (int i = 0; i < childIdsSize; i++) {
934 parcel.writeInt(childIds.valueAt(i));
935 }
936
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -0700937 parcel.writeInt(mBoundsInParent.top);
938 parcel.writeInt(mBoundsInParent.bottom);
939 parcel.writeInt(mBoundsInParent.left);
940 parcel.writeInt(mBoundsInParent.right);
941
942 parcel.writeInt(mBoundsInScreen.top);
943 parcel.writeInt(mBoundsInScreen.bottom);
944 parcel.writeInt(mBoundsInScreen.left);
945 parcel.writeInt(mBoundsInScreen.right);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700946
947 parcel.writeInt(mActions);
948
949 parcel.writeInt(mBooleanProperties);
950
951 TextUtils.writeToParcel(mPackageName, parcel, flags);
952 TextUtils.writeToParcel(mClassName, parcel, flags);
953 TextUtils.writeToParcel(mText, parcel, flags);
954 TextUtils.writeToParcel(mContentDescription, parcel, flags);
955
956 // Since instances of this class are fetched via synchronous i.e. blocking
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -0700957 // calls in IPCs we always recycle as soon as the instance is marshaled.
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700958 recycle();
959 }
960
961 /**
Svetoslav Ganov35bfede2011-07-14 17:57:06 -0700962 * Initializes this instance from another one.
963 *
964 * @param other The other instance.
965 */
966 private void init(AccessibilityNodeInfo other) {
967 mSealed = other.mSealed;
968 mConnection = other.mConnection;
969 mAccessibilityViewId = other.mAccessibilityViewId;
970 mParentAccessibilityViewId = other.mParentAccessibilityViewId;
971 mAccessibilityWindowId = other.mAccessibilityWindowId;
972 mBoundsInParent.set(other.mBoundsInParent);
973 mBoundsInScreen.set(other.mBoundsInScreen);
974 mPackageName = other.mPackageName;
975 mClassName = other.mClassName;
976 mText = other.mText;
977 mContentDescription = other.mContentDescription;
978 mActions= other.mActions;
979 mBooleanProperties = other.mBooleanProperties;
980 mChildAccessibilityIds = other.mChildAccessibilityIds.clone();
981 }
982
983 /**
Svetoslav Ganov8643aa02011-04-20 12:12:33 -0700984 * Creates a new instance from a {@link Parcel}.
985 *
986 * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}.
987 */
988 private void initFromParcel(Parcel parcel) {
989 if (parcel.readInt() == 1) {
990 mConnection = IAccessibilityServiceConnection.Stub.asInterface(
991 parcel.readStrongBinder());
992 }
993 mSealed = (parcel.readInt() == 1);
994 mAccessibilityViewId = parcel.readInt();
995 mAccessibilityWindowId = parcel.readInt();
996 mParentAccessibilityViewId = parcel.readInt();
997
998 SparseIntArray childIds = mChildAccessibilityIds;
999 final int childrenSize = parcel.readInt();
1000 for (int i = 0; i < childrenSize; i++) {
1001 final int childId = parcel.readInt();
1002 childIds.put(i, childId);
1003 }
1004
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -07001005 mBoundsInParent.top = parcel.readInt();
1006 mBoundsInParent.bottom = parcel.readInt();
1007 mBoundsInParent.left = parcel.readInt();
1008 mBoundsInParent.right = parcel.readInt();
1009
1010 mBoundsInScreen.top = parcel.readInt();
1011 mBoundsInScreen.bottom = parcel.readInt();
1012 mBoundsInScreen.left = parcel.readInt();
1013 mBoundsInScreen.right = parcel.readInt();
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001014
1015 mActions = parcel.readInt();
1016
1017 mBooleanProperties = parcel.readInt();
1018
1019 mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1020 mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1021 mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1022 mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
1023 }
1024
1025 /**
1026 * Clears the state of this instance.
1027 */
1028 private void clear() {
1029 mSealed = false;
1030 mConnection = null;
1031 mAccessibilityViewId = View.NO_ID;
1032 mParentAccessibilityViewId = View.NO_ID;
Svetoslav Ganov35bfede2011-07-14 17:57:06 -07001033 mAccessibilityWindowId = View.NO_ID;
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001034 mChildAccessibilityIds.clear();
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -07001035 mBoundsInParent.set(0, 0, 0, 0);
1036 mBoundsInScreen.set(0, 0, 0, 0);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001037 mBooleanProperties = 0;
1038 mPackageName = null;
1039 mClassName = null;
1040 mText = null;
1041 mContentDescription = null;
1042 mActions = 0;
1043 }
1044
1045 /**
1046 * Gets the human readable action symbolic name.
1047 *
1048 * @param action The action.
1049 * @return The symbolic name.
1050 */
1051 private static String getActionSymbolicName(int action) {
Svetoslav Ganov38e8b4e2011-06-29 20:00:53 -07001052 switch (action) {
1053 case ACTION_FOCUS:
1054 return "ACTION_FOCUS";
1055 case ACTION_CLEAR_FOCUS:
1056 return "ACTION_CLEAR_FOCUS";
1057 case ACTION_SELECT:
1058 return "ACTION_SELECT";
1059 case ACTION_CLEAR_SELECTION:
1060 return "ACTION_CLEAR_SELECTION";
1061 default:
1062 throw new IllegalArgumentException("Unknown action: " + action);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001063 }
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001064 }
1065
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -07001066 private boolean canPerformRequestOverConnection(int accessibilityViewId) {
1067 return (mAccessibilityWindowId != View.NO_ID
1068 && accessibilityViewId != View.NO_ID
1069 && mConnection != null);
1070 }
1071
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001072 @Override
Svetoslav Ganov8dffad62011-06-10 12:38:36 -07001073 public boolean equals(Object object) {
1074 if (this == object) {
1075 return true;
1076 }
1077 if (object == null) {
1078 return false;
1079 }
1080 if (getClass() != object.getClass()) {
1081 return false;
1082 }
1083 AccessibilityNodeInfo other = (AccessibilityNodeInfo) object;
1084 if (mAccessibilityViewId != other.mAccessibilityViewId) {
1085 return false;
1086 }
1087 if (mAccessibilityWindowId != other.mAccessibilityWindowId) {
1088 return false;
1089 }
1090 return true;
1091 }
1092
1093 @Override
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001094 public int hashCode() {
1095 final int prime = 31;
1096 int result = 1;
1097 result = prime * result + mAccessibilityViewId;
1098 result = prime * result + mAccessibilityWindowId;
1099 return result;
1100 }
1101
1102 @Override
1103 public String toString() {
1104 StringBuilder builder = new StringBuilder();
1105 builder.append(super.toString());
1106
1107 if (DEBUG) {
1108 builder.append("; accessibilityId: " + mAccessibilityViewId);
1109 builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId);
1110 SparseIntArray childIds = mChildAccessibilityIds;
1111 builder.append("; childAccessibilityIds: [");
1112 for (int i = 0, count = childIds.size(); i < count; i++) {
1113 builder.append(childIds.valueAt(i));
1114 if (i < count - 1) {
1115 builder.append(", ");
1116 }
1117 }
1118 builder.append("]");
1119 }
1120
Svetoslav Ganoveeee4d22011-06-10 20:51:30 -07001121 builder.append("; boundsInParent: " + mBoundsInParent);
1122 builder.append("; boundsInScreen: " + mBoundsInScreen);
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001123
1124 builder.append("; packageName: ").append(mPackageName);
1125 builder.append("; className: ").append(mClassName);
1126 builder.append("; text: ").append(mText);
1127 builder.append("; contentDescription: ").append(mContentDescription);
1128
1129 builder.append("; checkable: ").append(isCheckable());
1130 builder.append("; checked: ").append(isChecked());
1131 builder.append("; focusable: ").append(isFocusable());
1132 builder.append("; focused: ").append(isFocused());
1133 builder.append("; selected: ").append(isSelected());
1134 builder.append("; clickable: ").append(isClickable());
1135 builder.append("; longClickable: ").append(isLongClickable());
1136 builder.append("; enabled: ").append(isEnabled());
1137 builder.append("; password: ").append(isPassword());
Svetoslav Ganova0156172011-06-26 17:55:44 -07001138 builder.append("; scrollable: " + isScrollable());
Svetoslav Ganov8643aa02011-04-20 12:12:33 -07001139
1140 builder.append("; [");
1141
1142 for (int actionBits = mActions; actionBits != 0;) {
1143 final int action = 1 << Integer.numberOfTrailingZeros(actionBits);
1144 actionBits &= ~action;
1145 builder.append(getActionSymbolicName(action));
1146 if (actionBits != 0) {
1147 builder.append(", ");
1148 }
1149 }
1150
1151 builder.append("]");
1152
1153 return builder.toString();
1154 }
1155
1156 /**
1157 * @see Parcelable.Creator
1158 */
1159 public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
1160 new Parcelable.Creator<AccessibilityNodeInfo>() {
1161 public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
1162 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
1163 info.initFromParcel(parcel);
1164 return info;
1165 }
1166
1167 public AccessibilityNodeInfo[] newArray(int size) {
1168 return new AccessibilityNodeInfo[size];
1169 }
1170 };
1171}