diff options
author | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
---|---|---|
committer | Jeff Davidson <jpd@google.com> | 2018-02-08 15:30:06 -0800 |
commit | a192cc2a132cb0ee8588e2df755563ec7008c179 (patch) | |
tree | 380e4db22df19c819bd37df34bf06e7568916a50 /android/view/accessibility | |
parent | 98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff) | |
download | android-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')
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 + ")"; + } + +} |