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