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