1 /* 2 * Copyright (C) 2014 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 17 package android.view.accessibility; 18 19 import android.annotation.Nullable; 20 import android.annotation.TestApi; 21 import android.graphics.Rect; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.util.LongArray; 25 import android.util.Pools.SynchronizedPool; 26 27 import java.util.concurrent.atomic.AtomicInteger; 28 29 /** 30 * This class represents a state snapshot of a window for accessibility 31 * purposes. The screen content contains one or more windows where some 32 * windows can be descendants of other windows, which is the windows are 33 * hierarchically ordered. Note that there is no root window. Hence, the 34 * screen content can be seen as a collection of window trees. 35 */ 36 public final class AccessibilityWindowInfo implements Parcelable { 37 38 private static final boolean DEBUG = false; 39 40 /** 41 * Window type: This is an application window. Such a window shows UI for 42 * interacting with an application. 43 */ 44 public static final int TYPE_APPLICATION = 1; 45 46 /** 47 * Window type: This is an input method window. Such a window shows UI for 48 * inputting text such as keyboard, suggestions, etc. 49 */ 50 public static final int TYPE_INPUT_METHOD = 2; 51 52 /** 53 * Window type: This is an system window. Such a window shows UI for 54 * interacting with the system. 55 */ 56 public static final int TYPE_SYSTEM = 3; 57 58 /** 59 * Window type: Windows that are overlaid <em>only</em> by an {@link 60 * android.accessibilityservice.AccessibilityService} for interception of 61 * user interactions without changing the windows an accessibility service 62 * can introspect. In particular, an accessibility service can introspect 63 * only windows that a sighted user can interact with which they can touch 64 * these windows or can type into these windows. For example, if there 65 * is a full screen accessibility overlay that is touchable, the windows 66 * below it will be introspectable by an accessibility service regardless 67 * they are covered by a touchable window. 68 */ 69 public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; 70 71 /** 72 * Window type: A system window used to divide the screen in split-screen mode. 73 * This type of window is present only in split-screen mode. 74 */ 75 public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; 76 77 /* Special values for window IDs */ 78 /** @hide */ 79 public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; 80 /** @hide */ 81 public static final int UNDEFINED_WINDOW_ID = -1; 82 /** @hide */ 83 public static final int ANY_WINDOW_ID = -2; 84 /** @hide */ 85 public static final int PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID = -3; 86 87 private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0; 88 private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1; 89 private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2; 90 91 // Housekeeping. 92 private static final int MAX_POOL_SIZE = 10; 93 private static final SynchronizedPool<AccessibilityWindowInfo> sPool = 94 new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE); 95 private static AtomicInteger sNumInstancesInUse; 96 97 // Data. 98 private int mType = UNDEFINED_WINDOW_ID; 99 private int mLayer = UNDEFINED_WINDOW_ID; 100 private int mBooleanProperties; 101 private int mId = UNDEFINED_WINDOW_ID; 102 private int mParentId = UNDEFINED_WINDOW_ID; 103 private final Rect mBoundsInScreen = new Rect(); 104 private LongArray mChildIds; 105 private CharSequence mTitle; 106 private int mAnchorId = UNDEFINED_WINDOW_ID; 107 private boolean mInPictureInPicture; 108 109 private int mConnectionId = UNDEFINED_WINDOW_ID; 110 AccessibilityWindowInfo()111 private AccessibilityWindowInfo() { 112 /* do nothing - hide constructor */ 113 } 114 115 /** 116 * Gets the title of the window. 117 * 118 * @return The title of the window, or {@code null} if none is available. 119 */ 120 @Nullable getTitle()121 public CharSequence getTitle() { 122 return mTitle; 123 } 124 125 /** 126 * Sets the title of the window. 127 * 128 * @param title The title. 129 * 130 * @hide 131 */ setTitle(CharSequence title)132 public void setTitle(CharSequence title) { 133 mTitle = title; 134 } 135 136 /** 137 * Gets the type of the window. 138 * 139 * @return The type. 140 * 141 * @see #TYPE_APPLICATION 142 * @see #TYPE_INPUT_METHOD 143 * @see #TYPE_SYSTEM 144 * @see #TYPE_ACCESSIBILITY_OVERLAY 145 */ getType()146 public int getType() { 147 return mType; 148 } 149 150 /** 151 * Sets the type of the window. 152 * 153 * @param type The type 154 * 155 * @hide 156 */ setType(int type)157 public void setType(int type) { 158 mType = type; 159 } 160 161 /** 162 * Gets the layer which determines the Z-order of the window. Windows 163 * with greater layer appear on top of windows with lesser layer. 164 * 165 * @return The window layer. 166 */ getLayer()167 public int getLayer() { 168 return mLayer; 169 } 170 171 /** 172 * Sets the layer which determines the Z-order of the window. Windows 173 * with greater layer appear on top of windows with lesser layer. 174 * 175 * @param layer The window layer. 176 * 177 * @hide 178 */ setLayer(int layer)179 public void setLayer(int layer) { 180 mLayer = layer; 181 } 182 183 /** 184 * Gets the root node in the window's hierarchy. 185 * 186 * @return The root node. 187 */ getRoot()188 public AccessibilityNodeInfo getRoot() { 189 if (mConnectionId == UNDEFINED_WINDOW_ID) { 190 return null; 191 } 192 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 193 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, 194 mId, AccessibilityNodeInfo.ROOT_NODE_ID, 195 true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null); 196 } 197 198 /** 199 * Sets the anchor node's ID. 200 * 201 * @param anchorId The anchor's accessibility id in its window. 202 * 203 * @hide 204 */ setAnchorId(int anchorId)205 public void setAnchorId(int anchorId) { 206 mAnchorId = anchorId; 207 } 208 209 /** 210 * Gets the node that anchors this window to another. 211 * 212 * @return The anchor node, or {@code null} if none exists. 213 */ getAnchor()214 public AccessibilityNodeInfo getAnchor() { 215 if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID) 216 || (mParentId == UNDEFINED_WINDOW_ID)) { 217 return null; 218 } 219 220 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 221 return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, 222 mParentId, mAnchorId, true, 0, null); 223 } 224 225 /** @hide */ setPictureInPicture(boolean pictureInPicture)226 public void setPictureInPicture(boolean pictureInPicture) { 227 mInPictureInPicture = pictureInPicture; 228 } 229 230 /** 231 * Check if the window is in picture-in-picture mode. 232 * 233 * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. 234 * @removed 235 */ inPictureInPicture()236 public boolean inPictureInPicture() { 237 return isInPictureInPictureMode(); 238 } 239 240 /** 241 * Check if the window is in picture-in-picture mode. 242 * 243 * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. 244 */ isInPictureInPictureMode()245 public boolean isInPictureInPictureMode() { 246 return mInPictureInPicture; 247 } 248 249 /** 250 * Gets the parent window. 251 * 252 * @return The parent window, or {@code null} if none exists. 253 */ getParent()254 public AccessibilityWindowInfo getParent() { 255 if (mConnectionId == UNDEFINED_WINDOW_ID || mParentId == UNDEFINED_WINDOW_ID) { 256 return null; 257 } 258 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 259 return client.getWindow(mConnectionId, mParentId); 260 } 261 262 /** 263 * Sets the parent window id. 264 * 265 * @param parentId The parent id. 266 * 267 * @hide 268 */ setParentId(int parentId)269 public void setParentId(int parentId) { 270 mParentId = parentId; 271 } 272 273 /** 274 * Gets the unique window id. 275 * 276 * @return windowId The window id. 277 */ getId()278 public int getId() { 279 return mId; 280 } 281 282 /** 283 * Sets the unique window id. 284 * 285 * @param id The window id. 286 * 287 * @hide 288 */ setId(int id)289 public void setId(int id) { 290 mId = id; 291 } 292 293 /** 294 * Sets the unique id of the IAccessibilityServiceConnection over which 295 * this instance can send requests to the system. 296 * 297 * @param connectionId The connection id. 298 * 299 * @hide 300 */ setConnectionId(int connectionId)301 public void setConnectionId(int connectionId) { 302 mConnectionId = connectionId; 303 } 304 305 /** 306 * Gets the bounds of this window in the screen. 307 * 308 * @param outBounds The out window bounds. 309 */ getBoundsInScreen(Rect outBounds)310 public void getBoundsInScreen(Rect outBounds) { 311 outBounds.set(mBoundsInScreen); 312 } 313 314 /** 315 * Sets the bounds of this window in the screen. 316 * 317 * @param bounds The out window bounds. 318 * 319 * @hide 320 */ setBoundsInScreen(Rect bounds)321 public void setBoundsInScreen(Rect bounds) { 322 mBoundsInScreen.set(bounds); 323 } 324 325 /** 326 * Gets if this window is active. An active window is the one 327 * the user is currently touching or the window has input focus 328 * and the user is not touching any window. 329 * 330 * @return Whether this is the active window. 331 */ isActive()332 public boolean isActive() { 333 return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE); 334 } 335 336 /** 337 * Sets if this window is active, which is this is the window 338 * the user is currently touching or the window has input focus 339 * and the user is not touching any window. 340 * 341 * @param active Whether this is the active window. 342 * 343 * @hide 344 */ setActive(boolean active)345 public void setActive(boolean active) { 346 setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active); 347 } 348 349 /** 350 * Gets if this window has input focus. 351 * 352 * @return Whether has input focus. 353 */ isFocused()354 public boolean isFocused() { 355 return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED); 356 } 357 358 /** 359 * Sets if this window has input focus. 360 * 361 * @param focused Whether has input focus. 362 * 363 * @hide 364 */ setFocused(boolean focused)365 public void setFocused(boolean focused) { 366 setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused); 367 } 368 369 /** 370 * Gets if this window has accessibility focus. 371 * 372 * @return Whether has accessibility focus. 373 */ isAccessibilityFocused()374 public boolean isAccessibilityFocused() { 375 return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED); 376 } 377 378 /** 379 * Sets if this window has accessibility focus. 380 * 381 * @param focused Whether has accessibility focus. 382 * 383 * @hide 384 */ setAccessibilityFocused(boolean focused)385 public void setAccessibilityFocused(boolean focused) { 386 setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused); 387 } 388 389 /** 390 * Gets the number of child windows. 391 * 392 * @return The child count. 393 */ getChildCount()394 public int getChildCount() { 395 return (mChildIds != null) ? mChildIds.size() : 0; 396 } 397 398 /** 399 * Gets the child window at a given index. 400 * 401 * @param index The index. 402 * @return The child. 403 */ getChild(int index)404 public AccessibilityWindowInfo getChild(int index) { 405 if (mChildIds == null) { 406 throw new IndexOutOfBoundsException(); 407 } 408 if (mConnectionId == UNDEFINED_WINDOW_ID) { 409 return null; 410 } 411 final int childId = (int) mChildIds.get(index); 412 AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); 413 return client.getWindow(mConnectionId, childId); 414 } 415 416 /** 417 * Adds a child window. 418 * 419 * @param childId The child window id. 420 * 421 * @hide 422 */ addChild(int childId)423 public void addChild(int childId) { 424 if (mChildIds == null) { 425 mChildIds = new LongArray(); 426 } 427 mChildIds.add(childId); 428 } 429 430 /** 431 * Returns a cached instance if such is available or a new one is 432 * created. 433 * 434 * @return An instance. 435 */ obtain()436 public static AccessibilityWindowInfo obtain() { 437 AccessibilityWindowInfo info = sPool.acquire(); 438 if (info == null) { 439 info = new AccessibilityWindowInfo(); 440 } 441 if (sNumInstancesInUse != null) { 442 sNumInstancesInUse.incrementAndGet(); 443 } 444 return info; 445 } 446 447 /** 448 * Returns a cached instance if such is available or a new one is 449 * created. The returned instance is initialized from the given 450 * <code>info</code>. 451 * 452 * @param info The other info. 453 * @return An instance. 454 */ obtain(AccessibilityWindowInfo info)455 public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) { 456 AccessibilityWindowInfo infoClone = obtain(); 457 458 infoClone.mType = info.mType; 459 infoClone.mLayer = info.mLayer; 460 infoClone.mBooleanProperties = info.mBooleanProperties; 461 infoClone.mId = info.mId; 462 infoClone.mParentId = info.mParentId; 463 infoClone.mBoundsInScreen.set(info.mBoundsInScreen); 464 infoClone.mTitle = info.mTitle; 465 infoClone.mAnchorId = info.mAnchorId; 466 infoClone.mInPictureInPicture = info.mInPictureInPicture; 467 468 if (info.mChildIds != null && info.mChildIds.size() > 0) { 469 if (infoClone.mChildIds == null) { 470 infoClone.mChildIds = info.mChildIds.clone(); 471 } else { 472 infoClone.mChildIds.addAll(info.mChildIds); 473 } 474 } 475 476 infoClone.mConnectionId = info.mConnectionId; 477 478 return infoClone; 479 } 480 481 /** 482 * Specify a counter that will be incremented on obtain() and decremented on recycle() 483 * 484 * @hide 485 */ 486 @TestApi setNumInstancesInUseCounter(AtomicInteger counter)487 public static void setNumInstancesInUseCounter(AtomicInteger counter) { 488 if (sNumInstancesInUse != null) { 489 sNumInstancesInUse = counter; 490 } 491 } 492 493 /** 494 * Return an instance back to be reused. 495 * <p> 496 * <strong>Note:</strong> You must not touch the object after calling this function. 497 * </p> 498 * 499 * @throws IllegalStateException If the info is already recycled. 500 */ recycle()501 public void recycle() { 502 clear(); 503 sPool.release(this); 504 if (sNumInstancesInUse != null) { 505 sNumInstancesInUse.decrementAndGet(); 506 } 507 } 508 509 @Override describeContents()510 public int describeContents() { 511 return 0; 512 } 513 514 @Override writeToParcel(Parcel parcel, int flags)515 public void writeToParcel(Parcel parcel, int flags) { 516 parcel.writeInt(mType); 517 parcel.writeInt(mLayer); 518 parcel.writeInt(mBooleanProperties); 519 parcel.writeInt(mId); 520 parcel.writeInt(mParentId); 521 mBoundsInScreen.writeToParcel(parcel, flags); 522 parcel.writeCharSequence(mTitle); 523 parcel.writeInt(mAnchorId); 524 parcel.writeInt(mInPictureInPicture ? 1 : 0); 525 526 final LongArray childIds = mChildIds; 527 if (childIds == null) { 528 parcel.writeInt(0); 529 } else { 530 final int childCount = childIds.size(); 531 parcel.writeInt(childCount); 532 for (int i = 0; i < childCount; i++) { 533 parcel.writeInt((int) childIds.get(i)); 534 } 535 } 536 537 parcel.writeInt(mConnectionId); 538 } 539 initFromParcel(Parcel parcel)540 private void initFromParcel(Parcel parcel) { 541 mType = parcel.readInt(); 542 mLayer = parcel.readInt(); 543 mBooleanProperties = parcel.readInt(); 544 mId = parcel.readInt(); 545 mParentId = parcel.readInt(); 546 mBoundsInScreen.readFromParcel(parcel); 547 mTitle = parcel.readCharSequence(); 548 mAnchorId = parcel.readInt(); 549 mInPictureInPicture = parcel.readInt() == 1; 550 551 final int childCount = parcel.readInt(); 552 if (childCount > 0) { 553 if (mChildIds == null) { 554 mChildIds = new LongArray(childCount); 555 } 556 for (int i = 0; i < childCount; i++) { 557 final int childId = parcel.readInt(); 558 mChildIds.add(childId); 559 } 560 } 561 562 mConnectionId = parcel.readInt(); 563 } 564 565 @Override hashCode()566 public int hashCode() { 567 return mId; 568 } 569 570 @Override equals(Object obj)571 public boolean equals(Object obj) { 572 if (this == obj) { 573 return true; 574 } 575 if (obj == null) { 576 return false; 577 } 578 if (getClass() != obj.getClass()) { 579 return false; 580 } 581 AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj; 582 return (mId == other.mId); 583 } 584 585 @Override toString()586 public String toString() { 587 StringBuilder builder = new StringBuilder(); 588 builder.append("AccessibilityWindowInfo["); 589 builder.append("title=").append(mTitle); 590 builder.append("id=").append(mId); 591 builder.append(", type=").append(typeToString(mType)); 592 builder.append(", layer=").append(mLayer); 593 builder.append(", bounds=").append(mBoundsInScreen); 594 builder.append(", focused=").append(isFocused()); 595 builder.append(", active=").append(isActive()); 596 builder.append(", pictureInPicture=").append(inPictureInPicture()); 597 if (DEBUG) { 598 builder.append(", parent=").append(mParentId); 599 builder.append(", children=["); 600 if (mChildIds != null) { 601 final int childCount = mChildIds.size(); 602 for (int i = 0; i < childCount; i++) { 603 builder.append(mChildIds.get(i)); 604 if (i < childCount - 1) { 605 builder.append(','); 606 } 607 } 608 } else { 609 builder.append("null"); 610 } 611 builder.append(']'); 612 } else { 613 builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID); 614 builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID); 615 builder.append(", hasChildren=").append(mChildIds != null 616 && mChildIds.size() > 0); 617 } 618 builder.append(']'); 619 return builder.toString(); 620 } 621 622 /** 623 * Clears the internal state. 624 */ clear()625 private void clear() { 626 mType = UNDEFINED_WINDOW_ID; 627 mLayer = UNDEFINED_WINDOW_ID; 628 mBooleanProperties = 0; 629 mId = UNDEFINED_WINDOW_ID; 630 mParentId = UNDEFINED_WINDOW_ID; 631 mBoundsInScreen.setEmpty(); 632 if (mChildIds != null) { 633 mChildIds.clear(); 634 } 635 mConnectionId = UNDEFINED_WINDOW_ID; 636 mAnchorId = UNDEFINED_WINDOW_ID; 637 mInPictureInPicture = false; 638 mTitle = null; 639 } 640 641 /** 642 * Gets the value of a boolean property. 643 * 644 * @param property The property. 645 * @return The value. 646 */ getBooleanProperty(int property)647 private boolean getBooleanProperty(int property) { 648 return (mBooleanProperties & property) != 0; 649 } 650 651 /** 652 * Sets a boolean property. 653 * 654 * @param property The property. 655 * @param value The value. 656 * 657 * @throws IllegalStateException If called from an AccessibilityService. 658 */ setBooleanProperty(int property, boolean value)659 private void setBooleanProperty(int property, boolean value) { 660 if (value) { 661 mBooleanProperties |= property; 662 } else { 663 mBooleanProperties &= ~property; 664 } 665 } 666 typeToString(int type)667 private static String typeToString(int type) { 668 switch (type) { 669 case TYPE_APPLICATION: { 670 return "TYPE_APPLICATION"; 671 } 672 case TYPE_INPUT_METHOD: { 673 return "TYPE_INPUT_METHOD"; 674 } 675 case TYPE_SYSTEM: { 676 return "TYPE_SYSTEM"; 677 } 678 case TYPE_ACCESSIBILITY_OVERLAY: { 679 return "TYPE_ACCESSIBILITY_OVERLAY"; 680 } 681 case TYPE_SPLIT_SCREEN_DIVIDER: { 682 return "TYPE_SPLIT_SCREEN_DIVIDER"; 683 } 684 default: 685 return "<UNKNOWN>"; 686 } 687 } 688 689 /** 690 * Checks whether this window changed. The argument should be 691 * another state of the same window, which is have the same id 692 * and type as they never change. 693 * 694 * @param other The new state. 695 * @return Whether something changed. 696 * 697 * @hide 698 */ changed(AccessibilityWindowInfo other)699 public boolean changed(AccessibilityWindowInfo other) { 700 if (other.mId != mId) { 701 throw new IllegalArgumentException("Not same window."); 702 } 703 if (other.mType != mType) { 704 throw new IllegalArgumentException("Not same type."); 705 } 706 if (!mBoundsInScreen.equals(other.mBoundsInScreen)) { 707 return true; 708 } 709 if (mLayer != other.mLayer) { 710 return true; 711 } 712 if (mBooleanProperties != other.mBooleanProperties) { 713 return true; 714 } 715 if (mParentId != other.mParentId) { 716 return true; 717 } 718 if (mChildIds == null) { 719 if (other.mChildIds != null) { 720 return true; 721 } 722 } else if (!mChildIds.equals(other.mChildIds)) { 723 return true; 724 } 725 return false; 726 } 727 728 public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR = 729 new Creator<AccessibilityWindowInfo>() { 730 @Override 731 public AccessibilityWindowInfo createFromParcel(Parcel parcel) { 732 AccessibilityWindowInfo info = obtain(); 733 info.initFromParcel(parcel); 734 return info; 735 } 736 737 @Override 738 public AccessibilityWindowInfo[] newArray(int size) { 739 return new AccessibilityWindowInfo[size]; 740 } 741 }; 742 } 743