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