AccessibilityNodeInfo.java revision ee33ad24cdc31ed0d7f99e110e041b0a63c9b0f1
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 // TODO: These are a workaround for 6623031. Remove when fixed. 386 private int mActualAndReportedWindowLeftDelta; 387 private int mActualAndReportedWindowTopDelta; 388 389 /** 390 * Hide constructor from clients. 391 */ 392 private AccessibilityNodeInfo() { 393 /* do nothing */ 394 } 395 396 /** 397 * Sets the source. 398 * <p> 399 * <strong>Note:</strong> Cannot be called from an 400 * {@link android.accessibilityservice.AccessibilityService}. 401 * This class is made immutable before being delivered to an AccessibilityService. 402 * </p> 403 * 404 * @param source The info source. 405 */ 406 public void setSource(View source) { 407 setSource(source, UNDEFINED); 408 } 409 410 /** 411 * Sets the source to be a virtual descendant of the given <code>root</code>. 412 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 413 * is set as the source. 414 * <p> 415 * A virtual descendant is an imaginary View that is reported as a part of the view 416 * hierarchy for accessibility purposes. This enables custom views that draw complex 417 * content to report themselves as a tree of virtual views, thus conveying their 418 * logical structure. 419 * </p> 420 * <p> 421 * <strong>Note:</strong> Cannot be called from an 422 * {@link android.accessibilityservice.AccessibilityService}. 423 * This class is made immutable before being delivered to an AccessibilityService. 424 * </p> 425 * 426 * @param root The root of the virtual subtree. 427 * @param virtualDescendantId The id of the virtual descendant. 428 */ 429 public void setSource(View root, int virtualDescendantId) { 430 enforceNotSealed(); 431 mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED; 432 final int rootAccessibilityViewId = 433 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 434 mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 435 if (root != null) { 436 mActualAndReportedWindowLeftDelta = root.getActualAndReportedWindowLeftDelta(); 437 mActualAndReportedWindowTopDelta = root.getActualAndReportedWindowTopDelta(); 438 } 439 } 440 441 /** 442 * Find the view that has the specified focus type. The search starts from 443 * the view represented by this node info. 444 * 445 * @param focus The focus to find. One of {@link #FOCUS_INPUT} or 446 * {@link #FOCUS_ACCESSIBILITY}. 447 * @return The node info of the focused view or null. 448 * 449 * @see #FOCUS_INPUT 450 * @see #FOCUS_ACCESSIBILITY 451 */ 452 public AccessibilityNodeInfo findFocus(int focus) { 453 enforceSealed(); 454 enforceValidFocusType(focus); 455 if (!canPerformRequestOverConnection(mSourceNodeId)) { 456 return null; 457 } 458 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId, 459 mSourceNodeId, focus); 460 } 461 462 /** 463 * Searches for the nearest view in the specified direction that can take 464 * the input focus. 465 * 466 * @param direction The direction. Can be one of: 467 * {@link View#FOCUS_DOWN}, 468 * {@link View#FOCUS_UP}, 469 * {@link View#FOCUS_LEFT}, 470 * {@link View#FOCUS_RIGHT}, 471 * {@link View#FOCUS_FORWARD}, 472 * {@link View#FOCUS_BACKWARD}, 473 * {@link View#ACCESSIBILITY_FOCUS_FORWARD}, 474 * {@link View#ACCESSIBILITY_FOCUS_BACKWARD}, 475 * {@link View#ACCESSIBILITY_FOCUS_UP}, 476 * {@link View#ACCESSIBILITY_FOCUS_RIGHT}, 477 * {@link View#ACCESSIBILITY_FOCUS_DOWN}, 478 * {@link View#ACCESSIBILITY_FOCUS_LEFT}. 479 * 480 * @return The node info for the view that can take accessibility focus. 481 */ 482 public AccessibilityNodeInfo focusSearch(int direction) { 483 enforceSealed(); 484 enforceValidFocusDirection(direction); 485 if (!canPerformRequestOverConnection(mSourceNodeId)) { 486 return null; 487 } 488 return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId, 489 mSourceNodeId, direction); 490 } 491 492 /** 493 * Gets the id of the window from which the info comes from. 494 * 495 * @return The window id. 496 */ 497 public int getWindowId() { 498 return mWindowId; 499 } 500 501 /** 502 * @return The ids of the children. 503 * 504 * @hide 505 */ 506 public SparseLongArray getChildNodeIds() { 507 return mChildNodeIds; 508 } 509 510 /** 511 * Gets the number of children. 512 * 513 * @return The child count. 514 */ 515 public int getChildCount() { 516 return mChildNodeIds.size(); 517 } 518 519 /** 520 * Get the child at given index. 521 * <p> 522 * <strong>Note:</strong> It is a client responsibility to recycle the 523 * received info by calling {@link AccessibilityNodeInfo#recycle()} 524 * to avoid creating of multiple instances. 525 * </p> 526 * 527 * @param index The child index. 528 * @return The child node. 529 * 530 * @throws IllegalStateException If called outside of an AccessibilityService. 531 * 532 */ 533 public AccessibilityNodeInfo getChild(int index) { 534 enforceSealed(); 535 if (!canPerformRequestOverConnection(mSourceNodeId)) { 536 return null; 537 } 538 final long childId = mChildNodeIds.get(index); 539 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 540 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId, 541 childId, FLAG_PREFETCH_DESCENDANTS); 542 } 543 544 /** 545 * Adds a child. 546 * <p> 547 * <strong>Note:</strong> Cannot be called from an 548 * {@link android.accessibilityservice.AccessibilityService}. 549 * This class is made immutable before being delivered to an AccessibilityService. 550 * </p> 551 * 552 * @param child The child. 553 * 554 * @throws IllegalStateException If called from an AccessibilityService. 555 */ 556 public void addChild(View child) { 557 addChild(child, UNDEFINED); 558 } 559 560 /** 561 * Adds a virtual child which is a descendant of the given <code>root</code>. 562 * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root 563 * is added as a child. 564 * <p> 565 * A virtual descendant is an imaginary View that is reported as a part of the view 566 * hierarchy for accessibility purposes. This enables custom views that draw complex 567 * content to report them selves as a tree of virtual views, thus conveying their 568 * logical structure. 569 * </p> 570 * 571 * @param root The root of the virtual subtree. 572 * @param virtualDescendantId The id of the virtual child. 573 */ 574 public void addChild(View root, int virtualDescendantId) { 575 enforceNotSealed(); 576 final int index = mChildNodeIds.size(); 577 final int rootAccessibilityViewId = 578 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 579 final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 580 mChildNodeIds.put(index, childNodeId); 581 } 582 583 /** 584 * Gets the actions that can be performed on the node. 585 * 586 * @return The bit mask of with actions. 587 * 588 * @see AccessibilityNodeInfo#ACTION_FOCUS 589 * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS 590 * @see AccessibilityNodeInfo#ACTION_SELECT 591 * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION 592 * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS 593 * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS 594 * @see AccessibilityNodeInfo#ACTION_CLICK 595 * @see AccessibilityNodeInfo#ACTION_LONG_CLICK 596 * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY 597 * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY 598 * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT 599 * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT 600 * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD 601 * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD 602 */ 603 public int getActions() { 604 return mActions; 605 } 606 607 /** 608 * Adds an action that can be performed on the node. 609 * <p> 610 * <strong>Note:</strong> Cannot be called from an 611 * {@link android.accessibilityservice.AccessibilityService}. 612 * This class is made immutable before being delivered to an AccessibilityService. 613 * </p> 614 * 615 * @param action The action. 616 * 617 * @throws IllegalStateException If called from an AccessibilityService. 618 */ 619 public void addAction(int action) { 620 enforceNotSealed(); 621 mActions |= action; 622 } 623 624 /** 625 * Sets the movement granularities for traversing the text of this node. 626 * <p> 627 * <strong>Note:</strong> Cannot be called from an 628 * {@link android.accessibilityservice.AccessibilityService}. 629 * This class is made immutable before being delivered to an AccessibilityService. 630 * </p> 631 * 632 * @param granularities The bit mask with granularities. 633 * 634 * @throws IllegalStateException If called from an AccessibilityService. 635 */ 636 public void setMovementGranularities(int granularities) { 637 enforceNotSealed(); 638 mMovementGranularities = granularities; 639 } 640 641 /** 642 * Gets the movement granularities for traversing the text of this node. 643 * 644 * @return The bit mask with granularities. 645 */ 646 public int getMovementGranularities() { 647 return mMovementGranularities; 648 } 649 650 /** 651 * Performs an action on the node. 652 * <p> 653 * <strong>Note:</strong> An action can be performed only if the request is made 654 * from an {@link android.accessibilityservice.AccessibilityService}. 655 * </p> 656 * 657 * @param action The action to perform. 658 * @return True if the action was performed. 659 * 660 * @throws IllegalStateException If called outside of an AccessibilityService. 661 */ 662 public boolean performAction(int action) { 663 enforceSealed(); 664 if (!canPerformRequestOverConnection(mSourceNodeId)) { 665 return false; 666 } 667 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 668 return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, 669 action, null); 670 } 671 672 /** 673 * Performs an action on the node. 674 * <p> 675 * <strong>Note:</strong> An action can be performed only if the request is made 676 * from an {@link android.accessibilityservice.AccessibilityService}. 677 * </p> 678 * 679 * @param action The action to perform. 680 * @param arguments A bundle with additional arguments. 681 * @return True if the action was performed. 682 * 683 * @throws IllegalStateException If called outside of an AccessibilityService. 684 */ 685 public boolean performAction(int action, Bundle arguments) { 686 enforceSealed(); 687 if (!canPerformRequestOverConnection(mSourceNodeId)) { 688 return false; 689 } 690 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 691 return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId, 692 action, arguments); 693 } 694 695 /** 696 * Finds {@link AccessibilityNodeInfo}s by text. The match is case 697 * insensitive containment. The search is relative to this info i.e. 698 * this info is the root of the traversed tree. 699 * 700 * <p> 701 * <strong>Note:</strong> It is a client responsibility to recycle the 702 * received info by calling {@link AccessibilityNodeInfo#recycle()} 703 * to avoid creating of multiple instances. 704 * </p> 705 * 706 * @param text The searched text. 707 * @return A list of node info. 708 */ 709 public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) { 710 enforceSealed(); 711 if (!canPerformRequestOverConnection(mSourceNodeId)) { 712 return Collections.emptyList(); 713 } 714 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 715 return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId, 716 text); 717 } 718 719 /** 720 * Gets the parent. 721 * <p> 722 * <strong>Note:</strong> It is a client responsibility to recycle the 723 * received info by calling {@link AccessibilityNodeInfo#recycle()} 724 * to avoid creating of multiple instances. 725 * </p> 726 * 727 * @return The parent. 728 */ 729 public AccessibilityNodeInfo getParent() { 730 enforceSealed(); 731 if (!canPerformRequestOverConnection(mParentNodeId)) { 732 return null; 733 } 734 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 735 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, 736 mWindowId, mParentNodeId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); 737 } 738 739 /** 740 * @return The parent node id. 741 * 742 * @hide 743 */ 744 public long getParentNodeId() { 745 return mParentNodeId; 746 } 747 748 /** 749 * Sets the parent. 750 * <p> 751 * <strong>Note:</strong> Cannot be called from an 752 * {@link android.accessibilityservice.AccessibilityService}. 753 * This class is made immutable before being delivered to an AccessibilityService. 754 * </p> 755 * 756 * @param parent The parent. 757 * 758 * @throws IllegalStateException If called from an AccessibilityService. 759 */ 760 public void setParent(View parent) { 761 setParent(parent, UNDEFINED); 762 } 763 764 /** 765 * Sets the parent to be a virtual descendant of the given <code>root</code>. 766 * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root 767 * is set as the parent. 768 * <p> 769 * A virtual descendant is an imaginary View that is reported as a part of the view 770 * hierarchy for accessibility purposes. This enables custom views that draw complex 771 * content to report them selves as a tree of virtual views, thus conveying their 772 * logical structure. 773 * </p> 774 * <p> 775 * <strong>Note:</strong> Cannot be called from an 776 * {@link android.accessibilityservice.AccessibilityService}. 777 * This class is made immutable before being delivered to an AccessibilityService. 778 * </p> 779 * 780 * @param root The root of the virtual subtree. 781 * @param virtualDescendantId The id of the virtual descendant. 782 */ 783 public void setParent(View root, int virtualDescendantId) { 784 enforceNotSealed(); 785 final int rootAccessibilityViewId = 786 (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 787 mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId); 788 } 789 790 /** 791 * Gets the node bounds in parent coordinates. 792 * 793 * @param outBounds The output node bounds. 794 */ 795 public void getBoundsInParent(Rect outBounds) { 796 outBounds.set(mBoundsInParent.left, mBoundsInParent.top, 797 mBoundsInParent.right, mBoundsInParent.bottom); 798 } 799 800 /** 801 * Sets the node bounds in parent coordinates. 802 * <p> 803 * <strong>Note:</strong> Cannot be called from an 804 * {@link android.accessibilityservice.AccessibilityService}. 805 * This class is made immutable before being delivered to an AccessibilityService. 806 * </p> 807 * 808 * @param bounds The node bounds. 809 * 810 * @throws IllegalStateException If called from an AccessibilityService. 811 */ 812 public void setBoundsInParent(Rect bounds) { 813 enforceNotSealed(); 814 mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 815 } 816 817 /** 818 * Gets the node bounds in screen coordinates. 819 * 820 * @param outBounds The output node bounds. 821 */ 822 public void getBoundsInScreen(Rect outBounds) { 823 outBounds.set(mBoundsInScreen.left, mBoundsInScreen.top, 824 mBoundsInScreen.right, mBoundsInScreen.bottom); 825 } 826 827 /** 828 * Sets the node bounds in screen coordinates. 829 * <p> 830 * <strong>Note:</strong> Cannot be called from an 831 * {@link android.accessibilityservice.AccessibilityService}. 832 * This class is made immutable before being delivered to an AccessibilityService. 833 * </p> 834 * 835 * @param bounds The node bounds. 836 * 837 * @throws IllegalStateException If called from an AccessibilityService. 838 */ 839 public void setBoundsInScreen(Rect bounds) { 840 enforceNotSealed(); 841 mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom); 842 mBoundsInScreen.offset(mActualAndReportedWindowLeftDelta, mActualAndReportedWindowTopDelta); 843 } 844 845 /** 846 * Gets whether this node is checkable. 847 * 848 * @return True if the node is checkable. 849 */ 850 public boolean isCheckable() { 851 return getBooleanProperty(PROPERTY_CHECKABLE); 852 } 853 854 /** 855 * Sets whether this node is checkable. 856 * <p> 857 * <strong>Note:</strong> Cannot be called from an 858 * {@link android.accessibilityservice.AccessibilityService}. 859 * This class is made immutable before being delivered to an AccessibilityService. 860 * </p> 861 * 862 * @param checkable True if the node is checkable. 863 * 864 * @throws IllegalStateException If called from an AccessibilityService. 865 */ 866 public void setCheckable(boolean checkable) { 867 setBooleanProperty(PROPERTY_CHECKABLE, checkable); 868 } 869 870 /** 871 * Gets whether this node is checked. 872 * 873 * @return True if the node is checked. 874 */ 875 public boolean isChecked() { 876 return getBooleanProperty(PROPERTY_CHECKED); 877 } 878 879 /** 880 * Sets whether this node is checked. 881 * <p> 882 * <strong>Note:</strong> Cannot be called from an 883 * {@link android.accessibilityservice.AccessibilityService}. 884 * This class is made immutable before being delivered to an AccessibilityService. 885 * </p> 886 * 887 * @param checked True if the node is checked. 888 * 889 * @throws IllegalStateException If called from an AccessibilityService. 890 */ 891 public void setChecked(boolean checked) { 892 setBooleanProperty(PROPERTY_CHECKED, checked); 893 } 894 895 /** 896 * Gets whether this node is focusable. 897 * 898 * @return True if the node is focusable. 899 */ 900 public boolean isFocusable() { 901 return getBooleanProperty(PROPERTY_FOCUSABLE); 902 } 903 904 /** 905 * Sets whether this node is focusable. 906 * <p> 907 * <strong>Note:</strong> Cannot be called from an 908 * {@link android.accessibilityservice.AccessibilityService}. 909 * This class is made immutable before being delivered to an AccessibilityService. 910 * </p> 911 * 912 * @param focusable True if the node is focusable. 913 * 914 * @throws IllegalStateException If called from an AccessibilityService. 915 */ 916 public void setFocusable(boolean focusable) { 917 setBooleanProperty(PROPERTY_FOCUSABLE, focusable); 918 } 919 920 /** 921 * Gets whether this node is focused. 922 * 923 * @return True if the node is focused. 924 */ 925 public boolean isFocused() { 926 return getBooleanProperty(PROPERTY_FOCUSED); 927 } 928 929 /** 930 * Sets whether this node is focused. 931 * <p> 932 * <strong>Note:</strong> Cannot be called from an 933 * {@link android.accessibilityservice.AccessibilityService}. 934 * This class is made immutable before being delivered to an AccessibilityService. 935 * </p> 936 * 937 * @param focused True if the node is focused. 938 * 939 * @throws IllegalStateException If called from an AccessibilityService. 940 */ 941 public void setFocused(boolean focused) { 942 setBooleanProperty(PROPERTY_FOCUSED, focused); 943 } 944 945 /** 946 * Sets whether this node is visible to the user. 947 * 948 * @return Whether the node is visible to the user. 949 */ 950 public boolean isVisibleToUser() { 951 return getBooleanProperty(PROPERTY_VISIBLE_TO_USER); 952 } 953 954 /** 955 * Sets whether this node is visible to the user. 956 * <p> 957 * <strong>Note:</strong> Cannot be called from an 958 * {@link android.accessibilityservice.AccessibilityService}. 959 * This class is made immutable before being delivered to an AccessibilityService. 960 * </p> 961 * 962 * @param visibleToUser Whether the node is visible to the user. 963 * 964 * @throws IllegalStateException If called from an AccessibilityService. 965 */ 966 public void setVisibleToUser(boolean visibleToUser) { 967 setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser); 968 } 969 970 /** 971 * Gets whether this node is accessibility focused. 972 * 973 * @return True if the node is accessibility focused. 974 */ 975 public boolean isAccessibilityFocused() { 976 return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED); 977 } 978 979 /** 980 * Sets whether this node is accessibility focused. 981 * <p> 982 * <strong>Note:</strong> Cannot be called from an 983 * {@link android.accessibilityservice.AccessibilityService}. 984 * This class is made immutable before being delivered to an AccessibilityService. 985 * </p> 986 * 987 * @param focused True if the node is accessibility focused. 988 * 989 * @throws IllegalStateException If called from an AccessibilityService. 990 */ 991 public void setAccessibilityFocused(boolean focused) { 992 setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused); 993 } 994 995 /** 996 * Gets whether this node is selected. 997 * 998 * @return True if the node is selected. 999 */ 1000 public boolean isSelected() { 1001 return getBooleanProperty(PROPERTY_SELECTED); 1002 } 1003 1004 /** 1005 * Sets whether this node is selected. 1006 * <p> 1007 * <strong>Note:</strong> Cannot be called from an 1008 * {@link android.accessibilityservice.AccessibilityService}. 1009 * This class is made immutable before being delivered to an AccessibilityService. 1010 * </p> 1011 * 1012 * @param selected True if the node is selected. 1013 * 1014 * @throws IllegalStateException If called from an AccessibilityService. 1015 */ 1016 public void setSelected(boolean selected) { 1017 setBooleanProperty(PROPERTY_SELECTED, selected); 1018 } 1019 1020 /** 1021 * Gets whether this node is clickable. 1022 * 1023 * @return True if the node is clickable. 1024 */ 1025 public boolean isClickable() { 1026 return getBooleanProperty(PROPERTY_CLICKABLE); 1027 } 1028 1029 /** 1030 * Sets whether this node is clickable. 1031 * <p> 1032 * <strong>Note:</strong> Cannot be called from an 1033 * {@link android.accessibilityservice.AccessibilityService}. 1034 * This class is made immutable before being delivered to an AccessibilityService. 1035 * </p> 1036 * 1037 * @param clickable True if the node is clickable. 1038 * 1039 * @throws IllegalStateException If called from an AccessibilityService. 1040 */ 1041 public void setClickable(boolean clickable) { 1042 setBooleanProperty(PROPERTY_CLICKABLE, clickable); 1043 } 1044 1045 /** 1046 * Gets whether this node is long clickable. 1047 * 1048 * @return True if the node is long clickable. 1049 */ 1050 public boolean isLongClickable() { 1051 return getBooleanProperty(PROPERTY_LONG_CLICKABLE); 1052 } 1053 1054 /** 1055 * Sets whether this node is long clickable. 1056 * <p> 1057 * <strong>Note:</strong> Cannot be called from an 1058 * {@link android.accessibilityservice.AccessibilityService}. 1059 * This class is made immutable before being delivered to an AccessibilityService. 1060 * </p> 1061 * 1062 * @param longClickable True if the node is long clickable. 1063 * 1064 * @throws IllegalStateException If called from an AccessibilityService. 1065 */ 1066 public void setLongClickable(boolean longClickable) { 1067 setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable); 1068 } 1069 1070 /** 1071 * Gets whether this node is enabled. 1072 * 1073 * @return True if the node is enabled. 1074 */ 1075 public boolean isEnabled() { 1076 return getBooleanProperty(PROPERTY_ENABLED); 1077 } 1078 1079 /** 1080 * Sets whether this node is enabled. 1081 * <p> 1082 * <strong>Note:</strong> Cannot be called from an 1083 * {@link android.accessibilityservice.AccessibilityService}. 1084 * This class is made immutable before being delivered to an AccessibilityService. 1085 * </p> 1086 * 1087 * @param enabled True if the node is enabled. 1088 * 1089 * @throws IllegalStateException If called from an AccessibilityService. 1090 */ 1091 public void setEnabled(boolean enabled) { 1092 setBooleanProperty(PROPERTY_ENABLED, enabled); 1093 } 1094 1095 /** 1096 * Gets whether this node is a password. 1097 * 1098 * @return True if the node is a password. 1099 */ 1100 public boolean isPassword() { 1101 return getBooleanProperty(PROPERTY_PASSWORD); 1102 } 1103 1104 /** 1105 * Sets whether this node is a password. 1106 * <p> 1107 * <strong>Note:</strong> Cannot be called from an 1108 * {@link android.accessibilityservice.AccessibilityService}. 1109 * This class is made immutable before being delivered to an AccessibilityService. 1110 * </p> 1111 * 1112 * @param password True if the node is a password. 1113 * 1114 * @throws IllegalStateException If called from an AccessibilityService. 1115 */ 1116 public void setPassword(boolean password) { 1117 setBooleanProperty(PROPERTY_PASSWORD, password); 1118 } 1119 1120 /** 1121 * Gets if the node is scrollable. 1122 * 1123 * @return True if the node is scrollable, false otherwise. 1124 */ 1125 public boolean isScrollable() { 1126 return getBooleanProperty(PROPERTY_SCROLLABLE); 1127 } 1128 1129 /** 1130 * Sets if the node is scrollable. 1131 * <p> 1132 * <strong>Note:</strong> Cannot be called from an 1133 * {@link android.accessibilityservice.AccessibilityService}. 1134 * This class is made immutable before being delivered to an AccessibilityService. 1135 * </p> 1136 * 1137 * @param scrollable True if the node is scrollable, false otherwise. 1138 * 1139 * @throws IllegalStateException If called from an AccessibilityService. 1140 */ 1141 public void setScrollable(boolean scrollable) { 1142 enforceNotSealed(); 1143 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 1144 } 1145 1146 /** 1147 * Gets the package this node comes from. 1148 * 1149 * @return The package name. 1150 */ 1151 public CharSequence getPackageName() { 1152 return mPackageName; 1153 } 1154 1155 /** 1156 * Sets the package this node comes from. 1157 * <p> 1158 * <strong>Note:</strong> Cannot be called from an 1159 * {@link android.accessibilityservice.AccessibilityService}. 1160 * This class is made immutable before being delivered to an AccessibilityService. 1161 * </p> 1162 * 1163 * @param packageName The package name. 1164 * 1165 * @throws IllegalStateException If called from an AccessibilityService. 1166 */ 1167 public void setPackageName(CharSequence packageName) { 1168 enforceNotSealed(); 1169 mPackageName = packageName; 1170 } 1171 1172 /** 1173 * Gets the class this node comes from. 1174 * 1175 * @return The class name. 1176 */ 1177 public CharSequence getClassName() { 1178 return mClassName; 1179 } 1180 1181 /** 1182 * Sets the class this node comes from. 1183 * <p> 1184 * <strong>Note:</strong> Cannot be called from an 1185 * {@link android.accessibilityservice.AccessibilityService}. 1186 * This class is made immutable before being delivered to an AccessibilityService. 1187 * </p> 1188 * 1189 * @param className The class name. 1190 * 1191 * @throws IllegalStateException If called from an AccessibilityService. 1192 */ 1193 public void setClassName(CharSequence className) { 1194 enforceNotSealed(); 1195 mClassName = className; 1196 } 1197 1198 /** 1199 * Gets the text of this node. 1200 * 1201 * @return The text. 1202 */ 1203 public CharSequence getText() { 1204 return mText; 1205 } 1206 1207 /** 1208 * Sets the text of this node. 1209 * <p> 1210 * <strong>Note:</strong> Cannot be called from an 1211 * {@link android.accessibilityservice.AccessibilityService}. 1212 * This class is made immutable before being delivered to an AccessibilityService. 1213 * </p> 1214 * 1215 * @param text The text. 1216 * 1217 * @throws IllegalStateException If called from an AccessibilityService. 1218 */ 1219 public void setText(CharSequence text) { 1220 enforceNotSealed(); 1221 mText = text; 1222 } 1223 1224 /** 1225 * Gets the content description of this node. 1226 * 1227 * @return The content description. 1228 */ 1229 public CharSequence getContentDescription() { 1230 return mContentDescription; 1231 } 1232 1233 /** 1234 * Sets the content description of this node. 1235 * <p> 1236 * <strong>Note:</strong> Cannot be called from an 1237 * {@link android.accessibilityservice.AccessibilityService}. 1238 * This class is made immutable before being delivered to an AccessibilityService. 1239 * </p> 1240 * 1241 * @param contentDescription The content description. 1242 * 1243 * @throws IllegalStateException If called from an AccessibilityService. 1244 */ 1245 public void setContentDescription(CharSequence contentDescription) { 1246 enforceNotSealed(); 1247 mContentDescription = contentDescription; 1248 } 1249 1250 /** 1251 * Gets the value of a boolean property. 1252 * 1253 * @param property The property. 1254 * @return The value. 1255 */ 1256 private boolean getBooleanProperty(int property) { 1257 return (mBooleanProperties & property) != 0; 1258 } 1259 1260 /** 1261 * Sets a boolean property. 1262 * 1263 * @param property The property. 1264 * @param value The value. 1265 * 1266 * @throws IllegalStateException If called from an AccessibilityService. 1267 */ 1268 private void setBooleanProperty(int property, boolean value) { 1269 enforceNotSealed(); 1270 if (value) { 1271 mBooleanProperties |= property; 1272 } else { 1273 mBooleanProperties &= ~property; 1274 } 1275 } 1276 1277 /** 1278 * Sets the unique id of the IAccessibilityServiceConnection over which 1279 * this instance can send requests to the system. 1280 * 1281 * @param connectionId The connection id. 1282 * 1283 * @hide 1284 */ 1285 public void setConnectionId(int connectionId) { 1286 enforceNotSealed(); 1287 mConnectionId = connectionId; 1288 } 1289 1290 /** 1291 * {@inheritDoc} 1292 */ 1293 public int describeContents() { 1294 return 0; 1295 } 1296 1297 /** 1298 * Gets the id of the source node. 1299 * 1300 * @return The id. 1301 * 1302 * @hide 1303 */ 1304 public long getSourceNodeId() { 1305 return mSourceNodeId; 1306 } 1307 1308 /** 1309 * Sets if this instance is sealed. 1310 * 1311 * @param sealed Whether is sealed. 1312 * 1313 * @hide 1314 */ 1315 public void setSealed(boolean sealed) { 1316 mSealed = sealed; 1317 } 1318 1319 /** 1320 * Gets if this instance is sealed. 1321 * 1322 * @return Whether is sealed. 1323 * 1324 * @hide 1325 */ 1326 public boolean isSealed() { 1327 return mSealed; 1328 } 1329 1330 /** 1331 * Enforces that this instance is sealed. 1332 * 1333 * @throws IllegalStateException If this instance is not sealed. 1334 * 1335 * @hide 1336 */ 1337 protected void enforceSealed() { 1338 if (!isSealed()) { 1339 throw new IllegalStateException("Cannot perform this " 1340 + "action on a not sealed instance."); 1341 } 1342 } 1343 1344 private void enforceValidFocusDirection(int direction) { 1345 switch (direction) { 1346 case View.FOCUS_DOWN: 1347 case View.FOCUS_UP: 1348 case View.FOCUS_LEFT: 1349 case View.FOCUS_RIGHT: 1350 case View.FOCUS_FORWARD: 1351 case View.FOCUS_BACKWARD: 1352 case View.ACCESSIBILITY_FOCUS_DOWN: 1353 case View.ACCESSIBILITY_FOCUS_UP: 1354 case View.ACCESSIBILITY_FOCUS_LEFT: 1355 case View.ACCESSIBILITY_FOCUS_RIGHT: 1356 case View.ACCESSIBILITY_FOCUS_FORWARD: 1357 case View.ACCESSIBILITY_FOCUS_BACKWARD: 1358 return; 1359 default: 1360 throw new IllegalArgumentException("Unknown direction: " + direction); 1361 } 1362 } 1363 1364 private void enforceValidFocusType(int focusType) { 1365 switch (focusType) { 1366 case FOCUS_INPUT: 1367 case FOCUS_ACCESSIBILITY: 1368 return; 1369 default: 1370 throw new IllegalArgumentException("Unknown focus type: " + focusType); 1371 } 1372 } 1373 1374 /** 1375 * Enforces that this instance is not sealed. 1376 * 1377 * @throws IllegalStateException If this instance is sealed. 1378 * 1379 * @hide 1380 */ 1381 protected void enforceNotSealed() { 1382 if (isSealed()) { 1383 throw new IllegalStateException("Cannot perform this " 1384 + "action on a sealed instance."); 1385 } 1386 } 1387 1388 /** 1389 * Returns a cached instance if such is available otherwise a new one 1390 * and sets the source. 1391 * 1392 * @param source The source view. 1393 * @return An instance. 1394 * 1395 * @see #setSource(View) 1396 */ 1397 public static AccessibilityNodeInfo obtain(View source) { 1398 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1399 info.setSource(source); 1400 return info; 1401 } 1402 1403 /** 1404 * Returns a cached instance if such is available otherwise a new one 1405 * and sets the source. 1406 * 1407 * @param root The root of the virtual subtree. 1408 * @param virtualDescendantId The id of the virtual descendant. 1409 * @return An instance. 1410 * 1411 * @see #setSource(View, int) 1412 */ 1413 public static AccessibilityNodeInfo obtain(View root, int virtualDescendantId) { 1414 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1415 info.setSource(root, virtualDescendantId); 1416 return info; 1417 } 1418 1419 /** 1420 * Returns a cached instance if such is available otherwise a new one. 1421 * 1422 * @return An instance. 1423 */ 1424 public static AccessibilityNodeInfo obtain() { 1425 synchronized (sPoolLock) { 1426 if (sPool != null) { 1427 AccessibilityNodeInfo info = sPool; 1428 sPool = sPool.mNext; 1429 sPoolSize--; 1430 info.mNext = null; 1431 info.mIsInPool = false; 1432 return info; 1433 } 1434 return new AccessibilityNodeInfo(); 1435 } 1436 } 1437 1438 /** 1439 * Returns a cached instance if such is available or a new one is 1440 * create. The returned instance is initialized from the given 1441 * <code>info</code>. 1442 * 1443 * @param info The other info. 1444 * @return An instance. 1445 */ 1446 public static AccessibilityNodeInfo obtain(AccessibilityNodeInfo info) { 1447 AccessibilityNodeInfo infoClone = AccessibilityNodeInfo.obtain(); 1448 infoClone.init(info); 1449 return infoClone; 1450 } 1451 1452 /** 1453 * Return an instance back to be reused. 1454 * <p> 1455 * <strong>Note:</strong> You must not touch the object after calling this function. 1456 * 1457 * @throws IllegalStateException If the info is already recycled. 1458 */ 1459 public void recycle() { 1460 if (mIsInPool) { 1461 throw new IllegalStateException("Info already recycled!"); 1462 } 1463 clear(); 1464 synchronized (sPoolLock) { 1465 if (sPoolSize <= MAX_POOL_SIZE) { 1466 mNext = sPool; 1467 sPool = this; 1468 mIsInPool = true; 1469 sPoolSize++; 1470 } 1471 } 1472 } 1473 1474 /** 1475 * {@inheritDoc} 1476 * <p> 1477 * <strong>Note:</strong> After the instance is written to a parcel it 1478 * is recycled. You must not touch the object after calling this function. 1479 * </p> 1480 */ 1481 public void writeToParcel(Parcel parcel, int flags) { 1482 parcel.writeInt(isSealed() ? 1 : 0); 1483 parcel.writeLong(mSourceNodeId); 1484 parcel.writeInt(mWindowId); 1485 parcel.writeLong(mParentNodeId); 1486 parcel.writeInt(mConnectionId); 1487 1488 SparseLongArray childIds = mChildNodeIds; 1489 final int childIdsSize = childIds.size(); 1490 parcel.writeInt(childIdsSize); 1491 for (int i = 0; i < childIdsSize; i++) { 1492 parcel.writeLong(childIds.valueAt(i)); 1493 } 1494 1495 parcel.writeInt(mBoundsInParent.top); 1496 parcel.writeInt(mBoundsInParent.bottom); 1497 parcel.writeInt(mBoundsInParent.left); 1498 parcel.writeInt(mBoundsInParent.right); 1499 1500 parcel.writeInt(mBoundsInScreen.top); 1501 parcel.writeInt(mBoundsInScreen.bottom); 1502 parcel.writeInt(mBoundsInScreen.left); 1503 parcel.writeInt(mBoundsInScreen.right); 1504 1505 parcel.writeInt(mActions); 1506 1507 parcel.writeInt(mMovementGranularities); 1508 1509 parcel.writeInt(mBooleanProperties); 1510 1511 parcel.writeCharSequence(mPackageName); 1512 parcel.writeCharSequence(mClassName); 1513 parcel.writeCharSequence(mText); 1514 parcel.writeCharSequence(mContentDescription); 1515 1516 // Since instances of this class are fetched via synchronous i.e. blocking 1517 // calls in IPCs we always recycle as soon as the instance is marshaled. 1518 recycle(); 1519 } 1520 1521 /** 1522 * Initializes this instance from another one. 1523 * 1524 * @param other The other instance. 1525 */ 1526 @SuppressWarnings("unchecked") 1527 private void init(AccessibilityNodeInfo other) { 1528 mSealed = other.mSealed; 1529 mSourceNodeId = other.mSourceNodeId; 1530 mParentNodeId = other.mParentNodeId; 1531 mWindowId = other.mWindowId; 1532 mConnectionId = other.mConnectionId; 1533 mBoundsInParent.set(other.mBoundsInParent); 1534 mBoundsInScreen.set(other.mBoundsInScreen); 1535 mPackageName = other.mPackageName; 1536 mClassName = other.mClassName; 1537 mText = other.mText; 1538 mContentDescription = other.mContentDescription; 1539 mActions= other.mActions; 1540 mBooleanProperties = other.mBooleanProperties; 1541 mMovementGranularities = other.mMovementGranularities; 1542 final int otherChildIdCount = other.mChildNodeIds.size(); 1543 for (int i = 0; i < otherChildIdCount; i++) { 1544 mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i)); 1545 } 1546 } 1547 1548 /** 1549 * Creates a new instance from a {@link Parcel}. 1550 * 1551 * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. 1552 */ 1553 private void initFromParcel(Parcel parcel) { 1554 mSealed = (parcel.readInt() == 1); 1555 mSourceNodeId = parcel.readLong(); 1556 mWindowId = parcel.readInt(); 1557 mParentNodeId = parcel.readLong(); 1558 mConnectionId = parcel.readInt(); 1559 1560 SparseLongArray childIds = mChildNodeIds; 1561 final int childrenSize = parcel.readInt(); 1562 for (int i = 0; i < childrenSize; i++) { 1563 final long childId = parcel.readLong(); 1564 childIds.put(i, childId); 1565 } 1566 1567 mBoundsInParent.top = parcel.readInt(); 1568 mBoundsInParent.bottom = parcel.readInt(); 1569 mBoundsInParent.left = parcel.readInt(); 1570 mBoundsInParent.right = parcel.readInt(); 1571 1572 mBoundsInScreen.top = parcel.readInt(); 1573 mBoundsInScreen.bottom = parcel.readInt(); 1574 mBoundsInScreen.left = parcel.readInt(); 1575 mBoundsInScreen.right = parcel.readInt(); 1576 1577 mActions = parcel.readInt(); 1578 1579 mMovementGranularities = parcel.readInt(); 1580 1581 mBooleanProperties = parcel.readInt(); 1582 1583 mPackageName = parcel.readCharSequence(); 1584 mClassName = parcel.readCharSequence(); 1585 mText = parcel.readCharSequence(); 1586 mContentDescription = parcel.readCharSequence(); 1587 } 1588 1589 /** 1590 * Clears the state of this instance. 1591 */ 1592 private void clear() { 1593 mSealed = false; 1594 mSourceNodeId = ROOT_NODE_ID; 1595 mParentNodeId = ROOT_NODE_ID; 1596 mWindowId = UNDEFINED; 1597 mConnectionId = UNDEFINED; 1598 mMovementGranularities = 0; 1599 mChildNodeIds.clear(); 1600 mBoundsInParent.set(0, 0, 0, 0); 1601 mBoundsInScreen.set(0, 0, 0, 0); 1602 mBooleanProperties = 0; 1603 mPackageName = null; 1604 mClassName = null; 1605 mText = null; 1606 mContentDescription = null; 1607 mActions = 0; 1608 } 1609 1610 /** 1611 * Gets the human readable action symbolic name. 1612 * 1613 * @param action The action. 1614 * @return The symbolic name. 1615 */ 1616 private static String getActionSymbolicName(int action) { 1617 switch (action) { 1618 case ACTION_FOCUS: 1619 return "ACTION_FOCUS"; 1620 case ACTION_CLEAR_FOCUS: 1621 return "ACTION_CLEAR_FOCUS"; 1622 case ACTION_SELECT: 1623 return "ACTION_SELECT"; 1624 case ACTION_CLEAR_SELECTION: 1625 return "ACTION_CLEAR_SELECTION"; 1626 case ACTION_CLICK: 1627 return "ACTION_CLICK"; 1628 case ACTION_LONG_CLICK: 1629 return "ACTION_LONG_CLICK"; 1630 case ACTION_ACCESSIBILITY_FOCUS: 1631 return "ACTION_ACCESSIBILITY_FOCUS"; 1632 case ACTION_CLEAR_ACCESSIBILITY_FOCUS: 1633 return "ACTION_CLEAR_ACCESSIBILITY_FOCUS"; 1634 case ACTION_NEXT_AT_MOVEMENT_GRANULARITY: 1635 return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY"; 1636 case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: 1637 return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY"; 1638 case ACTION_NEXT_HTML_ELEMENT: 1639 return "ACTION_NEXT_HTML_ELEMENT"; 1640 case ACTION_PREVIOUS_HTML_ELEMENT: 1641 return "ACTION_PREVIOUS_HTML_ELEMENT"; 1642 case ACTION_SCROLL_FORWARD: 1643 return "ACTION_SCROLL_FORWARD"; 1644 case ACTION_SCROLL_BACKWARD: 1645 return "ACTION_SCROLL_BACKWARD"; 1646 default: 1647 throw new IllegalArgumentException("Unknown action: " + action); 1648 } 1649 } 1650 1651 /** 1652 * Gets the human readable movement granularity symbolic name. 1653 * 1654 * @param granularity The granularity. 1655 * @return The symbolic name. 1656 */ 1657 private static String getMovementGranularitySymbolicName(int granularity) { 1658 switch (granularity) { 1659 case MOVEMENT_GRANULARITY_CHARACTER: 1660 return "MOVEMENT_GRANULARITY_CHARACTER"; 1661 case MOVEMENT_GRANULARITY_WORD: 1662 return "MOVEMENT_GRANULARITY_WORD"; 1663 case MOVEMENT_GRANULARITY_LINE: 1664 return "MOVEMENT_GRANULARITY_LINE"; 1665 case MOVEMENT_GRANULARITY_PARAGRAPH: 1666 return "MOVEMENT_GRANULARITY_PARAGRAPH"; 1667 case MOVEMENT_GRANULARITY_PAGE: 1668 return "MOVEMENT_GRANULARITY_PAGE"; 1669 default: 1670 throw new IllegalArgumentException("Unknown movement granularity: " + granularity); 1671 } 1672 } 1673 1674 private boolean canPerformRequestOverConnection(long accessibilityNodeId) { 1675 return (mWindowId != UNDEFINED 1676 && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED 1677 && mConnectionId != UNDEFINED); 1678 } 1679 1680 @Override 1681 public boolean equals(Object object) { 1682 if (this == object) { 1683 return true; 1684 } 1685 if (object == null) { 1686 return false; 1687 } 1688 if (getClass() != object.getClass()) { 1689 return false; 1690 } 1691 AccessibilityNodeInfo other = (AccessibilityNodeInfo) object; 1692 if (mSourceNodeId != other.mSourceNodeId) { 1693 return false; 1694 } 1695 if (mWindowId != other.mWindowId) { 1696 return false; 1697 } 1698 return true; 1699 } 1700 1701 @Override 1702 public int hashCode() { 1703 final int prime = 31; 1704 int result = 1; 1705 result = prime * result + getAccessibilityViewId(mSourceNodeId); 1706 result = prime * result + getVirtualDescendantId(mSourceNodeId); 1707 result = prime * result + mWindowId; 1708 return result; 1709 } 1710 1711 @Override 1712 public String toString() { 1713 StringBuilder builder = new StringBuilder(); 1714 builder.append(super.toString()); 1715 1716 if (DEBUG) { 1717 builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); 1718 builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); 1719 builder.append("; mParentNodeId: " + mParentNodeId); 1720 1721 int granularities = mMovementGranularities; 1722 builder.append("; MovementGranularities: ["); 1723 while (granularities != 0) { 1724 final int granularity = 1 << Integer.numberOfTrailingZeros(granularities); 1725 granularities &= ~granularity; 1726 builder.append(getMovementGranularitySymbolicName(granularity)); 1727 if (granularities != 0) { 1728 builder.append(", "); 1729 } 1730 } 1731 builder.append("]"); 1732 1733 SparseLongArray childIds = mChildNodeIds; 1734 builder.append("; childAccessibilityIds: ["); 1735 for (int i = 0, count = childIds.size(); i < count; i++) { 1736 builder.append(childIds.valueAt(i)); 1737 if (i < count - 1) { 1738 builder.append(", "); 1739 } 1740 } 1741 builder.append("]"); 1742 } 1743 1744 builder.append("; boundsInParent: " + mBoundsInParent); 1745 builder.append("; boundsInScreen: " + mBoundsInScreen); 1746 1747 builder.append("; packageName: ").append(mPackageName); 1748 builder.append("; className: ").append(mClassName); 1749 builder.append("; text: ").append(mText); 1750 builder.append("; contentDescription: ").append(mContentDescription); 1751 1752 builder.append("; checkable: ").append(isCheckable()); 1753 builder.append("; checked: ").append(isChecked()); 1754 builder.append("; focusable: ").append(isFocusable()); 1755 builder.append("; focused: ").append(isFocused()); 1756 builder.append("; selected: ").append(isSelected()); 1757 builder.append("; clickable: ").append(isClickable()); 1758 builder.append("; longClickable: ").append(isLongClickable()); 1759 builder.append("; enabled: ").append(isEnabled()); 1760 builder.append("; password: ").append(isPassword()); 1761 builder.append("; scrollable: " + isScrollable()); 1762 1763 builder.append("; ["); 1764 for (int actionBits = mActions; actionBits != 0;) { 1765 final int action = 1 << Integer.numberOfTrailingZeros(actionBits); 1766 actionBits &= ~action; 1767 builder.append(getActionSymbolicName(action)); 1768 if (actionBits != 0) { 1769 builder.append(", "); 1770 } 1771 } 1772 builder.append("]"); 1773 1774 return builder.toString(); 1775 } 1776 1777 /** 1778 * @see Parcelable.Creator 1779 */ 1780 public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = 1781 new Parcelable.Creator<AccessibilityNodeInfo>() { 1782 public AccessibilityNodeInfo createFromParcel(Parcel parcel) { 1783 AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); 1784 info.initFromParcel(parcel); 1785 return info; 1786 } 1787 1788 public AccessibilityNodeInfo[] newArray(int size) { 1789 return new AccessibilityNodeInfo[size]; 1790 } 1791 }; 1792} 1793