AccessibilityRecord.java revision 110414928ae13674b7ec6b816a45cf70ed521683
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.os.Parcelable; 20import android.view.View; 21 22import java.util.ArrayList; 23import java.util.List; 24 25/** 26 * Represents a record in an {@link AccessibilityEvent} and contains information 27 * about state change of its source {@link android.view.View}. When a view fires 28 * an accessibility event it requests from its parent to dispatch the 29 * constructed event. The parent may optionally append a record for itself 30 * for providing more context to 31 * {@link android.accessibilityservice.AccessibilityService}s. Hence, 32 * accessibility services can facilitate additional accessibility records 33 * to enhance feedback. 34 * </p> 35 * <p> 36 * Once the accessibility event containing a record is dispatched the record is 37 * made immutable and calling a state mutation method generates an error. 38 * </p> 39 * <p> 40 * <strong>Note:</strong> Not all properties are applicable to all accessibility 41 * event types. For detailed information please refer to {@link AccessibilityEvent}. 42 * </p> 43 * 44 * <div class="special reference"> 45 * <h3>Developer Guides</h3> 46 * <p>For more information about creating and processing AccessibilityRecords, read the 47 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a> 48 * developer guide.</p> 49 * </div> 50 * 51 * @see AccessibilityEvent 52 * @see AccessibilityManager 53 * @see android.accessibilityservice.AccessibilityService 54 * @see AccessibilityNodeInfo 55 */ 56public class AccessibilityRecord { 57 58 private static final int UNDEFINED = -1; 59 60 private static final int PROPERTY_CHECKED = 0x00000001; 61 private static final int PROPERTY_ENABLED = 0x00000002; 62 private static final int PROPERTY_PASSWORD = 0x00000004; 63 private static final int PROPERTY_FULL_SCREEN = 0x00000080; 64 private static final int PROPERTY_SCROLLABLE = 0x00000100; 65 66 private static final int GET_SOURCE_PREFETCH_FLAGS = 67 AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS 68 | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS 69 | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS; 70 71 // Housekeeping 72 private static final int MAX_POOL_SIZE = 10; 73 private static final Object sPoolLock = new Object(); 74 private static AccessibilityRecord sPool; 75 private static int sPoolSize; 76 private AccessibilityRecord mNext; 77 private boolean mIsInPool; 78 79 boolean mSealed; 80 int mBooleanProperties; 81 int mCurrentItemIndex = UNDEFINED; 82 int mItemCount = UNDEFINED; 83 int mFromIndex = UNDEFINED; 84 int mToIndex = UNDEFINED; 85 int mScrollX = UNDEFINED; 86 int mScrollY = UNDEFINED; 87 int mMaxScrollX = UNDEFINED; 88 int mMaxScrollY = UNDEFINED; 89 90 int mAddedCount= UNDEFINED; 91 int mRemovedCount = UNDEFINED; 92 long mSourceNodeId = AccessibilityNodeInfo.makeNodeId(UNDEFINED, UNDEFINED); 93 int mSourceWindowId = UNDEFINED; 94 95 CharSequence mClassName; 96 CharSequence mContentDescription; 97 CharSequence mBeforeText; 98 Parcelable mParcelableData; 99 100 final List<CharSequence> mText = new ArrayList<CharSequence>(); 101 102 int mConnectionId = UNDEFINED; 103 104 /* 105 * Hide constructor. 106 */ 107 AccessibilityRecord() { 108 } 109 110 /** 111 * Sets the event source. 112 * 113 * @param source The source. 114 * 115 * @throws IllegalStateException If called from an AccessibilityService. 116 */ 117 public void setSource(View source) { 118 setSource(source, UNDEFINED); 119 } 120 121 /** 122 * Sets the source to be a virtual descendant of the given <code>root</code>. 123 * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root 124 * is set as the source. 125 * <p> 126 * A virtual descendant is an imaginary View that is reported as a part of the view 127 * hierarchy for accessibility purposes. This enables custom views that draw complex 128 * content to report them selves as a tree of virtual views, thus conveying their 129 * logical structure. 130 * </p> 131 * 132 * @param root The root of the virtual subtree. 133 * @param virtualDescendantId The id of the virtual descendant. 134 */ 135 public void setSource(View root, int virtualDescendantId) { 136 enforceNotSealed(); 137 mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED; 138 final int rootViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED; 139 mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId); 140 } 141 142 /** 143 * Gets the {@link AccessibilityNodeInfo} of the event source. 144 * <p> 145 * <strong>Note:</strong> It is a client responsibility to recycle the received info 146 * by calling {@link AccessibilityNodeInfo#recycle() AccessibilityNodeInfo#recycle()} 147 * to avoid creating of multiple instances. 148 * </p> 149 * @return The info of the source. 150 */ 151 public AccessibilityNodeInfo getSource() { 152 enforceSealed(); 153 if (mConnectionId == UNDEFINED || mSourceWindowId == UNDEFINED 154 || AccessibilityNodeInfo.getAccessibilityViewId(mSourceNodeId) == UNDEFINED) { 155 return null; 156 } 157 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 158 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mSourceWindowId, 159 mSourceNodeId, GET_SOURCE_PREFETCH_FLAGS); 160 } 161 162 /** 163 * Gets the id of the window from which the event comes from. 164 * 165 * @return The window id. 166 */ 167 public int getWindowId() { 168 return mSourceWindowId; 169 } 170 171 /** 172 * Gets if the source is checked. 173 * 174 * @return True if the view is checked, false otherwise. 175 */ 176 public boolean isChecked() { 177 return getBooleanProperty(PROPERTY_CHECKED); 178 } 179 180 /** 181 * Sets if the source is checked. 182 * 183 * @param isChecked True if the view is checked, false otherwise. 184 * 185 * @throws IllegalStateException If called from an AccessibilityService. 186 */ 187 public void setChecked(boolean isChecked) { 188 enforceNotSealed(); 189 setBooleanProperty(PROPERTY_CHECKED, isChecked); 190 } 191 192 /** 193 * Gets if the source is enabled. 194 * 195 * @return True if the view is enabled, false otherwise. 196 */ 197 public boolean isEnabled() { 198 return getBooleanProperty(PROPERTY_ENABLED); 199 } 200 201 /** 202 * Sets if the source is enabled. 203 * 204 * @param isEnabled True if the view is enabled, false otherwise. 205 * 206 * @throws IllegalStateException If called from an AccessibilityService. 207 */ 208 public void setEnabled(boolean isEnabled) { 209 enforceNotSealed(); 210 setBooleanProperty(PROPERTY_ENABLED, isEnabled); 211 } 212 213 /** 214 * Gets if the source is a password field. 215 * 216 * @return True if the view is a password field, false otherwise. 217 */ 218 public boolean isPassword() { 219 return getBooleanProperty(PROPERTY_PASSWORD); 220 } 221 222 /** 223 * Sets if the source is a password field. 224 * 225 * @param isPassword True if the view is a password field, false otherwise. 226 * 227 * @throws IllegalStateException If called from an AccessibilityService. 228 */ 229 public void setPassword(boolean isPassword) { 230 enforceNotSealed(); 231 setBooleanProperty(PROPERTY_PASSWORD, isPassword); 232 } 233 234 /** 235 * Gets if the source is taking the entire screen. 236 * 237 * @return True if the source is full screen, false otherwise. 238 */ 239 public boolean isFullScreen() { 240 return getBooleanProperty(PROPERTY_FULL_SCREEN); 241 } 242 243 /** 244 * Sets if the source is taking the entire screen. 245 * 246 * @param isFullScreen True if the source is full screen, false otherwise. 247 * 248 * @throws IllegalStateException If called from an AccessibilityService. 249 */ 250 public void setFullScreen(boolean isFullScreen) { 251 enforceNotSealed(); 252 setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); 253 } 254 255 /** 256 * Gets if the source is scrollable. 257 * 258 * @return True if the source is scrollable, false otherwise. 259 */ 260 public boolean isScrollable() { 261 return getBooleanProperty(PROPERTY_SCROLLABLE); 262 } 263 264 /** 265 * Sets if the source is scrollable. 266 * 267 * @param scrollable True if the source is scrollable, false otherwise. 268 * 269 * @throws IllegalStateException If called from an AccessibilityService. 270 */ 271 public void setScrollable(boolean scrollable) { 272 enforceNotSealed(); 273 setBooleanProperty(PROPERTY_SCROLLABLE, scrollable); 274 } 275 276 /** 277 * Gets the number of items that can be visited. 278 * 279 * @return The number of items. 280 */ 281 public int getItemCount() { 282 return mItemCount; 283 } 284 285 /** 286 * Sets the number of items that can be visited. 287 * 288 * @param itemCount The number of items. 289 * 290 * @throws IllegalStateException If called from an AccessibilityService. 291 */ 292 public void setItemCount(int itemCount) { 293 enforceNotSealed(); 294 mItemCount = itemCount; 295 } 296 297 /** 298 * Gets the index of the source in the list of items the can be visited. 299 * 300 * @return The current item index. 301 */ 302 public int getCurrentItemIndex() { 303 return mCurrentItemIndex; 304 } 305 306 /** 307 * Sets the index of the source in the list of items that can be visited. 308 * 309 * @param currentItemIndex The current item index. 310 * 311 * @throws IllegalStateException If called from an AccessibilityService. 312 */ 313 public void setCurrentItemIndex(int currentItemIndex) { 314 enforceNotSealed(); 315 mCurrentItemIndex = currentItemIndex; 316 } 317 318 /** 319 * Gets the index of the first character of the changed sequence, 320 * or the beginning of a text selection or the index of the first 321 * visible item when scrolling. 322 * 323 * @return The index of the first character or selection 324 * start or the first visible item. 325 */ 326 public int getFromIndex() { 327 return mFromIndex; 328 } 329 330 /** 331 * Sets the index of the first character of the changed sequence 332 * or the beginning of a text selection or the index of the first 333 * visible item when scrolling. 334 * 335 * @param fromIndex The index of the first character or selection 336 * start or the first visible item. 337 * 338 * @throws IllegalStateException If called from an AccessibilityService. 339 */ 340 public void setFromIndex(int fromIndex) { 341 enforceNotSealed(); 342 mFromIndex = fromIndex; 343 } 344 345 /** 346 * Gets the index of text selection end or the index of the last 347 * visible item when scrolling. 348 * 349 * @return The index of selection end or last item index. 350 */ 351 public int getToIndex() { 352 return mToIndex; 353 } 354 355 /** 356 * Sets the index of text selection end or the index of the last 357 * visible item when scrolling. 358 * 359 * @param toIndex The index of selection end or last item index. 360 */ 361 public void setToIndex(int toIndex) { 362 enforceNotSealed(); 363 mToIndex = toIndex; 364 } 365 366 /** 367 * Gets the scroll offset of the source left edge in pixels. 368 * 369 * @return The scroll. 370 */ 371 public int getScrollX() { 372 return mScrollX; 373 } 374 375 /** 376 * Sets the scroll offset of the source left edge in pixels. 377 * 378 * @param scrollX The scroll. 379 */ 380 public void setScrollX(int scrollX) { 381 enforceNotSealed(); 382 mScrollX = scrollX; 383 } 384 385 /** 386 * Gets the scroll offset of the source top edge in pixels. 387 * 388 * @return The scroll. 389 */ 390 public int getScrollY() { 391 return mScrollY; 392 } 393 394 /** 395 * Sets the scroll offset of the source top edge in pixels. 396 * 397 * @param scrollY The scroll. 398 */ 399 public void setScrollY(int scrollY) { 400 enforceNotSealed(); 401 mScrollY = scrollY; 402 } 403 404 /** 405 * Gets the max scroll offset of the source left edge in pixels. 406 * 407 * @return The max scroll. 408 */ 409 public int getMaxScrollX() { 410 return mMaxScrollX; 411 } 412 413 /** 414 * Sets the max scroll offset of the source left edge in pixels. 415 * 416 * @param maxScrollX The max scroll. 417 */ 418 public void setMaxScrollX(int maxScrollX) { 419 enforceNotSealed(); 420 mMaxScrollX = maxScrollX; 421 } 422 423 /** 424 * Gets the max scroll offset of the source top edge in pixels. 425 * 426 * @return The max scroll. 427 */ 428 public int getMaxScrollY() { 429 return mMaxScrollY; 430 } 431 432 /** 433 * Sets the max scroll offset of the source top edge in pixels. 434 * 435 * @param maxScrollY The max scroll. 436 */ 437 public void setMaxScrollY(int maxScrollY) { 438 enforceNotSealed(); 439 mMaxScrollY = maxScrollY; 440 } 441 442 /** 443 * Gets the number of added characters. 444 * 445 * @return The number of added characters. 446 */ 447 public int getAddedCount() { 448 return mAddedCount; 449 } 450 451 /** 452 * Sets the number of added characters. 453 * 454 * @param addedCount The number of added characters. 455 * 456 * @throws IllegalStateException If called from an AccessibilityService. 457 */ 458 public void setAddedCount(int addedCount) { 459 enforceNotSealed(); 460 mAddedCount = addedCount; 461 } 462 463 /** 464 * Gets the number of removed characters. 465 * 466 * @return The number of removed characters. 467 */ 468 public int getRemovedCount() { 469 return mRemovedCount; 470 } 471 472 /** 473 * Sets the number of removed characters. 474 * 475 * @param removedCount The number of removed characters. 476 * 477 * @throws IllegalStateException If called from an AccessibilityService. 478 */ 479 public void setRemovedCount(int removedCount) { 480 enforceNotSealed(); 481 mRemovedCount = removedCount; 482 } 483 484 /** 485 * Gets the class name of the source. 486 * 487 * @return The class name. 488 */ 489 public CharSequence getClassName() { 490 return mClassName; 491 } 492 493 /** 494 * Sets the class name of the source. 495 * 496 * @param className The lass name. 497 * 498 * @throws IllegalStateException If called from an AccessibilityService. 499 */ 500 public void setClassName(CharSequence className) { 501 enforceNotSealed(); 502 mClassName = className; 503 } 504 505 /** 506 * Gets the text of the event. The index in the list represents the priority 507 * of the text. Specifically, the lower the index the higher the priority. 508 * 509 * @return The text. 510 */ 511 public List<CharSequence> getText() { 512 return mText; 513 } 514 515 /** 516 * Sets the text before a change. 517 * 518 * @return The text before the change. 519 */ 520 public CharSequence getBeforeText() { 521 return mBeforeText; 522 } 523 524 /** 525 * Sets the text before a change. 526 * 527 * @param beforeText The text before the change. 528 * 529 * @throws IllegalStateException If called from an AccessibilityService. 530 */ 531 public void setBeforeText(CharSequence beforeText) { 532 enforceNotSealed(); 533 mBeforeText = beforeText; 534 } 535 536 /** 537 * Gets the description of the source. 538 * 539 * @return The description. 540 */ 541 public CharSequence getContentDescription() { 542 return mContentDescription; 543 } 544 545 /** 546 * Sets the description of the source. 547 * 548 * @param contentDescription The description. 549 * 550 * @throws IllegalStateException If called from an AccessibilityService. 551 */ 552 public void setContentDescription(CharSequence contentDescription) { 553 enforceNotSealed(); 554 mContentDescription = contentDescription; 555 } 556 557 /** 558 * Gets the {@link Parcelable} data. 559 * 560 * @return The parcelable data. 561 */ 562 public Parcelable getParcelableData() { 563 return mParcelableData; 564 } 565 566 /** 567 * Sets the {@link Parcelable} data of the event. 568 * 569 * @param parcelableData The parcelable data. 570 * 571 * @throws IllegalStateException If called from an AccessibilityService. 572 */ 573 public void setParcelableData(Parcelable parcelableData) { 574 enforceNotSealed(); 575 mParcelableData = parcelableData; 576 } 577 578 /** 579 * Gets the id of the source node. 580 * 581 * @return The id. 582 * 583 * @hide 584 */ 585 public long getSourceNodeId() { 586 return mSourceNodeId; 587 } 588 589 /** 590 * Sets the unique id of the IAccessibilityServiceConnection over which 591 * this instance can send requests to the system. 592 * 593 * @param connectionId The connection id. 594 * 595 * @hide 596 */ 597 public void setConnectionId(int connectionId) { 598 enforceNotSealed(); 599 mConnectionId = connectionId; 600 } 601 602 /** 603 * Sets if this instance is sealed. 604 * 605 * @param sealed Whether is sealed. 606 * 607 * @hide 608 */ 609 public void setSealed(boolean sealed) { 610 mSealed = sealed; 611 } 612 613 /** 614 * Gets if this instance is sealed. 615 * 616 * @return Whether is sealed. 617 */ 618 boolean isSealed() { 619 return mSealed; 620 } 621 622 /** 623 * Enforces that this instance is sealed. 624 * 625 * @throws IllegalStateException If this instance is not sealed. 626 */ 627 void enforceSealed() { 628 if (!isSealed()) { 629 throw new IllegalStateException("Cannot perform this " 630 + "action on a not sealed instance."); 631 } 632 } 633 634 /** 635 * Enforces that this instance is not sealed. 636 * 637 * @throws IllegalStateException If this instance is sealed. 638 */ 639 void enforceNotSealed() { 640 if (isSealed()) { 641 throw new IllegalStateException("Cannot perform this " 642 + "action on a sealed instance."); 643 } 644 } 645 646 /** 647 * Gets the value of a boolean property. 648 * 649 * @param property The property. 650 * @return The value. 651 */ 652 private boolean getBooleanProperty(int property) { 653 return (mBooleanProperties & property) == property; 654 } 655 656 /** 657 * Sets a boolean property. 658 * 659 * @param property The property. 660 * @param value The value. 661 */ 662 private void setBooleanProperty(int property, boolean value) { 663 if (value) { 664 mBooleanProperties |= property; 665 } else { 666 mBooleanProperties &= ~property; 667 } 668 } 669 670 /** 671 * Returns a cached instance if such is available or a new one is 672 * instantiated. The instance is initialized with data from the 673 * given record. 674 * 675 * @return An instance. 676 */ 677 public static AccessibilityRecord obtain(AccessibilityRecord record) { 678 AccessibilityRecord clone = AccessibilityRecord.obtain(); 679 clone.init(record); 680 return clone; 681 } 682 683 /** 684 * Returns a cached instance if such is available or a new one is 685 * instantiated. 686 * 687 * @return An instance. 688 */ 689 public static AccessibilityRecord obtain() { 690 synchronized (sPoolLock) { 691 if (sPool != null) { 692 AccessibilityRecord record = sPool; 693 sPool = sPool.mNext; 694 sPoolSize--; 695 record.mNext = null; 696 record.mIsInPool = false; 697 return record; 698 } 699 return new AccessibilityRecord(); 700 } 701 } 702 703 /** 704 * Return an instance back to be reused. 705 * <p> 706 * <strong>Note:</strong> You must not touch the object after calling this function. 707 * 708 * @throws IllegalStateException If the record is already recycled. 709 */ 710 public void recycle() { 711 if (mIsInPool) { 712 throw new IllegalStateException("Record already recycled!"); 713 } 714 clear(); 715 synchronized (sPoolLock) { 716 if (sPoolSize <= MAX_POOL_SIZE) { 717 mNext = sPool; 718 sPool = this; 719 mIsInPool = true; 720 sPoolSize++; 721 } 722 } 723 } 724 725 /** 726 * Initialize this record from another one. 727 * 728 * @param record The to initialize from. 729 */ 730 void init(AccessibilityRecord record) { 731 mSealed = record.mSealed; 732 mBooleanProperties = record.mBooleanProperties; 733 mCurrentItemIndex = record.mCurrentItemIndex; 734 mItemCount = record.mItemCount; 735 mFromIndex = record.mFromIndex; 736 mToIndex = record.mToIndex; 737 mScrollX = record.mScrollX; 738 mScrollY = record.mScrollY; 739 mMaxScrollX = record.mMaxScrollX; 740 mMaxScrollY = record.mMaxScrollY; 741 mAddedCount = record.mAddedCount; 742 mRemovedCount = record.mRemovedCount; 743 mClassName = record.mClassName; 744 mContentDescription = record.mContentDescription; 745 mBeforeText = record.mBeforeText; 746 mParcelableData = record.mParcelableData; 747 mText.addAll(record.mText); 748 mSourceWindowId = record.mSourceWindowId; 749 mSourceNodeId = record.mSourceNodeId; 750 mConnectionId = record.mConnectionId; 751 } 752 753 /** 754 * Clears the state of this instance. 755 */ 756 void clear() { 757 mSealed = false; 758 mBooleanProperties = 0; 759 mCurrentItemIndex = UNDEFINED; 760 mItemCount = UNDEFINED; 761 mFromIndex = UNDEFINED; 762 mToIndex = UNDEFINED; 763 mScrollX = UNDEFINED; 764 mScrollY = UNDEFINED; 765 mMaxScrollX = UNDEFINED; 766 mMaxScrollY = UNDEFINED; 767 mAddedCount = UNDEFINED; 768 mRemovedCount = UNDEFINED; 769 mClassName = null; 770 mContentDescription = null; 771 mBeforeText = null; 772 mParcelableData = null; 773 mText.clear(); 774 mSourceNodeId = AccessibilityNodeInfo.makeNodeId(UNDEFINED, UNDEFINED); 775 mSourceWindowId = UNDEFINED; 776 mConnectionId = UNDEFINED; 777 } 778 779 @Override 780 public String toString() { 781 StringBuilder builder = new StringBuilder(); 782 builder.append(" [ ClassName: " + mClassName); 783 builder.append("; Text: " + mText); 784 builder.append("; ContentDescription: " + mContentDescription); 785 builder.append("; ItemCount: " + mItemCount); 786 builder.append("; CurrentItemIndex: " + mCurrentItemIndex); 787 builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED)); 788 builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); 789 builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); 790 builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); 791 builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); 792 builder.append("; BeforeText: " + mBeforeText); 793 builder.append("; FromIndex: " + mFromIndex); 794 builder.append("; ToIndex: " + mToIndex); 795 builder.append("; ScrollX: " + mScrollX); 796 builder.append("; ScrollY: " + mScrollY); 797 builder.append("; MaxScrollX: " + mMaxScrollX); 798 builder.append("; MaxScrollY: " + mMaxScrollY); 799 builder.append("; AddedCount: " + mAddedCount); 800 builder.append("; RemovedCount: " + mRemovedCount); 801 builder.append("; ParcelableData: " + mParcelableData); 802 builder.append(" ]"); 803 return builder.toString(); 804 } 805} 806