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