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