summaryrefslogtreecommitdiff
path: root/android/view/accessibility
diff options
context:
space:
mode:
authorJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
committerJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
commita192cc2a132cb0ee8588e2df755563ec7008c179 (patch)
tree380e4db22df19c819bd37df34bf06e7568916a50 /android/view/accessibility
parent98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff)
downloadandroid-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \ --bid 4575844 \ --target sdk_phone_x86_64-sdk \ sdk-repo-linux-sources-4575844.zip Test: TreeHugger Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/view/accessibility')
-rw-r--r--android/view/accessibility/AccessibilityEvent.java459
-rw-r--r--android/view/accessibility/AccessibilityNodeInfo.java124
-rw-r--r--android/view/accessibility/AccessibilityRecord.java89
-rw-r--r--android/view/accessibility/AccessibilityViewHierarchyState.java61
-rw-r--r--android/view/accessibility/AccessibilityWindowInfo.java59
-rw-r--r--android/view/accessibility/SendViewScrolledAccessibilityEvent.java58
-rw-r--r--android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java111
-rw-r--r--android/view/accessibility/ThrottlingAccessibilityEventSender.java248
8 files changed, 969 insertions, 240 deletions
diff --git a/android/view/accessibility/AccessibilityEvent.java b/android/view/accessibility/AccessibilityEvent.java
index 1d19a9f5..e0f74a7d 100644
--- a/android/view/accessibility/AccessibilityEvent.java
+++ b/android/view/accessibility/AccessibilityEvent.java
@@ -38,19 +38,14 @@ import java.util.List;
* <p>
* An accessibility event is fired by an individual view which populates the event with
* data for its state and requests from its parent to send the event to interested
- * parties. The parent can optionally add an {@link AccessibilityRecord} for itself before
- * dispatching a similar request to its parent. A parent can also choose not to respect the
- * request for sending an event. The accessibility event is sent by the topmost view in the
- * view tree. Therefore, an {@link android.accessibilityservice.AccessibilityService} can
- * explore all records in an accessibility event to obtain more information about the
- * context in which the event was fired.
+ * parties. The parent can optionally modify or even block the event based on its broader
+ * understanding of the user interface's context.
* </p>
* <p>
- * The main purpose of an accessibility event is to expose enough information for an
- * {@link android.accessibilityservice.AccessibilityService} to provide meaningful feedback
- * to the user. Sometimes however, an accessibility service may need more contextual
- * information then the one in the event pay-load. In such cases the service can obtain
- * the event source which is an {@link AccessibilityNodeInfo} (snapshot of a View state)
+ * The main purpose of an accessibility event is to communicate changes in the UI to an
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
* which can be used for exploring the window content. Note that the privilege for accessing
* an event's source, thus the window content, has to be explicitly requested. For more
* details refer to {@link android.accessibilityservice.AccessibilityService}. If an
@@ -85,21 +80,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isChecked()} - Whether the source is checked.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -113,21 +93,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isChecked()} - Whether the source is checked.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -141,23 +106,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isChecked()} - Whether the source is checked.</li>
- * <li>{@link #getItemCount()} - The number of selectable items of the source.</li>
- * <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -171,23 +119,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isChecked()} - Whether the source is checked.</li>
- * <li>{@link #getItemCount()} - The number of focusable items on the screen.</li>
- * <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -201,15 +132,11 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getText()} - The new text of the source.</li>
+ * <li>{@link #getBeforeText()} - The text of the source before the change.</li>
* <li>{@link #getFromIndex()} - The text change start index.</li>
* <li>{@link #getAddedCount()} - The number of added characters.</li>
* <li>{@link #getRemovedCount()} - The number of removed characters.</li>
- * <li>{@link #getBeforeText()} - The text of the source before the change.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
* </ul>
* </p>
* <p>
@@ -223,13 +150,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #getFromIndex()} - The selection start index.</li>
- * <li>{@link #getToIndex()} - The selection end index.</li>
- * <li>{@link #getItemCount()} - The length of the source text.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
* </ul>
* </p>
* <b>View text traversed at movement granularity</b> - represents the event of traversing the
@@ -251,23 +171,11 @@ import java.util.List;
* <li>{@link #getToIndex()} - The end of the text that was skipped over in this movement.
* This is the ending point when moving forward through the text, but not when moving
* back.</li>
- * <li>{@link #isPassword()} - Whether the source is password.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getMovementGranularity()} - Sets the granularity at which a view's text
- * was traversed.</li>
* <li>{@link #getAction()} - Gets traversal action which specifies the direction.</li>
* </ul>
* </p>
* <p>
- * <b>View scrolled</b> - represents the event of scrolling a view. If
- * the source is a descendant of {@link android.widget.AdapterView} the
- * scroll is reported in terms of visible items - the first visible item,
- * the last visible item, and the total items - because the the source
- * is unaware of its pixel size since its adapter is responsible for
- * creating views. In all other cases the scroll is reported as the current
- * scroll on the X and Y axis respectively plus the height of the source in
- * pixels.</br>
+ * <b>View scrolled</b> - represents the event of scrolling a view. </br>
* <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br>
* <em>Properties:</em></br>
* <ul>
@@ -276,37 +184,19 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
+ * <li>{@link #getScrollDeltaX()} - The difference in the horizontal position.</li>
+ * <li>{@link #getScrollDeltaY()} - The difference in the vertical position.</li>
* </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
* </p>
* <p>
* <b>TRANSITION TYPES</b></br>
* </p>
* <p>
- * <b>Window state changed</b> - represents the event of opening a
- * {@link android.widget.PopupWindow}, {@link android.view.Menu},
- * {@link android.app.Dialog}, etc.</br>
+ * <b>Window state changed</b> - represents the event of a change to a section of
+ * the user interface that is visually distinct. Should be sent from either the
+ * root view of a window or from a view that is marked as a pane
+ * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes
+ * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br>
* <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
@@ -315,8 +205,7 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li>
* </ul>
* </p>
* <p>
@@ -325,10 +214,6 @@ import java.util.List;
* a view size, etc.</br>
* </p>
* <p>
- * <strong>Note:</strong> This event is fired only for the window source of the
- * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}
- * and its purpose is to notify clients that the content of the user interaction
- * window has changed.</br>
* <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
@@ -339,32 +224,26 @@ import java.util.List;
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
* </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
* </p>
* <p>
- * <b>Windows changed</b> - represents the event of changes in the windows shown on
+ * <b>Windows changed</b> - represents a change in the windows shown on
* the screen such as a window appeared, a window disappeared, a window size changed,
- * a window layer changed, etc.</br>
+ * a window layer changed, etc. These events should only come from the system, which is responsible
+ * for managing windows. The list of windows is available from
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()}.
+ * For regions of the user interface that are presented as windows but are
+ * controlled by an app's process, use {@link #TYPE_WINDOW_STATE_CHANGED}.</br>
* <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
* <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getWindowChanges()}</li> - The specific change to the source window
* </ul>
* <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
- * source of the event via {@link AccessibilityEvent#getSource()} to get the source
- * node on which then call {@link AccessibilityNodeInfo#getWindow()
- * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can
- * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows()
- * android.accessibilityservice.AccessibilityService.getWindows()}.
+ * source of the event by looking through the list returned by
+ * {@link android.accessibilityservice.AccessibilityService#getWindows()} for the window whose ID
+ * matches {@link #getWindowId()}.
* </p>
* <p>
* <b>NOTIFICATION TYPES</b></br>
@@ -402,19 +281,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <b>View hover exit</b> - represents the event of stopping to hover
@@ -428,19 +294,6 @@ import java.util.List;
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source's sub-tree.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getContentDescription()} - The content description of the source.</li>
- * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
- * (without descendants of AdapterView).</li>
- * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
- * inclusive (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source
- * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -513,10 +366,10 @@ import java.util.List;
* <b>MISCELLANEOUS TYPES</b></br>
* </p>
* <p>
- * <b>Announcement</b> - represents the event of an application making an
- * announcement. Usually this announcement is related to some sort of a context
- * change for which none of the events representing UI transitions is a good fit.
- * For example, announcing a new page in a book.</br>
+ * <b>Announcement</b> - represents the event of an application requesting a screen reader to make
+ * an announcement. Because the event carries no semantic meaning, this event is appropriate only
+ * in exceptional situations where additional screen reader output is needed but other types of
+ * accessibility services do not need to be aware of the change.</br>
* <em>Type:</em> {@link #TYPE_ANNOUNCEMENT}</br>
* <em>Properties:</em></br>
* <ul>
@@ -526,7 +379,6 @@ import java.util.List;
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
* <li>{@link #getText()} - The text of the announcement.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* </ul>
* </p>
*
@@ -586,8 +438,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
/**
- * Represents the event of opening a {@link android.widget.PopupWindow},
- * {@link android.view.Menu}, {@link android.app.Dialog}, etc.
+ * Represents the event of a change to a visually distinct section of the user interface.
+ * These events should only be dispatched from {@link android.view.View}s that have
+ * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those
+ * sources. Details about the change are available from {@link #getContentChangeTypes()}.
*/
public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
@@ -674,7 +528,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
/**
- * Represents the event change in the windows shown on the screen.
+ * Represents the event change in the system windows shown on the screen. This event type should
+ * only be dispatched by the system.
*/
public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
@@ -696,7 +551,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
- * A node in the subtree rooted at the source node was added or removed.
+ * One or more content changes occurred in the the subtree rooted at the source node,
+ * or the subtree's structure changed when a node was added or removed.
*/
public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;
@@ -712,6 +568,124 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+ /**
+ * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+ * The node's pane title changed.
+ */
+ public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+ * The node has a pane title, and either just appeared or just was assigned a title when it
+ * had none before.
+ */
+ public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event:
+ * Can mean one of two slightly different things. The primary meaning is that the node has
+ * a pane title, and was removed from the node hierarchy. It will also be sent if the pane
+ * title is set to {@code null} after it contained a title.
+ * No source will be returned if the node is no longer on the screen. To make the change more
+ * clear for the user, the first entry in {@link #getText()} will return the value that would
+ * have been returned by {@code getSource().getPaneTitle()}.
+ */
+ public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window was added.
+ */
+ public static final int WINDOWS_CHANGE_ADDED = 0x00000001;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * A window was removed.
+ */
+ public static final int WINDOWS_CHANGE_REMOVED = 0x00000002;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's title changed.
+ */
+ public static final int WINDOWS_CHANGE_TITLE = 0x00000004;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's bounds changed.
+ */
+ public static final int WINDOWS_CHANGE_BOUNDS = 0x00000008;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's layer changed.
+ */
+ public static final int WINDOWS_CHANGE_LAYER = 0x00000010;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isActive()} changed.
+ */
+ public static final int WINDOWS_CHANGE_ACTIVE = 0x00000020;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isFocused()} changed.
+ */
+ public static final int WINDOWS_CHANGE_FOCUSED = 0x00000040;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's {@link AccessibilityWindowInfo#isAccessibilityFocused()} changed.
+ */
+ public static final int WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED = 0x00000080;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's parent changed.
+ */
+ public static final int WINDOWS_CHANGE_PARENT = 0x00000100;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window's children changed.
+ */
+ public static final int WINDOWS_CHANGE_CHILDREN = 0x00000200;
+
+ /**
+ * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
+ * The window either entered or exited picture-in-picture mode.
+ */
+ public static final int WINDOWS_CHANGE_PIP = 0x00000400;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "WINDOWS_CHANGE_" }, value = {
+ WINDOWS_CHANGE_ADDED,
+ WINDOWS_CHANGE_REMOVED,
+ WINDOWS_CHANGE_TITLE,
+ WINDOWS_CHANGE_BOUNDS,
+ WINDOWS_CHANGE_LAYER,
+ WINDOWS_CHANGE_ACTIVE,
+ WINDOWS_CHANGE_FOCUSED,
+ WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED,
+ WINDOWS_CHANGE_PARENT,
+ WINDOWS_CHANGE_CHILDREN,
+ WINDOWS_CHANGE_PIP
+ })
+ public @interface WindowsChangeTypes {}
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "CONTENT_CHANGE_TYPE_" },
+ value = {
+ CONTENT_CHANGE_TYPE_UNDEFINED,
+ CONTENT_CHANGE_TYPE_SUBTREE,
+ CONTENT_CHANGE_TYPE_TEXT,
+ CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+ CONTENT_CHANGE_TYPE_PANE_TITLE
+ })
+ public @interface ContentChangeTypes {}
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -782,6 +756,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
int mMovementGranularity;
int mAction;
int mContentChangeTypes;
+ int mWindowChangeTypes;
private ArrayList<AccessibilityRecord> mRecords;
@@ -802,6 +777,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
mMovementGranularity = event.mMovementGranularity;
mAction = event.mAction;
mContentChangeTypes = event.mContentChangeTypes;
+ mWindowChangeTypes = event.mWindowChangeTypes;
mEventTime = event.mEventTime;
mPackageName = event.mPackageName;
}
@@ -885,6 +861,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
* </ul>
*/
+ @ContentChangeTypes
public int getContentChangeTypes() {
return mContentChangeTypes;
}
@@ -913,12 +890,49 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @throws IllegalStateException If called from an AccessibilityService.
* @see #getContentChangeTypes()
*/
- public void setContentChangeTypes(int changeTypes) {
+ public void setContentChangeTypes(@ContentChangeTypes int changeTypes) {
enforceNotSealed();
mContentChangeTypes = changeTypes;
}
/**
+ * Get the bit mask of change types signaled by a {@link #TYPE_WINDOWS_CHANGED} event. A
+ * single event may represent multiple change types.
+ *
+ * @return The bit mask of change types.
+ */
+ @WindowsChangeTypes
+ public int getWindowChanges() {
+ return mWindowChangeTypes;
+ }
+
+ /** @hide */
+ public void setWindowChanges(@WindowsChangeTypes int changes) {
+ mWindowChangeTypes = changes;
+ }
+
+ private static String windowChangeTypesToString(@WindowsChangeTypes int types) {
+ return BitUtils.flagsToString(types, AccessibilityEvent::singleWindowChangeTypeToString);
+ }
+
+ private static String singleWindowChangeTypeToString(int type) {
+ switch (type) {
+ case WINDOWS_CHANGE_ADDED: return "WINDOWS_CHANGE_ADDED";
+ case WINDOWS_CHANGE_REMOVED: return "WINDOWS_CHANGE_REMOVED";
+ case WINDOWS_CHANGE_TITLE: return "WINDOWS_CHANGE_TITLE";
+ case WINDOWS_CHANGE_BOUNDS: return "WINDOWS_CHANGE_BOUNDS";
+ case WINDOWS_CHANGE_LAYER: return "WINDOWS_CHANGE_LAYER";
+ case WINDOWS_CHANGE_ACTIVE: return "WINDOWS_CHANGE_ACTIVE";
+ case WINDOWS_CHANGE_FOCUSED: return "WINDOWS_CHANGE_FOCUSED";
+ case WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED:
+ return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED";
+ case WINDOWS_CHANGE_PARENT: return "WINDOWS_CHANGE_PARENT";
+ case WINDOWS_CHANGE_CHILDREN: return "WINDOWS_CHANGE_CHILDREN";
+ default: return Integer.toHexString(type);
+ }
+ }
+
+ /**
* Sets the event type.
*
* @param eventType The event type.
@@ -1025,6 +1039,26 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
}
/**
+ * Convenience method to obtain a {@link #TYPE_WINDOWS_CHANGED} event for a specific window and
+ * change set.
+ *
+ * @param windowId The ID of the window that changed
+ * @param windowChangeTypes The changes to populate
+ * @return An instance of a TYPE_WINDOWS_CHANGED, populated with the requested fields and with
+ * importantForAccessibility set to {@code true}.
+ *
+ * @hide
+ */
+ public static AccessibilityEvent obtainWindowsChangedEvent(
+ int windowId, int windowChangeTypes) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_WINDOWS_CHANGED);
+ event.setWindowId(windowId);
+ event.setWindowChanges(windowChangeTypes);
+ event.setImportantForAccessibility(true);
+ return event;
+ }
+
+ /**
* Returns a cached instance if such is available or a new one is
* instantiated with its type property set.
*
@@ -1099,6 +1133,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
mMovementGranularity = 0;
mAction = 0;
mContentChangeTypes = 0;
+ mWindowChangeTypes = 0;
mPackageName = null;
mEventTime = 0;
if (mRecords != null) {
@@ -1120,6 +1155,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
mMovementGranularity = parcel.readInt();
mAction = parcel.readInt();
mContentChangeTypes = parcel.readInt();
+ mWindowChangeTypes = parcel.readInt();
mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
mEventTime = parcel.readLong();
mConnectionId = parcel.readInt();
@@ -1178,6 +1214,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
parcel.writeInt(mMovementGranularity);
parcel.writeInt(mAction);
parcel.writeInt(mContentChangeTypes);
+ parcel.writeInt(mWindowChangeTypes);
TextUtils.writeToParcel(mPackageName, parcel, 0);
parcel.writeLong(mEventTime);
parcel.writeInt(mConnectionId);
@@ -1236,41 +1273,33 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
builder.append("EventType: ").append(eventTypeToString(mEventType));
builder.append("; EventTime: ").append(mEventTime);
builder.append("; PackageName: ").append(mPackageName);
- builder.append("; MovementGranularity: ").append(mMovementGranularity);
- builder.append("; Action: ").append(mAction);
- builder.append(super.toString());
- if (DEBUG) {
- builder.append("\n");
+ if (!DEBUG_CONCISE_TOSTRING || mMovementGranularity != 0) {
+ builder.append("; MovementGranularity: ").append(mMovementGranularity);
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mAction != 0) {
+ builder.append("; Action: ").append(mAction);
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mContentChangeTypes != 0) {
builder.append("; ContentChangeTypes: ").append(
contentChangeTypesToString(mContentChangeTypes));
- builder.append("; sourceWindowId: ").append(mSourceWindowId);
- builder.append("; mSourceNodeId: ").append(mSourceNodeId);
- for (int i = 0; i < getRecordCount(); i++) {
- final AccessibilityRecord record = getRecord(i);
- builder.append(" Record ");
- builder.append(i);
- builder.append(":");
- builder.append(" [ ClassName: " + record.mClassName);
- builder.append("; Text: " + record.mText);
- builder.append("; ContentDescription: " + record.mContentDescription);
- builder.append("; ItemCount: " + record.mItemCount);
- builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
- builder.append("; IsEnabled: " + record.isEnabled());
- builder.append("; IsPassword: " + record.isPassword());
- builder.append("; IsChecked: " + record.isChecked());
- builder.append("; IsFullScreen: " + record.isFullScreen());
- builder.append("; Scrollable: " + record.isScrollable());
- builder.append("; BeforeText: " + record.mBeforeText);
- builder.append("; FromIndex: " + record.mFromIndex);
- builder.append("; ToIndex: " + record.mToIndex);
- builder.append("; ScrollX: " + record.mScrollX);
- builder.append("; ScrollY: " + record.mScrollY);
- builder.append("; AddedCount: " + record.mAddedCount);
- builder.append("; RemovedCount: " + record.mRemovedCount);
- builder.append("; ParcelableData: " + record.mParcelableData);
- builder.append(" ]");
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
+ builder.append("; WindowChangeTypes: ").append(
+ contentChangeTypesToString(mWindowChangeTypes));
+ }
+ super.appendTo(builder);
+ if (DEBUG || DEBUG_CONCISE_TOSTRING) {
+ if (!DEBUG_CONCISE_TOSTRING) {
builder.append("\n");
}
+ if (DEBUG) {
+ builder.append("; SourceWindowId: ").append(mSourceWindowId);
+ builder.append("; SourceNodeId: ").append(mSourceNodeId);
+ }
+ for (int i = 0; i < getRecordCount(); i++) {
+ builder.append(" Record ").append(i).append(":");
+ getRecord(i).appendTo(builder).append("\n");
+ }
} else {
builder.append("; recordCount: ").append(getRecordCount());
}
diff --git a/android/view/accessibility/AccessibilityNodeInfo.java b/android/view/accessibility/AccessibilityNodeInfo.java
index 28ef6978..23e7d619 100644
--- a/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/android/view/accessibility/AccessibilityNodeInfo.java
@@ -639,6 +639,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000;
+ private static final int BOOLEAN_PROPERTY_IS_HEADING = 0x0200000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -723,7 +725,9 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mText;
private CharSequence mHintText;
private CharSequence mError;
+ private CharSequence mPaneTitle;
private CharSequence mContentDescription;
+ private CharSequence mTooltipText;
private String mViewIdResourceName;
private ArrayList<String> mExtraDataKeys;
@@ -2033,6 +2037,33 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * If this node represents a visually distinct region of the screen that may update separately
+ * from the rest of the window, it is considered a pane. Set the pane title to indicate that
+ * the node is a pane, and to provide a title for it.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param paneTitle The title of the pane represented by this node.
+ */
+ public void setPaneTitle(@Nullable CharSequence paneTitle) {
+ enforceNotSealed();
+ mPaneTitle = (paneTitle == null)
+ ? null : paneTitle.subSequence(0, paneTitle.length());
+ }
+
+ /**
+ * Get the title of the pane represented by this node.
+ *
+ * @return The title of the pane represented by this node, or {@code null} if this node does
+ * not represent a pane.
+ */
+ public @Nullable CharSequence getPaneTitle() {
+ return mPaneTitle;
+ }
+
+ /**
* Get the drawing order of the view corresponding it this node.
* <p>
* Drawing order is determined only within the node's parent, so this index is only relative
@@ -2381,6 +2412,30 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Returns whether node represents a heading.
+ *
+ * @return {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public boolean isHeading() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING);
+ }
+
+ /**
+ * Sets whether the node represents a heading.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param isHeading {@code true} if the node is a heading, {@code false} otherwise.
+ */
+ public void setHeading(boolean isHeading) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IS_HEADING, isHeading);
+ }
+
+ /**
* Gets the package this node comes from.
*
* @return The package name.
@@ -2601,6 +2656,34 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets the tooltip text of this node.
+ *
+ * @return The tooltip text.
+ */
+ @Nullable
+ public CharSequence getTooltipText() {
+ return mTooltipText;
+ }
+
+ /**
+ * Sets the tooltip text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param tooltipText The tooltip text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTooltipText(@Nullable CharSequence tooltipText) {
+ enforceNotSealed();
+ mTooltipText = (tooltipText == null) ? null
+ : tooltipText.subSequence(0, tooltipText.length());
+ }
+
+ /**
* Sets the view for which the view represented by this info serves as a
* label for accessibility purposes.
*
@@ -3151,6 +3234,14 @@ public class AccessibilityNodeInfo implements Parcelable {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -3270,6 +3361,9 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeCharSequence(mContentDescription);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
@@ -3341,6 +3435,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mHintText = other.mHintText;
mError = other.mError;
mContentDescription = other.mContentDescription;
+ mPaneTitle = other.mPaneTitle;
+ mTooltipText = other.mTooltipText;
mViewIdResourceName = other.mViewIdResourceName;
if (mActions != null) mActions.clear();
@@ -3461,6 +3557,8 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mContentDescription = parcel.readCharSequence();
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
@@ -3623,6 +3721,10 @@ public class AccessibilityNodeInfo implements Parcelable {
return "ACTION_SET_PROGRESS";
case R.id.accessibilityActionContextClick:
return "ACTION_CONTEXT_CLICK";
+ case R.id.accessibilityActionShowTooltip:
+ return "ACTION_SHOW_TOOLTIP";
+ case R.id.accessibilityActionHideTooltip:
+ return "ACTION_HIDE_TOOLTIP";
default:
return "ACTION_UNKNOWN";
}
@@ -3736,6 +3838,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; error: ").append(mError);
builder.append("; maxTextLength: ").append(mMaxTextLength);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; tooltipText: ").append(mTooltipText);
builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; checkable: ").append(isCheckable());
@@ -4150,6 +4253,20 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final AccessibilityAction ACTION_MOVE_WINDOW =
new AccessibilityAction(R.id.accessibilityActionMoveWindow);
+ /**
+ * Action to show a tooltip. A node should expose this action only for views with tooltip
+ * text that but are not currently showing a tooltip.
+ */
+ public static final AccessibilityAction ACTION_SHOW_TOOLTIP =
+ new AccessibilityAction(R.id.accessibilityActionShowTooltip);
+
+ /**
+ * Action to hide a tooltip. A node should expose this action only for views that are
+ * currently showing a tooltip.
+ */
+ public static final AccessibilityAction ACTION_HIDE_TOOLTIP =
+ new AccessibilityAction(R.id.accessibilityActionHideTooltip);
+
private final int mActionId;
private final CharSequence mLabel;
@@ -4562,7 +4679,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)}).
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
int columnIndex, int columnSpan, boolean heading) {
@@ -4576,7 +4694,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param rowSpan The number of rows the item spans.
* @param columnIndex The column index at which the item is located.
* @param columnSpan The number of columns the item spans.
- * @param heading Whether the item is a heading.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)})
* @param selected Whether the item is selected.
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
@@ -4663,6 +4782,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* heading, table header, etc.
*
* @return If the item is a heading.
+ * @deprecated Use {@link AccessibilityNodeInfo#isHeading()}
*/
public boolean isHeading() {
return mHeading;
diff --git a/android/view/accessibility/AccessibilityRecord.java b/android/view/accessibility/AccessibilityRecord.java
index fa505c97..0a709f8f 100644
--- a/android/view/accessibility/AccessibilityRecord.java
+++ b/android/view/accessibility/AccessibilityRecord.java
@@ -16,6 +16,8 @@
package android.view.accessibility;
+import static com.android.internal.util.CollectionUtils.isEmpty;
+
import android.annotation.Nullable;
import android.os.Parcelable;
import android.view.View;
@@ -55,6 +57,8 @@ import java.util.List;
* @see AccessibilityNodeInfo
*/
public class AccessibilityRecord {
+ /** @hide */
+ protected static final boolean DEBUG_CONCISE_TOSTRING = false;
private static final int UNDEFINED = -1;
@@ -888,28 +892,69 @@ public class AccessibilityRecord {
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(" [ ClassName: " + mClassName);
- builder.append("; Text: " + mText);
- builder.append("; ContentDescription: " + mContentDescription);
- builder.append("; ItemCount: " + mItemCount);
- builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
- builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
- builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
- builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
- builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
- builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
- builder.append("; BeforeText: " + mBeforeText);
- builder.append("; FromIndex: " + mFromIndex);
- builder.append("; ToIndex: " + mToIndex);
- builder.append("; ScrollX: " + mScrollX);
- builder.append("; ScrollY: " + mScrollY);
- builder.append("; MaxScrollX: " + mMaxScrollX);
- builder.append("; MaxScrollY: " + mMaxScrollY);
- builder.append("; AddedCount: " + mAddedCount);
- builder.append("; RemovedCount: " + mRemovedCount);
- builder.append("; ParcelableData: " + mParcelableData);
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ StringBuilder appendTo(StringBuilder builder) {
+ builder.append(" [ ClassName: ").append(mClassName);
+ if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) {
+ appendPropName(builder, "Text").append(mText);
+ }
+ append(builder, "ContentDescription", mContentDescription);
+ append(builder, "ItemCount", mItemCount);
+ append(builder, "CurrentItemIndex", mCurrentItemIndex);
+
+ appendUnless(true, PROPERTY_ENABLED, builder);
+ appendUnless(false, PROPERTY_PASSWORD, builder);
+ appendUnless(false, PROPERTY_CHECKED, builder);
+ appendUnless(false, PROPERTY_FULL_SCREEN, builder);
+ appendUnless(false, PROPERTY_SCROLLABLE, builder);
+
+ append(builder, "BeforeText", mBeforeText);
+ append(builder, "FromIndex", mFromIndex);
+ append(builder, "ToIndex", mToIndex);
+ append(builder, "ScrollX", mScrollX);
+ append(builder, "ScrollY", mScrollY);
+ append(builder, "MaxScrollX", mMaxScrollX);
+ append(builder, "MaxScrollY", mMaxScrollY);
+ append(builder, "AddedCount", mAddedCount);
+ append(builder, "RemovedCount", mRemovedCount);
+ append(builder, "ParcelableData", mParcelableData);
builder.append(" ]");
- return builder.toString();
+ return builder;
+ }
+
+ private void appendUnless(boolean defValue, int prop, StringBuilder builder) {
+ boolean value = getBooleanProperty(prop);
+ if (DEBUG_CONCISE_TOSTRING && value == defValue) return;
+ appendPropName(builder, singleBooleanPropertyToString(prop))
+ .append(value);
+ }
+
+ private static String singleBooleanPropertyToString(int prop) {
+ switch (prop) {
+ case PROPERTY_CHECKED: return "Checked";
+ case PROPERTY_ENABLED: return "Enabled";
+ case PROPERTY_PASSWORD: return "Password";
+ case PROPERTY_FULL_SCREEN: return "FullScreen";
+ case PROPERTY_SCROLLABLE: return "Scrollable";
+ case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
+ return "ImportantForAccessibility";
+ default: return Integer.toHexString(prop);
+ }
+ }
+
+ private void append(StringBuilder builder, String propName, int propValue) {
+ if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return;
+ appendPropName(builder, propName).append(propValue);
+ }
+
+ private void append(StringBuilder builder, String propName, Object propValue) {
+ if (DEBUG_CONCISE_TOSTRING && propValue == null) return;
+ appendPropName(builder, propName).append(propValue);
+ }
+
+ private StringBuilder appendPropName(StringBuilder builder, String propName) {
+ return builder.append("; ").append(propName).append(": ");
}
}
diff --git a/android/view/accessibility/AccessibilityViewHierarchyState.java b/android/view/accessibility/AccessibilityViewHierarchyState.java
new file mode 100644
index 00000000..447fafaa
--- /dev/null
+++ b/android/view/accessibility/AccessibilityViewHierarchyState.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Accessibility-related state of a {@link android.view.ViewRootImpl}
+ *
+ * @hide
+ */
+public class AccessibilityViewHierarchyState {
+ private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
+ private @Nullable SendWindowContentChangedAccessibilityEvent
+ mSendWindowContentChangedAccessibilityEvent;
+
+ /**
+ * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed
+ */
+ public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() {
+ if (mSendViewScrolledAccessibilityEvent == null) {
+ mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+ }
+ return mSendViewScrolledAccessibilityEvent;
+ }
+
+ public boolean isScrollEventSenderInitialized() {
+ return mSendViewScrolledAccessibilityEvent != null;
+ }
+
+ /**
+ * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed
+ */
+ public @NonNull SendWindowContentChangedAccessibilityEvent
+ getSendWindowContentChangedAccessibilityEvent() {
+ if (mSendWindowContentChangedAccessibilityEvent == null) {
+ mSendWindowContentChangedAccessibilityEvent =
+ new SendWindowContentChangedAccessibilityEvent();
+ }
+ return mSendWindowContentChangedAccessibilityEvent;
+ }
+
+ public boolean isWindowContentChangedEventSenderInitialized() {
+ return mSendWindowContentChangedAccessibilityEvent != null;
+ }
+}
diff --git a/android/view/accessibility/AccessibilityWindowInfo.java b/android/view/accessibility/AccessibilityWindowInfo.java
index ef1a3f3b..c1c9174c 100644
--- a/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/android/view/accessibility/AccessibilityWindowInfo.java
@@ -21,9 +21,12 @@ import android.annotation.TestApi;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
+import android.view.accessibility.AccessibilityEvent.WindowsChangeTypes;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -575,7 +578,7 @@ public final class AccessibilityWindowInfo implements Parcelable {
StringBuilder builder = new StringBuilder();
builder.append("AccessibilityWindowInfo[");
builder.append("title=").append(mTitle);
- builder.append("id=").append(mId);
+ builder.append(", id=").append(mId);
builder.append(", type=").append(typeToString(mType));
builder.append(", layer=").append(mLayer);
builder.append(", bounds=").append(mBoundsInScreen);
@@ -713,6 +716,60 @@ public final class AccessibilityWindowInfo implements Parcelable {
return false;
}
+ /**
+ * Reports how this window differs from a possibly different state of the same window. The
+ * argument must have the same id and type as neither of those properties may change.
+ *
+ * @param other The new state.
+ * @return A set of flags showing how the window has changes, or 0 if the two states are the
+ * same.
+ *
+ * @hide
+ */
+ @WindowsChangeTypes
+ public int differenceFrom(AccessibilityWindowInfo other) {
+ if (other.mId != mId) {
+ throw new IllegalArgumentException("Not same window.");
+ }
+ if (other.mType != mType) {
+ throw new IllegalArgumentException("Not same type.");
+ }
+ int changes = 0;
+ if (!TextUtils.equals(mTitle, other.mTitle)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_TITLE;
+ }
+
+ if (!mBoundsInScreen.equals(other.mBoundsInScreen)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+ }
+ if (mLayer != other.mLayer) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_LAYER;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
+ }
+ if (getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)
+ != other.getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_PIP;
+ }
+ if (mParentId != other.mParentId) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_PARENT;
+ }
+ if (!Objects.equals(mChildIds, other.mChildIds)) {
+ changes |= AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
+ }
+ return changes;
+ }
+
public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
new Creator<AccessibilityWindowInfo>() {
@Override
diff --git a/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
new file mode 100644
index 00000000..40a1b6a2
--- /dev/null
+++ b/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+
+import android.annotation.NonNull;
+import android.view.View;
+
+/**
+ * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+ *
+ * @hide
+ */
+public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender {
+
+ public int mDeltaX;
+ public int mDeltaY;
+
+ /**
+ * Post a scroll event to be sent for the given view
+ */
+ public void post(View source, int dx, int dy) {
+ if (!isPendingFor(source)) sendNowIfPending();
+
+ mDeltaX += dx;
+ mDeltaY += dy;
+
+ if (!isPendingFor(source)) scheduleFor(source);
+ }
+
+ @Override
+ protected void performSendEvent(@NonNull View source) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ event.setScrollDeltaX(mDeltaX);
+ event.setScrollDeltaY(mDeltaY);
+ source.sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
+ protected void resetState(@NonNull View source) {
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+}
diff --git a/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
new file mode 100644
index 00000000..df38fba5
--- /dev/null
+++ b/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+
+import static com.android.internal.util.ObjectUtils.firstNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+import android.view.ViewParent;
+
+import java.util.HashSet;
+
+/**
+ * @hide
+ */
+public class SendWindowContentChangedAccessibilityEvent
+ extends ThrottlingAccessibilityEventSender {
+
+ private int mChangeTypes = 0;
+
+ private HashSet<View> mTempHashSet;
+
+ @Override
+ protected void performSendEvent(@NonNull View source) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(mChangeTypes);
+ source.sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
+ protected void resetState(@Nullable View source) {
+ if (source != null) {
+ source.resetSubtreeAccessibilityStateChanged();
+ }
+ mChangeTypes = 0;
+ }
+
+ /**
+ * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given
+ * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view
+ */
+ public void runOrPost(View source, int changeType) {
+ if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) {
+ sendNowIfPending();
+ mChangeTypes = changeType;
+ sendNow(source);
+ } else {
+ mChangeTypes |= changeType;
+ scheduleFor(source);
+ }
+ }
+
+ @Override
+ protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+ // If there is no common predecessor, then oldSource points to
+ // a removed view, hence in this case always prefer the newSource.
+ return firstNotNull(
+ getCommonPredecessor(oldSource, newSource),
+ newSource);
+ }
+
+ private View getCommonPredecessor(View first, View second) {
+ if (mTempHashSet == null) {
+ mTempHashSet = new HashSet<>();
+ }
+ HashSet<View> seen = mTempHashSet;
+ seen.clear();
+ View firstCurrent = first;
+ while (firstCurrent != null) {
+ seen.add(firstCurrent);
+ ViewParent firstCurrentParent = firstCurrent.getParent();
+ if (firstCurrentParent instanceof View) {
+ firstCurrent = (View) firstCurrentParent;
+ } else {
+ firstCurrent = null;
+ }
+ }
+ View secondCurrent = second;
+ while (secondCurrent != null) {
+ if (seen.contains(secondCurrent)) {
+ seen.clear();
+ return secondCurrent;
+ }
+ ViewParent secondCurrentParent = secondCurrent.getParent();
+ if (secondCurrentParent instanceof View) {
+ secondCurrent = (View) secondCurrentParent;
+ } else {
+ secondCurrent = null;
+ }
+ }
+ seen.clear();
+ return null;
+ }
+}
diff --git a/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/android/view/accessibility/ThrottlingAccessibilityEventSender.java
new file mode 100644
index 00000000..66fa3010
--- /dev/null
+++ b/android/view/accessibility/ThrottlingAccessibilityEventSender.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.CalledFromWrongThreadException;
+
+/**
+ * A throttling {@link AccessibilityEvent} sender that relies on its currently associated
+ * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly
+ * {@link #tryMerge merge} together any events that come in less than
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval
+ * the configured amount of milliseconds} apart.
+ *
+ * The suggested usage is to create a singleton extending this class, holding any state specific to
+ * the particular event type that the subclass represents, and have an 'entrypoint' method that
+ * delegates to {@link #scheduleFor(View)}.
+ * For example:
+ *
+ * {@code
+ * public void post(View view, String text, int resId) {
+ * mText = text;
+ * mId = resId;
+ * scheduleFor(view);
+ * }
+ * }
+ *
+ * @see #scheduleFor(View)
+ * @see #tryMerge(View, View)
+ * @see #performSendEvent(View)
+ * @hide
+ */
+public abstract class ThrottlingAccessibilityEventSender {
+
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "ThrottlingA11ySender";
+
+ View mSource;
+ private long mLastSendTimeMillis = Long.MIN_VALUE;
+ private boolean mIsPending = false;
+
+ private final Runnable mWorker = () -> {
+ View source = mSource;
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")");
+
+ if (!checkAndResetIsPending() || source == null) {
+ resetStateInternal();
+ return;
+ }
+
+ // Accessibility may be turned off while we were waiting
+ if (isAccessibilityEnabled(source)) {
+ mLastSendTimeMillis = SystemClock.uptimeMillis();
+ performSendEvent(source);
+ }
+ resetStateInternal();
+ };
+
+ /**
+ * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well
+ * as any extra data from this instance's state.
+ *
+ * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or
+ * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for
+ * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that
+ * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any.
+ */
+ protected abstract void performSendEvent(@NonNull View source);
+
+ /**
+ * Perform optional cleanup after {@link #performSendEvent}
+ *
+ * @param source the view this event was associated with
+ */
+ protected abstract void resetState(@Nullable View source);
+
+ /**
+ * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource}
+ * into one, with source set to the resulting {@link View}
+ *
+ * A result of {@code null} means merger is not possible, resulting in the currently pending
+ * event being flushed before proceeding.
+ */
+ protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+ return null;
+ }
+
+ /**
+ * Schedules a {@link #performSendEvent} with the source {@link View} set to given
+ * {@code source}
+ *
+ * If an event is already scheduled a {@link #tryMerge merge} will be attempted.
+ * If merging is not possible (as indicated by the null result from {@link #tryMerge}),
+ * the currently scheduled event will be {@link #sendNow sent immediately} and the new one
+ * will be scheduled afterwards.
+ */
+ protected final void scheduleFor(@NonNull View source) {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")");
+
+ Handler uiHandler = source.getHandler();
+ if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) {
+ CalledFromWrongThreadException e = new CalledFromWrongThreadException(
+ "Expected to be called from main thread but was called from "
+ + Thread.currentThread());
+ // TODO: Throw the exception
+ Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android "
+ + "versions will throw an exception.", e);
+ }
+
+ if (!isAccessibilityEnabled(source)) return;
+
+ if (mIsPending) {
+ View merged = tryMerge(mSource, source);
+ if (merged != null) {
+ setSource(merged);
+ return;
+ } else {
+ sendNow();
+ }
+ }
+
+ setSource(source);
+
+ final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis;
+ final long minEventIntervalMillis =
+ ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+ if (timeSinceLastMillis >= minEventIntervalMillis) {
+ sendNow();
+ } else {
+ mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis);
+ }
+ }
+
+ static boolean isAccessibilityEnabled(@NonNull View contextProvider) {
+ return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled();
+ }
+
+ protected final void sendNow(View source) {
+ setSource(source);
+ sendNow();
+ }
+
+ private void sendNow() {
+ mSource.removeCallbacks(mWorker);
+ mWorker.run();
+ }
+
+ /**
+ * Flush the event if one is pending
+ */
+ public void sendNowIfPending() {
+ if (mIsPending) sendNow();
+ }
+
+ /**
+ * Cancel the event if one is pending and is for the given view
+ */
+ public final void cancelIfPendingFor(@NonNull View source) {
+ if (isPendingFor(source)) cancelIfPending(this);
+ }
+
+ /**
+ * @return whether an event is currently pending for the given source view
+ */
+ protected final boolean isPendingFor(@Nullable View source) {
+ return mIsPending && mSource == source;
+ }
+
+ /**
+ * Cancel the event if one is not null and pending
+ */
+ public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) {
+ if (sender == null || !sender.checkAndResetIsPending()) return;
+ sender.mSource.removeCallbacks(sender.mWorker);
+ sender.resetStateInternal();
+ }
+
+ void resetStateInternal() {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()");
+
+ resetState(mSource);
+ setSource(null);
+ }
+
+ boolean checkAndResetIsPending() {
+ if (mIsPending) {
+ mIsPending = false;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void setSource(@Nullable View source) {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")");
+
+ if (source == null && mIsPending) {
+ Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this);
+ return;
+ }
+
+ if (source != null && !mIsPending) {
+ // At most one can be pending at any given time
+ View oldSource = mSource;
+ if (oldSource != null) {
+ ViewRootImpl viewRootImpl = oldSource.getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.flushPendingAccessibilityEvents();
+ }
+ }
+ mIsPending = true;
+ }
+ mSource = source;
+ }
+
+ String thisClass() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public String toString() {
+ return thisClass() + "(" + mSource + ")";
+ }
+
+}