1 /* 2 * Copyright (C) 2015 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 androidx.core.view.accessibility; 18 19 import static android.os.Build.VERSION.SDK_INT; 20 import static android.view.Display.DEFAULT_DISPLAY; 21 22 import android.graphics.Rect; 23 import android.graphics.Region; 24 import android.os.Build; 25 import android.os.LocaleList; 26 import android.os.SystemClock; 27 import android.view.accessibility.AccessibilityNodeInfo; 28 import android.view.accessibility.AccessibilityWindowInfo; 29 30 import androidx.annotation.RequiresApi; 31 import androidx.core.os.LocaleListCompat; 32 33 import org.jspecify.annotations.NonNull; 34 import org.jspecify.annotations.Nullable; 35 36 /** 37 * Helper for accessing {@link android.view.accessibility.AccessibilityWindowInfo}. 38 */ 39 public class AccessibilityWindowInfoCompat { 40 private final Object mInfo; 41 42 private static final int UNDEFINED = -1; 43 44 /** 45 * Window type: This is an application window. Such a window shows UI for 46 * interacting with an application. 47 */ 48 public static final int TYPE_APPLICATION = 1; 49 50 /** 51 * Window type: This is an input method window. Such a window shows UI for 52 * inputting text such as keyboard, suggestions, etc. 53 */ 54 public static final int TYPE_INPUT_METHOD = 2; 55 56 /** 57 * Window type: This is an system window. Such a window shows UI for 58 * interacting with the system. 59 */ 60 public static final int TYPE_SYSTEM = 3; 61 62 /** 63 * Window type: Windows that are overlaid <em>only</em> by an {@link 64 * android.accessibilityservice.AccessibilityService} for interception of 65 * user interactions without changing the windows an accessibility service 66 * can introspect. In particular, an accessibility service can introspect 67 * only windows that a sighted user can interact with which they can touch 68 * these windows or can type into these windows. For example, if there 69 * is a full screen accessibility overlay that is touchable, the windows 70 * below it will be introspectable by an accessibility service regardless 71 * they are covered by a touchable window. 72 */ 73 public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; 74 75 /** 76 * Window type: A system window used to divide the screen in split-screen mode. 77 * This type of window is present only in split-screen mode. 78 */ 79 public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; 80 81 /** 82 * Window type: A system window used to show the UI for the interaction with 83 * window-based magnification, which includes the magnified content and the option menu. 84 */ 85 public static final int TYPE_MAGNIFICATION_OVERLAY = 6; 86 87 /** 88 * Creates a wrapper for info implementation. 89 * 90 * @param object The info to wrap. 91 * @return A wrapper for if the object is not null, null otherwise. 92 */ wrapNonNullInstance(Object object)93 static AccessibilityWindowInfoCompat wrapNonNullInstance(Object object) { 94 if (object != null) { 95 return new AccessibilityWindowInfoCompat(object); 96 } 97 return null; 98 } 99 100 /** 101 * Creates a new AccessibilityWindowInfoCompat. 102 * <p> 103 * Compatibility: 104 * <ul> 105 * <li>Api < 30: Will not wrap an 106 * {@link android.view.accessibility.AccessibilityWindowInfo} instance.</li> 107 * </ul> 108 * </p> 109 * 110 */ AccessibilityWindowInfoCompat()111 public AccessibilityWindowInfoCompat() { 112 if (SDK_INT >= 30) { 113 mInfo = Api30Impl.instantiateAccessibilityWindowInfo(); 114 } else { 115 mInfo = null; 116 } 117 } 118 AccessibilityWindowInfoCompat(Object info)119 private AccessibilityWindowInfoCompat(Object info) { 120 mInfo = info; 121 } 122 123 /** 124 * Gets the type of the window. 125 * 126 * @return The type. 127 * @see #TYPE_APPLICATION 128 * @see #TYPE_INPUT_METHOD 129 * @see #TYPE_SYSTEM 130 * @see #TYPE_ACCESSIBILITY_OVERLAY 131 */ getType()132 public int getType() { 133 if (SDK_INT >= 21) { 134 return Api21Impl.getType((AccessibilityWindowInfo) mInfo); 135 } else { 136 return UNDEFINED; 137 } 138 } 139 140 /** 141 * Gets the layer which determines the Z-order of the window. Windows 142 * with greater layer appear on top of windows with lesser layer. 143 * 144 * @return The window layer. 145 */ getLayer()146 public int getLayer() { 147 if (SDK_INT >= 21) { 148 return Api21Impl.getLayer((AccessibilityWindowInfo) mInfo); 149 } else { 150 return UNDEFINED; 151 } 152 } 153 154 /** 155 * Gets the root node in the window's hierarchy. 156 * 157 * @return The root node. 158 */ getRoot()159 public @Nullable AccessibilityNodeInfoCompat getRoot() { 160 if (SDK_INT >= 21) { 161 return AccessibilityNodeInfoCompat.wrapNonNullInstance( 162 Api21Impl.getRoot((AccessibilityWindowInfo) mInfo)); 163 } else { 164 return null; 165 } 166 } 167 168 /** 169 * Gets the root node in the window's hierarchy. 170 * 171 * @param prefetchingStrategy the prefetching strategy. 172 * @return The root node. 173 * 174 * @see AccessibilityNodeInfoCompat#getParent(int) for a description of prefetching. 175 */ getRoot(int prefetchingStrategy)176 public @Nullable AccessibilityNodeInfoCompat getRoot(int prefetchingStrategy) { 177 if (Build.VERSION.SDK_INT >= 33) { 178 return Api33Impl.getRoot(mInfo, prefetchingStrategy); 179 } 180 return getRoot(); 181 } 182 183 /** 184 * Check if the window is in picture-in-picture mode. 185 * <p> 186 * Compatibility: 187 * <ul> 188 * <li>API < 26: Returns false.</li> 189 * </ul> 190 * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise. 191 */ isInPictureInPictureMode()192 public boolean isInPictureInPictureMode() { 193 if (SDK_INT >= 26) { 194 return Api26Impl.isInPictureInPictureMode((AccessibilityWindowInfo) mInfo); 195 } else { 196 return false; 197 } 198 } 199 200 /** 201 * Gets the parent window if such. 202 * 203 * @return The parent window. 204 */ getParent()205 public @Nullable AccessibilityWindowInfoCompat getParent() { 206 if (SDK_INT >= 21) { 207 return wrapNonNullInstance(Api21Impl.getParent((AccessibilityWindowInfo) mInfo)); 208 } else { 209 return null; 210 } 211 } 212 213 /** 214 * Gets the unique window id. 215 * 216 * @return windowId The window id. 217 */ getId()218 public int getId() { 219 if (SDK_INT >= 21) { 220 return Api21Impl.getId((AccessibilityWindowInfo) mInfo); 221 } else { 222 return UNDEFINED; 223 } 224 } 225 226 /** 227 * Gets the touchable region of this window in the screen. 228 * <p> 229 * Compatibility: 230 * <ul> 231 * <li>API < 33: Gets the bounds of this window in the screen. </li> 232 * <li>API < 21: Does not operate. </li> 233 * </ul> 234 * 235 * @param outRegion The out window region. 236 */ getRegionInScreen(@onNull Region outRegion)237 public void getRegionInScreen(@NonNull Region outRegion) { 238 if (SDK_INT >= 33) { 239 Api33Impl.getRegionInScreen((AccessibilityWindowInfo) mInfo, outRegion); 240 } else if (SDK_INT >= 21) { 241 Rect outBounds = new Rect(); 242 Api21Impl.getBoundsInScreen((AccessibilityWindowInfo) mInfo, outBounds); 243 outRegion.set(outBounds); 244 } 245 } 246 247 /** 248 * Gets the bounds of this window in the screen. 249 * <p> 250 * Compatibility: 251 * <ul> 252 * <li>API < 21: Does not operate. </li> 253 * </ul> 254 * 255 * @param outBounds The out window bounds. 256 */ getBoundsInScreen(@onNull Rect outBounds)257 public void getBoundsInScreen(@NonNull Rect outBounds) { 258 if (SDK_INT >= 21) { 259 Api21Impl.getBoundsInScreen((AccessibilityWindowInfo) mInfo, outBounds); 260 } 261 } 262 263 /** 264 * Gets if this window is active. An active window is the one 265 * the user is currently touching or the window has input focus 266 * and the user is not touching any window. 267 * 268 * @return Whether this is the active window. 269 */ isActive()270 public boolean isActive() { 271 if (SDK_INT >= 21) { 272 return Api21Impl.isActive((AccessibilityWindowInfo) mInfo); 273 } else { 274 return true; 275 } 276 } 277 278 /** 279 * Gets if this window has input focus. 280 * 281 * @return Whether has input focus. 282 */ isFocused()283 public boolean isFocused() { 284 if (SDK_INT >= 21) { 285 return Api21Impl.isFocused((AccessibilityWindowInfo) mInfo); 286 } else { 287 return true; 288 } 289 } 290 291 /** 292 * Gets if this window has accessibility focus. 293 * 294 * @return Whether has accessibility focus. 295 */ isAccessibilityFocused()296 public boolean isAccessibilityFocused() { 297 if (SDK_INT >= 21) { 298 return Api21Impl.isAccessibilityFocused((AccessibilityWindowInfo) mInfo); 299 } else { 300 return true; 301 } 302 } 303 304 /** 305 * Gets the number of child windows. 306 * 307 * @return The child count. 308 */ getChildCount()309 public int getChildCount() { 310 if (SDK_INT >= 21) { 311 return Api21Impl.getChildCount((AccessibilityWindowInfo) mInfo); 312 } else { 313 return 0; 314 } 315 } 316 317 /** 318 * Gets the child window at a given index. 319 * 320 * @param index The index. 321 * @return The child. 322 */ getChild(int index)323 public @Nullable AccessibilityWindowInfoCompat getChild(int index) { 324 if (SDK_INT >= 21) { 325 return wrapNonNullInstance(Api21Impl.getChild((AccessibilityWindowInfo) mInfo, index)); 326 } else { 327 return null; 328 } 329 } 330 331 /** 332 * Returns the ID of the display this window is on, for use with 333 * {@link android.hardware.display.DisplayManager#getDisplay(int)}. 334 * <p> 335 * Compatibility: 336 * <ul> 337 * <li>Api < 33: Will return {@link android.view.Display.DEFAULT_DISPLAY}.</li> 338 * </ul> 339 * 340 * @return the logical display id. 341 */ getDisplayId()342 public int getDisplayId() { 343 if (SDK_INT >= 33) { 344 return Api33Impl.getDisplayId((AccessibilityWindowInfo) mInfo); 345 } else { 346 return DEFAULT_DISPLAY; 347 } 348 } 349 350 /** 351 * Returns the {@link SystemClock#uptimeMillis()} at which the last transition happens. 352 * A transition happens when {@link #getBoundsInScreen(Rect)} is changed. 353 * <p> 354 * Compatibility: 355 * <ul> 356 * <li>Api < 34: Will return 0.</li> 357 * </ul> 358 * @return The transition timestamp. 359 */ getTransitionTimeMillis()360 public long getTransitionTimeMillis() { 361 if (SDK_INT >= 34) { 362 return Api34Impl.getTransitionTimeMillis((AccessibilityWindowInfo) mInfo); 363 } 364 return 0; 365 } 366 367 /** 368 * Returns the {@link android.os.LocaleList} of the window. 369 * <p> 370 * Compatibility: 371 * <ul> 372 * <li>Api < 34: Will return {@link LocaleListCompat#getEmptyLocaleList()}.</li> 373 * </ul> 374 * @return the locales of the window. 375 */ getLocales()376 public @NonNull LocaleListCompat getLocales() { 377 if (SDK_INT >= 34) { 378 return LocaleListCompat.wrap(Api34Impl.getLocales((AccessibilityWindowInfo) mInfo)); 379 } else { 380 return LocaleListCompat.getEmptyLocaleList(); 381 } 382 } 383 384 /** 385 * Gets the title of the window. 386 * 387 * @return The title of the window, or the application label for the window if no title was 388 * explicitly set, or {@code null} if neither is available. 389 */ getTitle()390 public @Nullable CharSequence getTitle() { 391 if (SDK_INT >= 24) { 392 return Api24Impl.getTitle((AccessibilityWindowInfo) mInfo); 393 } else { 394 return null; 395 } 396 } 397 398 /** 399 * Gets the node that anchors this window to another. 400 * 401 * @return The anchor node, or {@code null} if none exists. 402 */ getAnchor()403 public @Nullable AccessibilityNodeInfoCompat getAnchor() { 404 if (SDK_INT >= 24) { 405 return AccessibilityNodeInfoCompat.wrapNonNullInstance( 406 Api24Impl.getAnchor((AccessibilityWindowInfo) mInfo)); 407 } else { 408 return null; 409 } 410 } 411 412 /** 413 * Returns a cached instance if such is available or a new one is 414 * created. 415 * 416 * @return An instance. 417 */ obtain()418 public static @Nullable AccessibilityWindowInfoCompat obtain() { 419 if (SDK_INT >= 21) { 420 return wrapNonNullInstance(Api21Impl.obtain()); 421 } else { 422 return null; 423 } 424 } 425 426 /** 427 * Returns a cached instance if such is available or a new one is 428 * created. The returned instance is initialized from the given 429 * <code>info</code>. 430 * 431 * @param info The other info. 432 * @return An instance. 433 */ obtain( @ullable AccessibilityWindowInfoCompat info)434 public static @Nullable AccessibilityWindowInfoCompat obtain( 435 @Nullable AccessibilityWindowInfoCompat info) { 436 if (SDK_INT >= 21) { 437 return info == null 438 ? null 439 : wrapNonNullInstance( 440 Api21Impl.obtain((AccessibilityWindowInfo) info.mInfo)); 441 } else { 442 return null; 443 } 444 } 445 446 /** 447 * Return an instance back to be reused. 448 * <p> 449 * <strong>Note:</strong> You must not touch the object after calling this function. 450 * </p> 451 * 452 * @deprecated Accessibility Object recycling is no longer necessary or functional. 453 */ 454 @Deprecated recycle()455 public void recycle() { } 456 457 /** 458 * @return The unwrapped {@link android.view.accessibility.AccessibilityWindowInfo}. 459 */ unwrap()460 public @Nullable AccessibilityWindowInfo unwrap() { 461 if (SDK_INT >= 21) { 462 return (AccessibilityWindowInfo) mInfo; 463 } else { 464 return null; 465 } 466 } 467 468 @Override hashCode()469 public int hashCode() { 470 return (mInfo == null) ? 0 : mInfo.hashCode(); 471 } 472 473 @Override equals(Object obj)474 public boolean equals(Object obj) { 475 if (this == obj) { 476 return true; 477 } 478 if (obj == null) { 479 return false; 480 } 481 if (!(obj instanceof AccessibilityWindowInfoCompat)) { 482 return false; 483 } 484 AccessibilityWindowInfoCompat other = (AccessibilityWindowInfoCompat) obj; 485 if (mInfo == null) { 486 return other.mInfo == null; 487 } 488 return mInfo.equals(other.mInfo); 489 } 490 491 @Override toString()492 public @NonNull String toString() { 493 StringBuilder builder = new StringBuilder(); 494 Rect bounds = new Rect(); 495 getBoundsInScreen(bounds); 496 builder.append("AccessibilityWindowInfo["); 497 builder.append("id=").append(getId()); 498 builder.append(", type=").append(typeToString(getType())); 499 builder.append(", layer=").append(getLayer()); 500 builder.append(", bounds=").append(bounds); 501 builder.append(", focused=").append(isFocused()); 502 builder.append(", active=").append(isActive()); 503 builder.append(", hasParent=").append(getParent() != null); 504 builder.append(", hasChildren=").append(getChildCount() > 0); 505 builder.append(", transitionTime=").append(getTransitionTimeMillis()); 506 builder.append(", locales=").append(getLocales()); 507 builder.append(']'); 508 return builder.toString(); 509 } 510 typeToString(int type)511 private static String typeToString(int type) { 512 switch (type) { 513 case TYPE_APPLICATION: { 514 return "TYPE_APPLICATION"; 515 } 516 case TYPE_INPUT_METHOD: { 517 return "TYPE_INPUT_METHOD"; 518 } 519 case TYPE_SYSTEM: { 520 return "TYPE_SYSTEM"; 521 } 522 case TYPE_ACCESSIBILITY_OVERLAY: { 523 return "TYPE_ACCESSIBILITY_OVERLAY"; 524 } 525 default: 526 return "<UNKNOWN>"; 527 } 528 } 529 530 @RequiresApi(21) 531 private static class Api21Impl { Api21Impl()532 private Api21Impl() { 533 // This class is not instantiable. 534 } 535 getBoundsInScreen(AccessibilityWindowInfo info, Rect outBounds)536 static void getBoundsInScreen(AccessibilityWindowInfo info, Rect outBounds) { 537 info.getBoundsInScreen(outBounds); 538 } 539 getChild(AccessibilityWindowInfo info, int index)540 static AccessibilityWindowInfo getChild(AccessibilityWindowInfo info, int index) { 541 return info.getChild(index); 542 } 543 getChildCount(AccessibilityWindowInfo info)544 static int getChildCount(AccessibilityWindowInfo info) { 545 return info.getChildCount(); 546 } 547 getId(AccessibilityWindowInfo info)548 static int getId(AccessibilityWindowInfo info) { 549 return info.getId(); 550 } 551 getLayer(AccessibilityWindowInfo info)552 static int getLayer(AccessibilityWindowInfo info) { 553 return info.getLayer(); 554 } 555 getParent(AccessibilityWindowInfo info)556 static AccessibilityWindowInfo getParent(AccessibilityWindowInfo info) { 557 return info.getParent(); 558 } 559 getRoot(AccessibilityWindowInfo info)560 static AccessibilityNodeInfo getRoot(AccessibilityWindowInfo info) { 561 return info.getRoot(); 562 } 563 getType(AccessibilityWindowInfo info)564 static int getType(AccessibilityWindowInfo info) { 565 return info.getType(); 566 } 567 isAccessibilityFocused(AccessibilityWindowInfo info)568 static boolean isAccessibilityFocused(AccessibilityWindowInfo info) { 569 return info.isAccessibilityFocused(); 570 } 571 isActive(AccessibilityWindowInfo info)572 static boolean isActive(AccessibilityWindowInfo info) { 573 return info.isActive(); 574 } 575 isFocused(AccessibilityWindowInfo info)576 static boolean isFocused(AccessibilityWindowInfo info) { 577 return info.isFocused(); 578 } 579 obtain()580 static AccessibilityWindowInfo obtain() { 581 return AccessibilityWindowInfo.obtain(); 582 } 583 obtain(AccessibilityWindowInfo info)584 static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) { 585 return AccessibilityWindowInfo.obtain(info); 586 } 587 } 588 589 @RequiresApi(24) 590 private static class Api24Impl { Api24Impl()591 private Api24Impl() { 592 // This class is not instantiable. 593 } 594 getAnchor(AccessibilityWindowInfo info)595 static AccessibilityNodeInfo getAnchor(AccessibilityWindowInfo info) { 596 return info.getAnchor(); 597 } 598 getTitle(AccessibilityWindowInfo info)599 static CharSequence getTitle(AccessibilityWindowInfo info) { 600 return info.getTitle(); 601 } 602 } 603 604 @RequiresApi(26) 605 private static class Api26Impl { Api26Impl()606 private Api26Impl() { 607 // This class is non instantiable. 608 } 609 isInPictureInPictureMode(AccessibilityWindowInfo info)610 static boolean isInPictureInPictureMode(AccessibilityWindowInfo info) { 611 return info.isInPictureInPictureMode(); 612 } 613 } 614 615 @RequiresApi(30) 616 private static class Api30Impl { Api30Impl()617 private Api30Impl() { 618 // This class is non instantiable. 619 } 620 instantiateAccessibilityWindowInfo()621 static AccessibilityWindowInfo instantiateAccessibilityWindowInfo() { 622 return new AccessibilityWindowInfo(); 623 } 624 } 625 626 @RequiresApi(33) 627 private static class Api33Impl { Api33Impl()628 private Api33Impl() { 629 // This class is non instantiable. 630 } 631 getDisplayId(AccessibilityWindowInfo info)632 static int getDisplayId(AccessibilityWindowInfo info) { 633 return info.getDisplayId(); 634 } 635 getRegionInScreen(AccessibilityWindowInfo info, Region outRegion)636 static void getRegionInScreen(AccessibilityWindowInfo info, Region outRegion) { 637 info.getRegionInScreen(outRegion); 638 } 639 getRoot(Object info, int prefetchingStrategy)640 public static AccessibilityNodeInfoCompat getRoot(Object info, int prefetchingStrategy) { 641 return AccessibilityNodeInfoCompat.wrapNonNullInstance( 642 ((AccessibilityWindowInfo) info).getRoot(prefetchingStrategy)); 643 } 644 } 645 646 @RequiresApi(34) 647 private static class Api34Impl { Api34Impl()648 private Api34Impl() { 649 // This class is non instantiable. 650 } 651 getTransitionTimeMillis(AccessibilityWindowInfo info)652 public static long getTransitionTimeMillis(AccessibilityWindowInfo info) { 653 return info.getTransitionTimeMillis(); 654 } 655 getLocales(AccessibilityWindowInfo info)656 static LocaleList getLocales(AccessibilityWindowInfo info) { 657 return info.getLocales(); 658 } 659 } 660 }