AccessibilityNodeInfo.java revision 2ef6905003c20010032ee993dfcc5899ad9be6f8
1/* 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.graphics.Rect; 20import android.os.Bundle; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.util.SparseLongArray; 24import android.view.View; 25 26import java.util.Collections; 27import java.util.List; 28 29/** 30 * This class represents a node of the window content as well as actions that 31 * can be requested from its source. From the point of view of an 32 * {@link android.accessibilityservice.AccessibilityService} a window content is 33 * presented as tree of accessibility node info which may or may not map one-to-one 34 * to the view hierarchy. In other words, a custom view is free to report itself as 35 * a tree of accessibility node info. 36 * </p> 37 * <p> 38 * Once an accessibility node info is delivered to an accessibility service it is 39 * made immutable and calling a state mutation method generates an error. 40 * </p> 41 * <p> 42 * Please refer to {@link android.accessibilityservice.AccessibilityService} for 43 * details about how to obtain a handle to window content as a tree of accessibility 44 * node info as well as familiarizing with the security model. 45 * </p> 46 * <div class="special reference"> 47 * <h3>Developer Guides</h3> 48 * <p>For more information about making applications accessible, read the 49 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> 50 * developer guide.</p> 51 * </div> 52 * 53 * @see android.accessibilityservice.AccessibilityService 54 * @see AccessibilityEvent 55 * @see AccessibilityManager 56 */ 57public class AccessibilityNodeInfo implements Parcelable { 58 59 private static final boolean DEBUG = false; 60 61 /** @hide */ 62 public static final int UNDEFINED = -1; 63 64 /** @hide */ 65 public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED); 66 67 /** @hide */ 68 public static final int ACTIVE_WINDOW_ID = UNDEFINED; 69 70 /** @hide */ 71 public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001; 72 73 /** @hide */ 74 public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002; 75 76 /** @hide */ 77 public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004; 78 79 /** @hide */ 80 public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008; 81 82 // Actions. 83 84 /** 85 * Action that gives input focus to the node. 86 */ 87 public static final int ACTION_FOCUS = 0x00000001; 88 89 /** 90 * Action that clears input focus of the node. 91 */ 92 public static final int ACTION_CLEAR_FOCUS = 0x00000002; 93 94 /** 95 * Action that selects the node. 96 */ 97 public static final int ACTION_SELECT = 0x00000004; 98 99 /** 100 * Action that unselects the node. 101 */ 102 public static final int ACTION_CLEAR_SELECTION = 0x00000008; 103 104 /** 105 * Action that clicks on the node info. 106 */ 107 public static final int ACTION_CLICK = 0x00000010; 108 109 /** 110 * Action that long clicks on the node. 111 */ 112 public static final int ACTION_LONG_CLICK = 0x00000020; 113 114 /** 115 * Action that gives accessibility focus to the node. 116 */ 117 public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040; 118 119 /** 120 * Action that clears accessibility focus of the node. 121 */ 122 public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080; 123 124 /** 125 * Action that requests to go to the next entity in this node's text 126 * at a given movement granularity. For example, move to the next character, 127 * word, etc. 128 * <p> 129 * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br> 130 * <strong>Example:</strong> 131 * <code><pre><p> 132 * Bundle arguments = new Bundle(); 133 * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, 134 * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); 135 * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments); 136 * </code></pre></p> 137 * </p> 138 * 139 * @see #setMovementGranularities(int) 140 * @see #getMovementGranularities() 141 * 142 * @see #MOVEMENT_GRANULARITY_CHARACTER 143 * @see #MOVEMENT_GRANULARITY_WORD 144 * @see #MOVEMENT_GRANULARITY_LINE 145 * @see #MOVEMENT_GRANULARITY_PARAGRAPH 146 * @see #MOVEMENT_GRANULARITY_PAGE 147 */ 148 public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100; 149 150 /** 151 * Action that requests to go to the previous entity in this node's text 152 * at a given movement granularity. For example, move to the next character, 153 * word, etc. 154 * <p> 155 * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br> 156 * <strong>Example:</strong> 157 * <code><pre><p> 158 * Bundle arguments = new Bundle(); 159 * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, 160 * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); 161 * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, 162 * arguments); 163 * </code></pre></p> 164 * </p> 165 * 166 * @see #setMovementGranularities(int) 167 * @see #getMovementGranularities() 168 * 169 * @see #MOVEMENT_GRANULARITY_CHARACTER 170 * @see #MOVEMENT_GRANULARITY_WORD 171 * @see #MOVEMENT_GRANULARITY_LINE 172 * @see #MOVEMENT_GRANULARITY_PARAGRAPH 173 * @see #MOVEMENT_GRANULARITY_PAGE 174 */ 175 public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200; 176 177 /** 178 * Action to move to the next HTML element of a given type. For example, move 179 * to the BUTTON, INPUT, TABLE, etc. 180 * <p> 181 * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br> 182 * <strong>Example:</strong> 183 * <code><pre><p> 184 * Bundle arguments = new Bundle(); 185 * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); 186 * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments); 187 * </code></pre></p> 188 * </p> 189 */ 190 public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400; 191 192 /** 193 * Action to move to the previous HTML element of a given type. For example, move 194 * to the BUTTON, INPUT, TABLE, etc. 195 * <p> 196 * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br> 197 * <strong>Example:</strong> 198 * <code><pre><p> 199 * Bundle arguments = new Bundle(); 200 * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); 201 * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments); 202 * </code></pre></p> 203 * </p> 204 */ 205 public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800; 206 207 /** 208 * Action to scroll the node content forward. 209 */ 210 public static final int ACTION_SCROLL_FORWARD = 0x00001000; 211 212 /** 213 * Action to scroll the node content backward. 214 */ 215 public static final int ACTION_SCROLL_BACKWARD = 0x00002000; 216 217 /** 218 * Argument for which movement granularity to be used when traversing the node text. 219 * <p> 220 * <strong>Type:</strong> int<br> 221 * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, 222 * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} 223 * </p> 224 */ 225 public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = 226 "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; 227 228 /** 229 * Argument for which HTML element to get moving to the next/previous HTML element. 230 * <p> 231 * <strong>Type:</strong> String<br> 232 * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT}, 233 * {@link #ACTION_PREVIOUS_HTML_ELEMENT} 234 * </p> 235 */ 236 public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = 237 "ACTION_ARGUMENT_HTML_ELEMENT_STRING"; 238 239 /** 240 * The input focus. 241 */ 242 public static final int FOCUS_INPUT = 1; 243 244 /** 245 * The accessibility focus. 246 */ 247 public static final int FOCUS_ACCESSIBILITY = 2; 248 249 // Movement granularities 250 251 /** 252 * Movement granularity bit for traversing the text of a node by character. 253 */ 254 public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001; 255 256 /** 257 * Movement granularity bit for traversing the text of a node by word. 258 */ 259 public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002; 260 261 /** 262 * Movement granularity bit for traversing the text of a node by line. 263 */ 264 public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004; 265 266 /** 267 * Movement granularity bit for traversing the text of a node by paragraph. 268 */ 269 public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008; 270 271 /** 272 * Movement granularity bit for traversing the text of a node by page. 273 */ 274 public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010; 275 276 // Boolean attributes. 277 278 private static final int PROPERTY_CHECKABLE = 0x00000001; 279 280 private static final int PROPERTY_CHECKED = 0x00000002; 281 282 private static final int PROPERTY_FOCUSABLE = 0x00000004; 283 284 private static final int PROPERTY_FOCUSED = 0x00000008; 285 286 private static final int PROPERTY_SELECTED = 0x00000010; 287 288 private static final int PROPERTY_CLICKABLE = 0x00000020; 289 290 private static final int PROPERTY_LONG_CLICKABLE = 0x00000040; 291 292 private static final int PROPERTY_ENABLED = 0x00000080; 293 294 private static final int PROPERTY_PASSWORD = 0x00000100; 295 296 private static final int PROPERTY_SCROLLABLE = 0x00000200; 297 298 private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400; 299 300 private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800; 301 302 /** 303 * Bits that provide the id of a virtual descendant of a view. 304 */ 305 private static final long VIRTUAL_DESCENDANT_ID_MASK = 0xffffffff00000000L; 306 307 /** 308 * Bit shift of {@link #VIRTUAL_DESCENDANT_ID_MASK} to get to the id for a 309 * virtual descendant of a view. Such a descendant does not exist in the view 310 * hierarchy and is only reported via the accessibility APIs. 311 */ 312 private static final int VIRTUAL_DESCENDANT_ID_SHIFT = 32; 313 314 /** 315 * Gets the accessibility view id which identifies a View in the view three. 316 * 317 * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}. 318 * @return The accessibility view id part of the node id. 319 * 320 * @hide 321 */ 322 public static int getAccessibilityViewId(long accessibilityNodeId) { 323 return (int) accessibilityNodeId; 324 } 325 326 /** 327 * Gets the virtual descendant id which identifies an imaginary view in a 328 * containing View. 329 * 330 * @param accessibilityNodeId The id of an {@link AccessibilityNodeInfo}. 331 * @return The virtual view id part of the node id. 332 * 333 * @hide 334 */ 335 public static int getVirtualDescendantId(long accessibilityNodeId) { 336 return (int) ((accessibilityNodeId & VIRTUAL_DESCENDANT_ID_MASK) 337 >> VIRTUAL_DESCENDANT_ID_SHIFT); 338 } 339 340 /** 341 * Makes a node id by shifting the <code>virtualDescendantId</code> 342 * by {@link #VIRTUAL_DESCENDANT_ID_SHIFT} and taking 343 * the bitwise or with the <code>accessibilityViewId</code>. 344 * 345 * @param accessibilityViewId A View accessibility id. 346 * @param virtualDescendantId A virtual descendant id. 347 * @return The node id. 348 * 349 * @hide 350 */ 351 public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) { 352 return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId; 353 } 354 355 // Housekeeping. 356 private static final int MAX_POOL_SIZE = 50; 357 private static final Object sPoolLock = new Object(); 358 private static AccessibilityNodeInfo sPool; 359 private static int sPoolSize; 360 private AccessibilityNodeInfo mNext; 361 private boolean mIsInPool; 362 private boolean mSealed; 363 364 // Data. 365 private int mWindowId = UNDEFINED; 366 private long mSourceNodeId = ROOT_NODE_ID; 367 private long mParentNodeId = ROOT_NODE_ID; 368 369 private int mBooleanProperties; 370 private final Rect mBoundsInParent = new Rect(); 371 private final Rect mBoundsInScreen = new Rect(); 372 373 private CharSequence mPackageName; 374 private CharSequence mClassName; 375 private CharSequence mText; 376 private CharSequence mContentDescription; 377 378 private final SparseLongArray mChildNodeIds = new SparseLongArray(); 379 private int mActions; 380 381 private int mMovementGranularities; 382 383 private int mConnectionId = UNDEFINED; 384 385 /** 386 * Hide constructor from clients. 387 */ 388 private AccessibilityNodeInfo() { 389 /* do nothing */ 390 } 391 392 /** 393 * Sets the source. 394 * <p> 395 * <strong>Note:</strong> Cannot be called from an 396 * {@link android.accessibilityservice.AccessibilityService}. 397 * This class is made immutable before being delivered to an AccessibilityService. 398 * </p> 399 * 400 * @param source The info source. 401 */ 402 public void setSource(View source) { 403 setSource(source, UNDEFINED); 404 } 405 406 /** 407 * Sets the source to be a virtual descendant of the given <code>root</code>. 408 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 409 * is set as the source. 410 * <p> 411 * A virtual descendant is an imaginary View that is reported as a part of the view 412 * hierarchy for accessibility purposes. This enables custom views that draw complex 413 * content to report themselves as a tree of virtual views, thus conveying their 414 * logical structure. 415 * </p> 416 * <p> 417 * <strong>Note:</strong> Cannot be called from an 418 * {@link android.accessibilityservice.AccessibilityService}. 419 * This class is made immutable before being delivered to an AccessibilityService. 420 * </p> 421 * 422 * @param root The root of the virtual subtree. 423 * @param virtualDescendantId The id of the virtual descendant. 424 */ 425 public void setSource(View root, int virtualDescendantId) { 426 enforceNotSealed(); 427 mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED; 428 final int rootAccessibilityViewId = 429 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 430 mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 431 } 432 433 /** 434 * Find the view that has the specified focus type. The search starts from 435 * the view represented by this node info. 436 * 437 * @param focus The focus to find. One of {@link #FOCUS_INPUT} or 438 * {@link #FOCUS_ACCESSIBILITY}. 439 * @return The node info of the focused view or null. 440 * 441 * @see #FOCUS_INPUT 442 * @see #FOCUS_ACCESSIBILITY 443 */ 444 public AccessibilityNodeInfo findFocus(int focus) { 445 enforceSealed(); 446 enforceValidFocusType(focus); 447 if (!canPerformRequestOverConnection(mSourceNodeId)) { 448 return null; 449 } 450 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId, 451 mSourceNodeId, focus); 452 } 453 454 /** 455 * Searches for the nearest view in the specified direction that can take 456 * the input focus. 457 * 458 * @param direction The direction. Can be one of: 459 * {@link View#FOCUS_DOWN}, 460 * {@link View#FOCUS_UP}, 461 * {@link View#FOCUS_LEFT}, 462 * {@link View#FOCUS_RIGHT}, 463 * {@link View#FOCUS_FORWARD}, 464 * {@link View#FOCUS_BACKWARD}, 465 * {@link View#ACCESSIBILITY_FOCUS_FORWARD}, 466 * {@link View#ACCESSIBILITY_FOCUS_BACKWARD}, 467 * {@link View#ACCESSIBILITY_FOCUS_UP}, 468 * {@link View#ACCESSIBILITY_FOCUS_RIGHT}, 469 * {@link View#ACCESSIBILITY_FOCUS_DOWN}, 470 * {@link View#ACCESSIBILITY_FOCUS_LEFT}. 471 * 472 * @return The node info for the view that can take accessibility focus. 473 */ 474 public AccessibilityNodeInfo focusSearch(int direction) { 475 enforceSealed(); 476 enforceValidFocusDirection(direction); 477 if (!canPerformRequestOverConnection(mSourceNodeId)) { 478 return null; 479 } 480 return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId, 481 mSourceNodeId, direction); 482 } 483 484 /** 485 * Gets the id of the window from which the info comes from. 486 * 487 * @return The window id. 488 */ 489 public int getWindowId() { 490 return mWindowId; 491 } 492 493 /** 494 * @return The ids of the children. 495 * 496 * @hide 497 */ 498 public SparseLongArray getChildNodeIds() { 499 return mChildNodeIds; 500 } 501 502 /** 503 * Gets the number of children. 504 * 505 * @return The child count. 506 */ 507 public int getChildCount() { 508 return mChildNodeIds.size(); 509 } 510 511 /** 512 * Get the child at given index. 513 * <p> 514 * <strong>Note:</strong> It is a client responsibility to recycle the 515 * received info by calling {@link AccessibilityNodeInfo#recycle()} 516 * to avoid creating of multiple instances. 517 * </p> 518 * 519 * @param index The child index. 520 * @return The child node. 521 * 522 * @throws IllegalStateException If called outside of an AccessibilityService. 523 * 524 */ 525 public AccessibilityNodeInfo getChild(int index) { 526 enforceSealed(); 527 if (!canPerformRequestOverConnection(mSourceNodeId)) { 528 return null; 529 } 530 final long childId = mChildNodeIds.get(index); 531 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 532 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, 533 childId, FLAG_PREFETCH_DESCENDANTS); 534 } 535 536 /** 537 * Adds a child. 538 * <p> 539 * <strong>Note:</strong> Cannot be called from an 540 * {@link android.accessibilityservice.AccessibilityService}. 541 * This class is made immutable before being delivered to an AccessibilityService. 542 * </p> 543 * 544 * @param child The child. 545 * 546 * @throws IllegalStateException If called from an AccessibilityService. 547 */ 548 public void addChild(View child) { 549 addChild(child, UNDEFINED); 550 } 551 552 /** 553 * Adds a virtual child which is a descendant of the given <code>root</code>. 554 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 555 * is added as a child. 556 * <p> 557 * A virtual descendant is an imaginary View that is reported as a part of the view 558 * hierarchy for accessibility purposes. This enables custom views that draw complex 559 * content to report them selves as a tree of virtual views, thus conveying their 560 * logical structure. 561 * </p> 562 * 563 * @param root The root of the virtual subtree. 564 * @param virtualDescendantId The id of the virtual child. 565 */ 566 public void addChild(View root, int virtualDescendantId) { 567 enforceNotSealed(); 568 final int index = mChildNodeIds.size(); 569 final int rootAccessibilityViewId = 570 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 571 final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 572 mChildNodeIds.put(index, childNodeId); 573 } 574 575 /** 576 * Gets the actions that can be performed on the node. 577 * 578 * @return The bit mask of with actions. 579 * 580 * @see AccessibilityNodeInfo#ACTION_FOCUS 581 * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS 582 * @see AccessibilityNodeInfo#ACTION_SELECT 583 * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION 584 * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS 585 * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS 586 * @see AccessibilityNodeInfo#ACTION_CLICK 587 * @see AccessibilityNodeInfo#ACTION_LONG_CLICK 588 * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY 589 * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY 590 * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT 591 * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT 592 * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD 593 * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD 594 */ 595 public int getActions() { 596 return mActions; 597 } 598 599 /** 600 * Adds an action that can be performed on the node. 601 * <p> 602 * <strong>Note:</strong> Cannot be called from an 603 * {@link android.accessibilityservice.AccessibilityService}. 604 * This class is made immutable before being delivered to an AccessibilityService. 605 * </p> 606 * 607 * @param action The action. 608 * 609 * @throws IllegalStateException If called from an AccessibilityService. 610 */ 611 public void addAction(int action) { 612 enforceNotSealed(); 613 mActions |= action; 614 } 615 616 /** 617 * Sets the movement granularities for traversing the text of this node. 618 * <p> 619 * <strong>Note:</strong> Cannot be called from an 620 * {@link android.accessibilityservice.AccessibilityService}. 621 * This class is made immutable before being delivered to an AccessibilityService. 622 * </p> 623 * 624 * @param granularities The bit mask with granularities. 625 * 626 * @throws IllegalStateException If called from an AccessibilityService. 627 */ 628 public void setMovementGranularities(int granularities) { 629 enforceNotSealed(); 630 mMovementGranularities = granularities; 631 } 632 633 /** 634 * Gets the movement granularities for traversing the text of this node. 635 * 636 * @return The bit mask with granularities. 637 */ 638 public int getMovementGranularities() { 639 return mMovementGranularities; 640 } 641 642 /** 643 * Performs an action on the node. 644 * <p> 645 * <strong>Note:</strong> An action can be performed only if the request is made 646 * from an {@link android.accessibilityservice.AccessibilityService}. 647 * </p> 648 * 649 * @param action The action to perform. 650 * @return True if the action was performed. 651 * 652 * @throws IllegalStateException If called outside of an AccessibilityService. 653 */ 654 public boolean performAction(int action) { 655 enforceSealed(); 656 if (!canPerformRequestOverConnection(mSourceNodeId)) { 657 return false; 658 } 659 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 660 return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, 661 action, null); 662 } 663 664 /** 665 * Performs an action on the node. 666 * <p> 667 * <strong>Note:</strong> An action can be performed only if the request is made 668 * from an {@link android.accessibilityservice.AccessibilityService}. 669 * </p> 670 * 671 * @param action The action to perform. 672 * @param arguments A bundle with additional arguments. 673 * @return True if the action was performed. 674 * 675 * @throws IllegalStateException If called outside of an AccessibilityService. 676 */ 677 public boolean performAction(int action, Bundle arguments) { 678 enforceSealed(); 679 if (!canPerformRequestOverConnection(mSourceNodeId)) { 680 return false; 681 } 682 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 683 return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, 684 action, arguments); 685 } 686 687 /** 688 * Finds {@link AccessibilityNodeInfo}s by text. The match is case 689 * insensitive containment. The search is relative to this info i.e. 690 * this info is the root of the traversed tree. 691 * 692 * <p> 693 * <strong>Note:</strong> It is a client responsibility to recycle the 694 * received info by calling {@link AccessibilityNodeInfo#recycle()} 695 * to avoid creating of multiple instances. 696 * </p> 697 * 698 * @param text The searched text. 699 * @return A list of node info. 700 */ 701 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { 702 enforceSealed(); 703 if (!canPerformRequestOverConnection(mSourceNodeId)) { 704 return Collections.emptyList(); 705 } 706 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 707 return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId, 708 text); 709 } 710 711 /** 712 * Gets the parent. 713 * <p> 714 * <strong>Note:</strong> It is a client responsibility to recycle the 715 * received info by calling {@link AccessibilityNodeInfo#recycle()} 716 * to avoid creating of multiple instances. 717 * </p> 718 * 719 * @return The parent. 720 */ 721 public AccessibilityNodeInfo getParent() { 722 enforceSealed(); 723 if (!canPerformRequestOverConnection(mParentNodeId)) { 724 return null; 725 } 726 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 727 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, 728 mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); 729 } 730 731 /** 732 * @return The parent node id. 733 * 734 * @hide 735 */ 736 public long getParentNodeId() { 737 return mParentNodeId; 738 } 739 740 /** 741 * Sets the parent. 742 * <p> 743 * <strong>Note:</strong> Cannot be called from an 744 * {@link android.accessibilityservice.AccessibilityService}. 745 * This class is made immutable before being delivered to an AccessibilityService. 746 * </p> 747 * 748 * @param parent The parent. 749 * 750 * @throws IllegalStateException If called from an AccessibilityService. 751 */ 752 public void setParent(View parent) { 753 setParent(parent, UNDEFINED); 754 } 755 756 /** 757 * Sets the parent to be a virtual descendant of the given <code>root</code>. 758 * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root 759 * is set as the parent. 760 * <p> 761 * A virtual descendant is an imaginary View that is reported as a part of the view 762 * hierarchy for accessibility purposes. This enables custom views that draw complex 763 * content to report them selves as a tree of virtual views, thus conveying their 764 * logical structure. 765 * </p> 766 * <p> 767 * <strong>Note:</strong> Cannot be called from an 768 * {@link android.accessibilityservice.AccessibilityService}. 769 * This class is made immutable before being delivered to an AccessibilityService. 770 * </p> 771 * 772 * @param root The root of the virtual subtree. 773 * @param virtualDescendantId The id of the virtual descendant. 774 */ 775 public void setParent(View root, int virtualDescendantId) { 776 enforceNotSealed(); 777 final int rootAccessibilityViewId = 778 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 779 mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 780 } 781 782 /** 783 * Gets the node bounds in parent coordinates. 784 * 785 * @param outBounds The output node bounds. 786 */ 787 public void getBoundsInParent(Rect outBounds) { 788 outBounds.set(mBoundsInParent.left, mBoundsInParent.top, 789 mBoundsInParent.right, mBoundsInParent.bottom); 790 } 791 792 /** 793 * Sets the node bounds in parent coordinates. 794 * <p> 795 * <strong>Note:</strong> Cannot be called from an 796 * {@link android.accessibilityservice.AccessibilityService}. 797 * This class is made immutable before being delivered to an AccessibilityService. 798 * </p> 799 * 800 * @param bounds The node bounds. 801 * 802 * @throws IllegalStateException If called from an AccessibilityService. 803 */ 804 public void setBoundsInParent(Rect bounds) { 805 enforceNotSealed(); 806 mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 807 } 808 809 /** 810 * Gets the node bounds in screen coordinates. 811 * 812 * @param outBounds The output node bounds. 813 */ 814 public void getBoundsInScreen(Rect outBounds) { 815 outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, 816 mBoundsInScreen.right, mBoundsInScreen.bottom); 817 } 818 819 /** 820 * Sets the node bounds in screen coordinates. 821 * <p> 822 * <strong>Note:</strong> Cannot be called from an 823 * {@link android.accessibilityservice.AccessibilityService}. 824 * This class is made immutable before being delivered to an AccessibilityService. 825 * </p> 826 * 827 * @param bounds The node bounds. 828 * 829 * @throws IllegalStateException If called from an AccessibilityService. 830 */ 831 public void setBoundsInScreen(Rect bounds) { 832 enforceNotSealed(); 833 mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 834 } 835 836 /** 837 * Gets whether this node is checkable. 838 * 839 * @return True if the node is checkable. 840 */ 841 public boolean isCheckable() { 842 return getBooleanProperty(PROPERTY_CHECKABLE); 843 } 844 845 /** 846 * Sets whether this node is checkable. 847 * <p> 848 * <strong>Note:</strong> Cannot be called from an 849 * {@link android.accessibilityservice.AccessibilityService}. 850 * This class is made immutable before being delivered to an AccessibilityService. 851 * </p> 852 * 853 * @param checkable True if the node is checkable. 854 * 855 * @throws IllegalStateException If called from an AccessibilityService. 856 */ 857 public void setCheckable(boolean checkable) { 858 setBooleanProperty(PROPERTY_CHECKABLE, checkable); 859 } 860 861 /** 862 * Gets whether this node is checked. 863 * 864 * @return True if the node is checked. 865 */ 866 public boolean isChecked() { 867 return getBooleanProperty(PROPERTY_CHECKED); 868 } 869 870 /** 871 * Sets whether this node is checked. 872 * <p> 873 * <strong>Note:</strong> Cannot be called from an 874 * {@link android.accessibilityservice.AccessibilityService}. 875 * This class is made immutable before being delivered to an AccessibilityService. 876 * </p> 877 * 878 * @param checked True if the node is checked. 879 * 880 * @throws IllegalStateException If called from an AccessibilityService. 881 */ 882 public void setChecked(boolean checked) { 883 setBooleanProperty(PROPERTY_CHECKED, checked); 884 } 885 886 /** 887 * Gets whether this node is focusable. 888 * 889 * @return True if the node is focusable. 890 */ 891 public boolean isFocusable() { 892 return getBooleanProperty(PROPERTY_FOCUSABLE); 893 } 894 895 /** 896 * Sets whether this node is focusable. 897 * <p> 898 * <strong>Note:</strong> Cannot be called from an 899 * {@link android.accessibilityservice.AccessibilityService}. 900 * This class is made immutable before being delivered to an AccessibilityService. 901 * </p> 902 * 903 * @param focusable True if the node is focusable. 904 * 905 * @throws IllegalStateException If called from an AccessibilityService. 906 */ 907 public void setFocusable(boolean focusable) { 908 setBooleanProperty(PROPERTY_FOCUSABLE, focusable); 909 } 910 911 /** 912 * Gets whether this node is focused. 913 * 914 * @return True if the node is focused. 915 */ 916 public boolean isFocused() { 917 return getBooleanProperty(PROPERTY_FOCUSED); 918 } 919 920 /** 921 * Sets whether this node is focused. 922 * <p> 923 * <strong>Note:</strong> Cannot be called from an 924 * {@link android.accessibilityservice.AccessibilityService}. 925 * This class is made immutable before being delivered to an AccessibilityService. 926 * </p> 927 * 928 * @param focused True if the node is focused. 929 * 930 * @throws IllegalStateException If called from an AccessibilityService. 931 */ 932 public void setFocused(boolean focused) { 933 setBooleanProperty(PROPERTY_FOCUSED, focused); 934 } 935 936 /** 937 * Sets whether this node is visible to the user. 938 * 939 * @return Whether the node is visible to the user. 940 */ 941 public boolean isVisibleToUser() { 942 return getBooleanProperty(PROPERTY_VISIBLE_TO_USER); 943 } 944 945 /** 946 * Sets whether this node is visible to the user. 947 * <p> 948 * <strong>Note:</strong> Cannot be called from an 949 * {@link android.accessibilityservice.AccessibilityService}. 950 * This class is made immutable before being delivered to an AccessibilityService. 951 * </p> 952 * 953 * @param visibleToUser Whether the node is visible to the user. 954 * 955 * @throws IllegalStateException If called from an AccessibilityService. 956 */ 957 public void setVisibleToUser(boolean visibleToUser) { 958 setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser); 959 } 960 961 /** 962 * Gets whether this node is accessibility focused. 963 * 964 * @return True if the node is accessibility focused. 965 */ 966 public boolean isAccessibilityFocused() { 967 return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED); 968 } 969 970 /** 971 * Sets whether this node is accessibility focused. 972 * <p> 973 * <strong>Note:</strong> Cannot be called from an 974 * {@link android.accessibilityservice.AccessibilityService}. 975 * This class is made immutable before being delivered to an AccessibilityService. 976 * </p> 977 * 978 * @param focused True if the node is accessibility focused. 979 * 980 * @throws IllegalStateException If called from an AccessibilityService. 981 */ 982 public void setAccessibilityFocused(boolean focused) { 983 setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused); 984 } 985 986 /** 987 * Gets whether this node is selected. 988 * 989 * @return True if the node is selected. 990 */ 991 public boolean isSelected() { 992 return getBooleanProperty(PROPERTY_SELECTED); 993 } 994 995 /** 996 * Sets whether this node is selected. 997 * <p> 998 * <strong>Note:</strong> Cannot be called from an 999 * {@link android.accessibilityservice.AccessibilityService}. 1000 * This class is made immutable before being delivered to an AccessibilityService. 1001 * </p> 1002 * 1003 * @param selected True if the node is selected. 1004 * 1005 * @throws IllegalStateException If called from an AccessibilityService. 1006 */ 1007 public void setSelected(boolean selected) { 1008 setBooleanProperty(PROPERTY_SELECTED, selected); 1009 } 1010 1011 /** 1012 * Gets whether this node is clickable. 1013 * 1014 * @return True if the node is clickable. 1015 */ 1016 public boolean isClickable() { 1017 return getBooleanProperty(PROPERTY_CLICKABLE); 1018 } 1019 1020 /** 1021 * Sets whether this node is clickable. 1022 * <p> 1023 * <strong>Note:</strong> Cannot be called from an 1024 * {@link android.accessibilityservice.AccessibilityService}. 1025 * This class is made immutable before being delivered to an AccessibilityService. 1026 * </p> 1027 * 1028 * @param clickable True if the node is clickable. 1029 * 1030 * @throws IllegalStateException If called from an AccessibilityService. 1031 */ 1032 public void setClickable(boolean clickable) { 1033 setBooleanProperty(PROPERTY_CLICKABLE, clickable); 1034 } 1035 1036 /** 1037 * Gets whether this node is long clickable. 1038 * 1039 * @return True if the node is long clickable. 1040 */ 1041 public boolean isLongClickable() { 1042 return getBooleanProperty(PROPERTY_LONG_CLICKABLE); 1043 } 1044 1045 /** 1046 * Sets whether this node is long clickable. 1047 * <p> 1048 * <strong>Note:</strong> Cannot be called from an 1049 * {@link android.accessibilityservice.AccessibilityService}. 1050 * This class is made immutable before being delivered to an AccessibilityService. 1051 * </p> 1052 * 1053 * @param longClickable True if the node is long clickable. 1054 * 1055 * @throws IllegalStateException If called from an AccessibilityService. 1056 */ 1057 public void setLongClickable(boolean longClickable) { 1058 setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable); 1059 } 1060 1061 /** 1062 * Gets whether this node is enabled. 1063 * 1064 * @return True if the node is enabled. 1065 */ 1066 public boolean isEnabled() { 1067 return getBooleanProperty(PROPERTY_ENABLED); 1068 } 1069 1070 /** 1071 * Sets whether this node is enabled. 1072 * <p> 1073 * <strong>Note:</strong> Cannot be called from an 1074 * {@link android.accessibilityservice.AccessibilityService}. 1075 * This class is made immutable before being delivered to an AccessibilityService. 1076 * </p> 1077 * 1078 * @param enabled True if the node is enabled. 1079 * 1080 * @throws IllegalStateException If called from an AccessibilityService. 1081 */ 1082 public void setEnabled(boolean enabled) { 1083 setBooleanProperty(PROPERTY_ENABLED, enabled); 1084 } 1085 1086 /** 1087 * Gets whether this node is a password. 1088 * 1089 * @return True if the node is a password. 1090 */ 1091 public boolean isPassword() { 1092 return getBooleanProperty(PROPERTY_PASSWORD); 1093 } 1094 1095 /** 1096 * Sets whether this node is a password. 1097 * <p> 1098 * <strong>Note:</strong> Cannot be called from an 1099 * {@link android.accessibilityservice.AccessibilityService}. 1100 * This class is made immutable before being delivered to an AccessibilityService. 1101 * </p> 1102 * 1103 * @param password True if the node is a password. 1104 * 1105 * @throws IllegalStateException If called from an AccessibilityService. 1106 */ 1107 public void setPassword(boolean password) { 1108 setBooleanProperty(PROPERTY_PASSWORD, password); 1109 } 1110 1111 /** 1112 * Gets if the node is scrollable. 1113 * 1114 * @return True if the node is scrollable, false otherwise. 1115 */ 1116 public boolean isScrollable() { 1117 return getBooleanProperty(PROPERTY_SCROLLABLE); 1118 } 1119 1120 /** 1121 * Sets if the node is scrollable. 1122 * <p> 1123 * <strong>Note:</strong> Cannot be called from an 1124 * {@link android.accessibilityservice.AccessibilityService}. 1125 * This class is made immutable before being delivered to an AccessibilityService. 1126 * </p> 1127 * 1128 * @param scrollable True if the node is scrollable, false otherwise. 1129 * 1130 * @throws IllegalStateException If called from an AccessibilityService. 1131 */ 1132 public void setScrollable(boolean scrollable) { 1133 enforceNotSealed(); 1134 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 1135 } 1136 1137 /** 1138 * Gets the package this node comes from. 1139 * 1140 * @return The package name. 1141 */ 1142 public CharSequence getPackageName() { 1143 return mPackageName; 1144 } 1145 1146 /** 1147 * Sets the package this node comes from. 1148 * <p> 1149 * <strong>Note:</strong> Cannot be called from an 1150 * {@link android.accessibilityservice.AccessibilityService}. 1151 * This class is made immutable before being delivered to an AccessibilityService. 1152 * </p> 1153 * 1154 * @param packageName The package name. 1155 * 1156 * @throws IllegalStateException If called from an AccessibilityService. 1157 */ 1158 public void setPackageName(CharSequence packageName) { 1159 enforceNotSealed(); 1160 mPackageName = packageName; 1161 } 1162 1163 /** 1164 * Gets the class this node comes from. 1165 * 1166 * @return The class name. 1167 */ 1168 public CharSequence getClassName() { 1169 return mClassName; 1170 } 1171 1172 /** 1173 * Sets the class this node comes from. 1174 * <p> 1175 * <strong>Note:</strong> Cannot be called from an 1176 * {@link android.accessibilityservice.AccessibilityService}. 1177 * This class is made immutable before being delivered to an AccessibilityService. 1178 * </p> 1179 * 1180 * @param className The class name. 1181 * 1182 * @throws IllegalStateException If called from an AccessibilityService. 1183 */ 1184 public void setClassName(CharSequence className) { 1185 enforceNotSealed(); 1186 mClassName = className; 1187 } 1188 1189 /** 1190 * Gets the text of this node. 1191 * 1192 * @return The text. 1193 */ 1194 public CharSequence getText() { 1195 return mText; 1196 } 1197 1198 /** 1199 * Sets the text of this node. 1200 * <p> 1201 * <strong>Note:</strong> Cannot be called from an 1202 * {@link android.accessibilityservice.AccessibilityService}. 1203 * This class is made immutable before being delivered to an AccessibilityService. 1204 * </p> 1205 * 1206 * @param text The text. 1207 * 1208 * @throws IllegalStateException If called from an AccessibilityService. 1209 */ 1210 public void setText(CharSequence text) { 1211 enforceNotSealed(); 1212 mText = text; 1213 } 1214 1215 /** 1216 * Gets the content description of this node. 1217 * 1218 * @return The content description. 1219 */ 1220 public CharSequence getContentDescription() { 1221 return mContentDescription; 1222 } 1223 1224 /** 1225 * Sets the content description of this node. 1226 * <p> 1227 * <strong>Note:</strong> Cannot be called from an 1228 * {@link android.accessibilityservice.AccessibilityService}. 1229 * This class is made immutable before being delivered to an AccessibilityService. 1230 * </p> 1231 * 1232 * @param contentDescription The content description. 1233 * 1234 * @throws IllegalStateException If called from an AccessibilityService. 1235 */ 1236 public void setContentDescription(CharSequence contentDescription) { 1237 enforceNotSealed(); 1238 mContentDescription = contentDescription; 1239 } 1240 1241 /** 1242 * Gets the value of a boolean property. 1243 * 1244 * @param property The property. 1245 * @return The value. 1246 */ 1247 private boolean getBooleanProperty(int property) { 1248 return (mBooleanProperties & property) != 0; 1249 } 1250 1251 /** 1252 * Sets a boolean property. 1253 * 1254 * @param property The property. 1255 * @param value The value. 1256 * 1257 * @throws IllegalStateException If called from an AccessibilityService. 1258 */ 1259 private void setBooleanProperty(int property, boolean value) { 1260 enforceNotSealed(); 1261 if (value) { 1262 mBooleanProperties |= property; 1263 } else { 1264 mBooleanProperties &= ~property; 1265 } 1266 } 1267 1268 /** 1269 * Sets the unique id of the IAccessibilityServiceConnection over which 1270 * this instance can send requests to the system. 1271 * 1272 * @param connectionId The connection id. 1273 * 1274 * @hide 1275 */ 1276 public void setConnectionId(int connectionId) { 1277 enforceNotSealed(); 1278 mConnectionId = connectionId; 1279 } 1280 1281 /** 1282 * {@inheritDoc} 1283 */ 1284 public int describeContents() { 1285 return 0; 1286 } 1287 1288 /** 1289 * Gets the id of the source node. 1290 * 1291 * @return The id. 1292 * 1293 * @hide 1294 */ 1295 public long getSourceNodeId() { 1296 return mSourceNodeId; 1297 } 1298 1299 /** 1300 * Sets if this instance is sealed. 1301 * 1302 * @param sealed Whether is sealed. 1303 * 1304 * @hide 1305 */ 1306 public void setSealed(boolean sealed) { 1307 mSealed = sealed; 1308 } 1309 1310 /** 1311 * Gets if this instance is sealed. 1312 * 1313 * @return Whether is sealed. 1314 * 1315 * @hide 1316 */ 1317 public boolean isSealed() { 1318 return mSealed; 1319 } 1320 1321 /** 1322 * Enforces that this instance is sealed. 1323 * 1324 * @throws IllegalStateException If this instance is not sealed. 1325 * 1326 * @hide 1327 */ 1328 protected void enforceSealed() { 1329 if (!isSealed()) { 1330 throw new IllegalStateException("Cannot perform this " 1331 + "action on a not sealed instance."); 1332 } 1333 } 1334 1335 private void enforceValidFocusDirection(int direction) { 1336 switch (direction) { 1337 case View.FOCUS_DOWN: 1338 case View.FOCUS_UP: 1339 case View.FOCUS_LEFT: 1340 case View.FOCUS_RIGHT: 1341 case View.FOCUS_FORWARD: 1342 case View.FOCUS_BACKWARD: 1343 case View.ACCESSIBILITY_FOCUS_DOWN: 1344 case View.ACCESSIBILITY_FOCUS_UP: 1345 case View.ACCESSIBILITY_FOCUS_LEFT: 1346 case View.ACCESSIBILITY_FOCUS_RIGHT: 1347 case View.ACCESSIBILITY_FOCUS_FORWARD: 1348 case View.ACCESSIBILITY_FOCUS_BACKWARD: 1349 return; 1350 default: 1351 throw new IllegalArgumentException("Unknown direction: " + direction); 1352 } 1353 } 1354 1355 private void enforceValidFocusType(int focusType) { 1356 switch (focusType) { 1357 case FOCUS_INPUT: 1358 case FOCUS_ACCESSIBILITY: 1359 return; 1360 default: 1361 throw new IllegalArgumentException("Unknown focus type: " + focusType); 1362 } 1363 } 1364 1365 /** 1366 * Enforces that this instance is not sealed. 1367 * 1368 * @throws IllegalStateException If this instance is sealed. 1369 * 1370 * @hide 1371 */ 1372 protected void enforceNotSealed() { 1373 if (isSealed()) { 1374 throw new IllegalStateException("Cannot perform this " 1375 + "action on a sealed instance."); 1376 } 1377 } 1378 1379 /** 1380 * Returns a cached instance if such is available otherwise a new one 1381 * and sets the source. 1382 * 1383 * @param source The source view. 1384 * @return An instance. 1385 * 1386 * @see #setSource(View) 1387 */ 1388 public static AccessibilityNodeInfo obtain(View source) { 1389 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1390 info.setSource(source); 1391 return info; 1392 } 1393 1394 /** 1395 * Returns a cached instance if such is available otherwise a new one 1396 * and sets the source. 1397 * 1398 * @param root The root of the virtual subtree. 1399 * @param virtualDescendantId The id of the virtual descendant. 1400 * @return An instance. 1401 * 1402 * @see #setSource(View, int) 1403 */ 1404 public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) { 1405 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1406 info.setSource(root, virtualDescendantId); 1407 return info; 1408 } 1409 1410 /** 1411 * Returns a cached instance if such is available otherwise a new one. 1412 * 1413 * @return An instance. 1414 */ 1415 public static AccessibilityNodeInfo obtain() { 1416 synchronized (sPoolLock) { 1417 if (sPool != null) { 1418 AccessibilityNodeInfo info = sPool; 1419 sPool = sPool.mNext; 1420 sPoolSize--; 1421 info.mNext = null; 1422 info.mIsInPool = false; 1423 return info; 1424 } 1425 return new AccessibilityNodeInfo(); 1426 } 1427 } 1428 1429 /** 1430 * Returns a cached instance if such is available or a new one is 1431 * create. The returned instance is initialized from the given 1432 * <code>info</code>. 1433 * 1434 * @param info The other info. 1435 * @return An instance. 1436 */ 1437 public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) { 1438 AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain(); 1439 infoClone.init(info); 1440 return infoClone; 1441 } 1442 1443 /** 1444 * Return an instance back to be reused. 1445 * <p> 1446 * <strong>Note:</strong> You must not touch the object after calling this function. 1447 * 1448 * @throws IllegalStateException If the info is already recycled. 1449 */ 1450 public void recycle() { 1451 if (mIsInPool) { 1452 throw new IllegalStateException("Info already recycled!"); 1453 } 1454 clear(); 1455 synchronized (sPoolLock) { 1456 if (sPoolSize <= MAX_POOL_SIZE) { 1457 mNext = sPool; 1458 sPool = this; 1459 mIsInPool = true; 1460 sPoolSize++; 1461 } 1462 } 1463 } 1464 1465 /** 1466 * {@inheritDoc} 1467 * <p> 1468 * <strong>Note:</strong> After the instance is written to a parcel it 1469 * is recycled. You must not touch the object after calling this function. 1470 * </p> 1471 */ 1472 public void writeToParcel(Parcel parcel, int flags) { 1473 parcel.writeInt(isSealed() ? 1 : 0); 1474 parcel.writeLong(mSourceNodeId); 1475 parcel.writeInt(mWindowId); 1476 parcel.writeLong(mParentNodeId); 1477 parcel.writeInt(mConnectionId); 1478 1479 SparseLongArray childIds = mChildNodeIds; 1480 final int childIdsSize = childIds.size(); 1481 parcel.writeInt(childIdsSize); 1482 for (int i = 0; i < childIdsSize; i++) { 1483 parcel.writeLong(childIds.valueAt(i)); 1484 } 1485 1486 parcel.writeInt(mBoundsInParent.top); 1487 parcel.writeInt(mBoundsInParent.bottom); 1488 parcel.writeInt(mBoundsInParent.left); 1489 parcel.writeInt(mBoundsInParent.right); 1490 1491 parcel.writeInt(mBoundsInScreen.top); 1492 parcel.writeInt(mBoundsInScreen.bottom); 1493 parcel.writeInt(mBoundsInScreen.left); 1494 parcel.writeInt(mBoundsInScreen.right); 1495 1496 parcel.writeInt(mActions); 1497 1498 parcel.writeInt(mMovementGranularities); 1499 1500 parcel.writeInt(mBooleanProperties); 1501 1502 parcel.writeCharSequence(mPackageName); 1503 parcel.writeCharSequence(mClassName); 1504 parcel.writeCharSequence(mText); 1505 parcel.writeCharSequence(mContentDescription); 1506 1507 // Since instances of this class are fetched via synchronous i.e. blocking 1508 // calls in IPCs we always recycle as soon as the instance is marshaled. 1509 recycle(); 1510 } 1511 1512 /** 1513 * Initializes this instance from another one. 1514 * 1515 * @param other The other instance. 1516 */ 1517 @SuppressWarnings("unchecked") 1518 private void init(AccessibilityNodeInfo other) { 1519 mSealed = other.mSealed; 1520 mSourceNodeId = other.mSourceNodeId; 1521 mParentNodeId = other.mParentNodeId; 1522 mWindowId = other.mWindowId; 1523 mConnectionId = other.mConnectionId; 1524 mBoundsInParent.set(other.mBoundsInParent); 1525 mBoundsInScreen.set(other.mBoundsInScreen); 1526 mPackageName = other.mPackageName; 1527 mClassName = other.mClassName; 1528 mText = other.mText; 1529 mContentDescription = other.mContentDescription; 1530 mActions= other.mActions; 1531 mBooleanProperties = other.mBooleanProperties; 1532 mMovementGranularities = other.mMovementGranularities; 1533 final int otherChildIdCount = other.mChildNodeIds.size(); 1534 for (int i = 0; i < otherChildIdCount; i++) { 1535 mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); 1536 } 1537 } 1538 1539 /** 1540 * Creates a new instance from a {@link Parcel}. 1541 * 1542 * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. 1543 */ 1544 private void initFromParcel(Parcel parcel) { 1545 mSealed = (parcel.readInt() == 1); 1546 mSourceNodeId = parcel.readLong(); 1547 mWindowId = parcel.readInt(); 1548 mParentNodeId = parcel.readLong(); 1549 mConnectionId = parcel.readInt(); 1550 1551 SparseLongArray childIds = mChildNodeIds; 1552 final int childrenSize = parcel.readInt(); 1553 for (int i = 0; i < childrenSize; i++) { 1554 final long childId = parcel.readLong(); 1555 childIds.put(i, childId); 1556 } 1557 1558 mBoundsInParent.top = parcel.readInt(); 1559 mBoundsInParent.bottom = parcel.readInt(); 1560 mBoundsInParent.left = parcel.readInt(); 1561 mBoundsInParent.right = parcel.readInt(); 1562 1563 mBoundsInScreen.top = parcel.readInt(); 1564 mBoundsInScreen.bottom = parcel.readInt(); 1565 mBoundsInScreen.left = parcel.readInt(); 1566 mBoundsInScreen.right = parcel.readInt(); 1567 1568 mActions = parcel.readInt(); 1569 1570 mMovementGranularities = parcel.readInt(); 1571 1572 mBooleanProperties = parcel.readInt(); 1573 1574 mPackageName = parcel.readCharSequence(); 1575 mClassName = parcel.readCharSequence(); 1576 mText = parcel.readCharSequence(); 1577 mContentDescription = parcel.readCharSequence(); 1578 } 1579 1580 /** 1581 * Clears the state of this instance. 1582 */ 1583 private void clear() { 1584 mSealed = false; 1585 mSourceNodeId = ROOT_NODE_ID; 1586 mParentNodeId = ROOT_NODE_ID; 1587 mWindowId = UNDEFINED; 1588 mConnectionId = UNDEFINED; 1589 mMovementGranularities = 0; 1590 mChildNodeIds.clear(); 1591 mBoundsInParent.set(0, 0, 0, 0); 1592 mBoundsInScreen.set(0, 0, 0, 0); 1593 mBooleanProperties = 0; 1594 mPackageName = null; 1595 mClassName = null; 1596 mText = null; 1597 mContentDescription = null; 1598 mActions = 0; 1599 } 1600 1601 /** 1602 * Gets the human readable action symbolic name. 1603 * 1604 * @param action The action. 1605 * @return The symbolic name. 1606 */ 1607 private static String getActionSymbolicName(int action) { 1608 switch (action) { 1609 case ACTION_FOCUS: 1610 return "ACTION_FOCUS"; 1611 case ACTION_CLEAR_FOCUS: 1612 return "ACTION_CLEAR_FOCUS"; 1613 case ACTION_SELECT: 1614 return "ACTION_SELECT"; 1615 case ACTION_CLEAR_SELECTION: 1616 return "ACTION_CLEAR_SELECTION"; 1617 case ACTION_CLICK: 1618 return "ACTION_CLICK"; 1619 case ACTION_LONG_CLICK: 1620 return "ACTION_LONG_CLICK"; 1621 case ACTION_ACCESSIBILITY_FOCUS: 1622 return "ACTION_ACCESSIBILITY_FOCUS"; 1623 case ACTION_CLEAR_ACCESSIBILITY_FOCUS: 1624 return "ACTION_CLEAR_ACCESSIBILITY_FOCUS"; 1625 case ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 1626 return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY"; 1627 case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: 1628 return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY"; 1629 case ACTION_NEXT_HTML_ELEMENT: 1630 return "ACTION_NEXT_HTML_ELEMENT"; 1631 case ACTION_PREVIOUS_HTML_ELEMENT: 1632 return "ACTION_PREVIOUS_HTML_ELEMENT"; 1633 case ACTION_SCROLL_FORWARD: 1634 return "ACTION_SCROLL_FORWARD"; 1635 case ACTION_SCROLL_BACKWARD: 1636 return "ACTION_SCROLL_BACKWARD"; 1637 default: 1638 throw new IllegalArgumentException("Unknown action: " + action); 1639 } 1640 } 1641 1642 /** 1643 * Gets the human readable movement granularity symbolic name. 1644 * 1645 * @param granularity The granularity. 1646 * @return The symbolic name. 1647 */ 1648 private static String getMovementGranularitySymbolicName(int granularity) { 1649 switch (granularity) { 1650 case MOVEMENT_GRANULARITY_CHARACTER: 1651 return "MOVEMENT_GRANULARITY_CHARACTER"; 1652 case MOVEMENT_GRANULARITY_WORD: 1653 return "MOVEMENT_GRANULARITY_WORD"; 1654 case MOVEMENT_GRANULARITY_LINE: 1655 return "MOVEMENT_GRANULARITY_LINE"; 1656 case MOVEMENT_GRANULARITY_PARAGRAPH: 1657 return "MOVEMENT_GRANULARITY_PARAGRAPH"; 1658 case MOVEMENT_GRANULARITY_PAGE: 1659 return "MOVEMENT_GRANULARITY_PAGE"; 1660 default: 1661 throw new IllegalArgumentException("Unknown movement granularity: " + granularity); 1662 } 1663 } 1664 1665 private boolean canPerformRequestOverConnection(long accessibilityNodeId) { 1666 return (mWindowId != UNDEFINED 1667 && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED 1668 && mConnectionId != UNDEFINED); 1669 } 1670 1671 @Override 1672 public boolean equals(Object object) { 1673 if (this == object) { 1674 return true; 1675 } 1676 if (object == null) { 1677 return false; 1678 } 1679 if (getClass() != object.getClass()) { 1680 return false; 1681 } 1682 AccessibilityNodeInfo other = (AccessibilityNodeInfo) object; 1683 if (mSourceNodeId != other.mSourceNodeId) { 1684 return false; 1685 } 1686 if (mWindowId != other.mWindowId) { 1687 return false; 1688 } 1689 return true; 1690 } 1691 1692 @Override 1693 public int hashCode() { 1694 final int prime = 31; 1695 int result = 1; 1696 result = prime * result + getAccessibilityViewId(mSourceNodeId); 1697 result = prime * result + getVirtualDescendantId(mSourceNodeId); 1698 result = prime * result + mWindowId; 1699 return result; 1700 } 1701 1702 @Override 1703 public String toString() { 1704 StringBuilder builder = new StringBuilder(); 1705 builder.append(super.toString()); 1706 1707 if (DEBUG) { 1708 builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); 1709 builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); 1710 builder.append("; mParentNodeId: " + mParentNodeId); 1711 1712 int granularities = mMovementGranularities; 1713 builder.append("; MovementGranularities: ["); 1714 while (granularities != 0) { 1715 final int granularity = 1 << Integer.numberOfTrailingZeros(granularities); 1716 granularities &= ~granularity; 1717 builder.append(getMovementGranularitySymbolicName(granularity)); 1718 if (granularities != 0) { 1719 builder.append(", "); 1720 } 1721 } 1722 builder.append("]"); 1723 1724 SparseLongArray childIds = mChildNodeIds; 1725 builder.append("; childAccessibilityIds: ["); 1726 for (int i = 0, count = childIds.size(); i < count; i++) { 1727 builder.append(childIds.valueAt(i)); 1728 if (i < count - 1) { 1729 builder.append(", "); 1730 } 1731 } 1732 builder.append("]"); 1733 } 1734 1735 builder.append("; boundsInParent: " + mBoundsInParent); 1736 builder.append("; boundsInScreen: " + mBoundsInScreen); 1737 1738 builder.append("; packageName: ").append(mPackageName); 1739 builder.append("; className: ").append(mClassName); 1740 builder.append("; text: ").append(mText); 1741 builder.append("; contentDescription: ").append(mContentDescription); 1742 1743 builder.append("; checkable: ").append(isCheckable()); 1744 builder.append("; checked: ").append(isChecked()); 1745 builder.append("; focusable: ").append(isFocusable()); 1746 builder.append("; focused: ").append(isFocused()); 1747 builder.append("; selected: ").append(isSelected()); 1748 builder.append("; clickable: ").append(isClickable()); 1749 builder.append("; longClickable: ").append(isLongClickable()); 1750 builder.append("; enabled: ").append(isEnabled()); 1751 builder.append("; password: ").append(isPassword()); 1752 builder.append("; scrollable: " + isScrollable()); 1753 1754 builder.append("; ["); 1755 for (int actionBits = mActions; actionBits != 0;) { 1756 final int action = 1 << Integer.numberOfTrailingZeros(actionBits); 1757 actionBits &= ~action; 1758 builder.append(getActionSymbolicName(action)); 1759 if (actionBits != 0) { 1760 builder.append(", "); 1761 } 1762 } 1763 builder.append("]"); 1764 1765 return builder.toString(); 1766 } 1767 1768 /** 1769 * @see Parcelable.Creator 1770 */ 1771 public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = 1772 new Parcelable.Creator<AccessibilityNodeInfo>() { 1773 public AccessibilityNodeInfo createFromParcel(Parcel parcel) { 1774 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1775 info.initFromParcel(parcel); 1776 return info; 1777 } 1778 1779 public AccessibilityNodeInfo[] newArray(int size) { 1780 return new AccessibilityNodeInfo[size]; 1781 } 1782 }; 1783} 1784