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