AccessibilityRecord.java revision 8b6c7dd2fe1016a8f765f98e8114d5f491f02353
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 public int getMaxScrollX() { 396 return mMaxScrollX; 397 } 398 /** 399 * Sets the max scroll offset of the source left edge in pixels. 400 * 401 * @param maxScrollX The max scroll. 402 */ 403 public void setMaxScrollX(int maxScrollX) { 404 enforceNotSealed(); 405 mMaxScrollX = maxScrollX; 406 } 407 408 /** 409 * Gets the max scroll offset of the source top edge in pixels. 410 * 411 * @return The max scroll. 412 */ 413 public int getMaxScrollY() { 414 return mMaxScrollY; 415 } 416 417 /** 418 * Sets the max scroll offset of the source top edge in pixels. 419 * 420 * @param maxScrollY The max scroll. 421 */ 422 public void setMaxScrollY(int maxScrollY) { 423 enforceNotSealed(); 424 mMaxScrollY = maxScrollY; 425 } 426 427 /** 428 * Gets the number of added characters. 429 * 430 * @return The number of added characters. 431 */ 432 public int getAddedCount() { 433 return mAddedCount; 434 } 435 436 /** 437 * Sets the number of added characters. 438 * 439 * @param addedCount The number of added characters. 440 * 441 * @throws IllegalStateException If called from an AccessibilityService. 442 */ 443 public void setAddedCount(int addedCount) { 444 enforceNotSealed(); 445 mAddedCount = addedCount; 446 } 447 448 /** 449 * Gets the number of removed characters. 450 * 451 * @return The number of removed characters. 452 */ 453 public int getRemovedCount() { 454 return mRemovedCount; 455 } 456 457 /** 458 * Sets the number of removed characters. 459 * 460 * @param removedCount The number of removed characters. 461 * 462 * @throws IllegalStateException If called from an AccessibilityService. 463 */ 464 public void setRemovedCount(int removedCount) { 465 enforceNotSealed(); 466 mRemovedCount = removedCount; 467 } 468 469 /** 470 * Gets the class name of the source. 471 * 472 * @return The class name. 473 */ 474 public CharSequence getClassName() { 475 return mClassName; 476 } 477 478 /** 479 * Sets the class name of the source. 480 * 481 * @param className The lass name. 482 * 483 * @throws IllegalStateException If called from an AccessibilityService. 484 */ 485 public void setClassName(CharSequence className) { 486 enforceNotSealed(); 487 mClassName = className; 488 } 489 490 /** 491 * Gets the text of the event. The index in the list represents the priority 492 * of the text. Specifically, the lower the index the higher the priority. 493 * 494 * @return The text. 495 */ 496 public List<CharSequence> getText() { 497 return mText; 498 } 499 500 /** 501 * Sets the text before a change. 502 * 503 * @return The text before the change. 504 */ 505 public CharSequence getBeforeText() { 506 return mBeforeText; 507 } 508 509 /** 510 * Sets the text before a change. 511 * 512 * @param beforeText The text before the change. 513 * 514 * @throws IllegalStateException If called from an AccessibilityService. 515 */ 516 public void setBeforeText(CharSequence beforeText) { 517 enforceNotSealed(); 518 mBeforeText = beforeText; 519 } 520 521 /** 522 * Gets the description of the source. 523 * 524 * @return The description. 525 */ 526 public CharSequence getContentDescription() { 527 return mContentDescription; 528 } 529 530 /** 531 * Sets the description of the source. 532 * 533 * @param contentDescription The description. 534 * 535 * @throws IllegalStateException If called from an AccessibilityService. 536 */ 537 public void setContentDescription(CharSequence contentDescription) { 538 enforceNotSealed(); 539 mContentDescription = contentDescription; 540 } 541 542 /** 543 * Gets the {@link Parcelable} data. 544 * 545 * @return The parcelable data. 546 */ 547 public Parcelable getParcelableData() { 548 return mParcelableData; 549 } 550 551 /** 552 * Sets the {@link Parcelable} data of the event. 553 * 554 * @param parcelableData The parcelable data. 555 * 556 * @throws IllegalStateException If called from an AccessibilityService. 557 */ 558 public void setParcelableData(Parcelable parcelableData) { 559 enforceNotSealed(); 560 mParcelableData = parcelableData; 561 } 562 563 /** 564 * Sets if this instance is sealed. 565 * 566 * @param sealed Whether is sealed. 567 * 568 * @hide 569 */ 570 public void setSealed(boolean sealed) { 571 mSealed = sealed; 572 } 573 574 /** 575 * Gets if this instance is sealed. 576 * 577 * @return Whether is sealed. 578 */ 579 boolean isSealed() { 580 return mSealed; 581 } 582 583 /** 584 * Enforces that this instance is sealed. 585 * 586 * @throws IllegalStateException If this instance is not sealed. 587 */ 588 void enforceSealed() { 589 if (!isSealed()) { 590 throw new IllegalStateException("Cannot perform this " 591 + "action on a not sealed instance."); 592 } 593 } 594 595 /** 596 * Enforces that this instance is not sealed. 597 * 598 * @throws IllegalStateException If this instance is sealed. 599 */ 600 void enforceNotSealed() { 601 if (isSealed()) { 602 throw new IllegalStateException("Cannot perform this " 603 + "action on an sealed instance."); 604 } 605 } 606 607 /** 608 * Gets the value of a boolean property. 609 * 610 * @param property The property. 611 * @return The value. 612 */ 613 private boolean getBooleanProperty(int property) { 614 return (mBooleanProperties & property) == property; 615 } 616 617 /** 618 * Sets a boolean property. 619 * 620 * @param property The property. 621 * @param value The value. 622 */ 623 private void setBooleanProperty(int property, boolean value) { 624 if (value) { 625 mBooleanProperties |= property; 626 } else { 627 mBooleanProperties &= ~property; 628 } 629 } 630 631 /** 632 * Returns a cached instance if such is available or a new one is 633 * instantiated. The instance is initialized with data from the 634 * given record. 635 * 636 * @return An instance. 637 */ 638 public static AccessibilityRecord obtain(AccessibilityRecord record) { 639 AccessibilityRecord clone = AccessibilityRecord.obtain(); 640 clone.init(record); 641 return clone; 642 } 643 644 /** 645 * Returns a cached instance if such is available or a new one is 646 * instantiated. 647 * 648 * @return An instance. 649 */ 650 public static AccessibilityRecord obtain() { 651 synchronized (sPoolLock) { 652 if (sPool != null) { 653 AccessibilityRecord record = sPool; 654 sPool = sPool.mNext; 655 sPoolSize--; 656 record.mNext = null; 657 record.mIsInPool = false; 658 return record; 659 } 660 return new AccessibilityRecord(); 661 } 662 } 663 664 /** 665 * Return an instance back to be reused. 666 * <p> 667 * <strong>Note:</strong> You must not touch the object after calling this function. 668 * 669 * @throws IllegalStateException If the record is already recycled. 670 */ 671 public void recycle() { 672 if (mIsInPool) { 673 throw new IllegalStateException("Record already recycled!"); 674 } 675 clear(); 676 synchronized (sPoolLock) { 677 if (sPoolSize <= MAX_POOL_SIZE) { 678 mNext = sPool; 679 sPool = this; 680 mIsInPool = true; 681 sPoolSize++; 682 } 683 } 684 } 685 686 /** 687 * Initialize this record from another one. 688 * 689 * @param record The to initialize from. 690 */ 691 void init(AccessibilityRecord record) { 692 mSealed = record.mSealed; 693 mBooleanProperties = record.mBooleanProperties; 694 mCurrentItemIndex = record.mCurrentItemIndex; 695 mItemCount = record.mItemCount; 696 mFromIndex = record.mFromIndex; 697 mToIndex = record.mToIndex; 698 mScrollX = record.mScrollX; 699 mScrollY = record.mScrollY; 700 mMaxScrollX = record.mMaxScrollX; 701 mMaxScrollY = record.mMaxScrollY; 702 mAddedCount = record.mAddedCount; 703 mRemovedCount = record.mRemovedCount; 704 mClassName = record.mClassName; 705 mContentDescription = record.mContentDescription; 706 mBeforeText = record.mBeforeText; 707 mParcelableData = record.mParcelableData; 708 mText.addAll(record.mText); 709 mSourceWindowId = record.mSourceWindowId; 710 mSourceViewId = record.mSourceViewId; 711 mConnection = record.mConnection; 712 } 713 714 /** 715 * Clears the state of this instance. 716 */ 717 void clear() { 718 mSealed = false; 719 mBooleanProperties = 0; 720 mCurrentItemIndex = UNDEFINED; 721 mItemCount = UNDEFINED; 722 mFromIndex = UNDEFINED; 723 mToIndex = UNDEFINED; 724 mScrollX = UNDEFINED; 725 mScrollY = UNDEFINED; 726 mMaxScrollX = UNDEFINED; 727 mMaxScrollY = UNDEFINED; 728 mAddedCount = UNDEFINED; 729 mRemovedCount = UNDEFINED; 730 mClassName = null; 731 mContentDescription = null; 732 mBeforeText = null; 733 mParcelableData = null; 734 mText.clear(); 735 mSourceViewId = View.NO_ID; 736 mSourceWindowId = View.NO_ID; 737 } 738 739 @Override 740 public String toString() { 741 StringBuilder builder = new StringBuilder(); 742 builder.append(" [ ClassName: " + mClassName); 743 builder.append("; Text: " + mText); 744 builder.append("; ContentDescription: " + mContentDescription); 745 builder.append("; ItemCount: " + mItemCount); 746 builder.append("; CurrentItemIndex: " + mCurrentItemIndex); 747 builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED)); 748 builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD)); 749 builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED)); 750 builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN)); 751 builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE)); 752 builder.append("; BeforeText: " + mBeforeText); 753 builder.append("; FromIndex: " + mFromIndex); 754 builder.append("; ToIndex: " + mToIndex); 755 builder.append("; ScrollX: " + mScrollX); 756 builder.append("; ScrollY: " + mScrollY); 757 builder.append("; MaxScrollX: " + mMaxScrollX); 758 builder.append("; MaxScrollY: " + mMaxScrollY); 759 builder.append("; AddedCount: " + mAddedCount); 760 builder.append("; RemovedCount: " + mRemovedCount); 761 builder.append("; ParcelableData: " + mParcelableData); 762 builder.append(" ]"); 763 return builder.toString(); 764 } 765} 766