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