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