[Accessibility APIs] Add Expansion API
This CL will add the implementation for the API defined here:
go/aria-expanded-collapsed-android-api-change-design-proposal
Flag: android.view.accessibility.a11y_expansion_state_api
Test: CQ + new Tests
Bug: 362782466
Change-Id: Ibb53e25539a68e614cbf712035241a1e8f2e67f4
diff --git a/core/api/current.txt b/core/api/current.txt
index 911e7de..f3f3828 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -55010,6 +55010,7 @@
field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
field public static final int CONTENT_CHANGE_TYPE_ENABLED = 4096; // 0x1000
field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
+ field @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public static final int CONTENT_CHANGE_TYPE_EXPANDED = 16384; // 0x4000
field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -55159,6 +55160,7 @@
method public CharSequence getContentDescription();
method public int getDrawingOrder();
method public CharSequence getError();
+ method @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public int getExpandedState();
method @Nullable public android.view.accessibility.AccessibilityNodeInfo.ExtraRenderingInfo getExtraRenderingInfo();
method public android.os.Bundle getExtras();
method public CharSequence getHintText();
@@ -55251,6 +55253,7 @@
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(CharSequence);
+ method @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public void setExpandedState(int);
method public void setFocusable(boolean);
method public void setFocused(boolean);
method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
@@ -55337,6 +55340,10 @@
field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CHECKED_STATE_PARTIAL = 2; // 0x2
field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CHECKED_STATE_TRUE = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo> CREATOR;
+ field @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public static final int EXPANDED_STATE_COLLAPSED = 1; // 0x1
+ field @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public static final int EXPANDED_STATE_FULL = 3; // 0x3
+ field @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public static final int EXPANDED_STATE_PARTIAL = 2; // 0x2
+ field @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public static final int EXPANDED_STATE_UNDEFINED = 0; // 0x0
field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
field public static final int EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_MAX_LENGTH = 20000; // 0x4e20
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index dfac244..4e9d054 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -798,6 +798,18 @@
@FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
public static final int CONTENT_CHANGE_TYPE_CHECKED = 1 << 13;
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: The source node changed its
+ * expanded state which is returned by {@link AccessibilityNodeInfo#getExpandedState()}. The
+ * view changing the node's expanded state should call {@link
+ * AccessibilityNodeInfo#setExpandedState(int)} and then send this event.
+ *
+ * @see AccessibilityNodeInfo#getExpandedState()
+ * @see AccessibilityNodeInfo#setExpandedState(int)
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public static final int CONTENT_CHANGE_TYPE_EXPANDED = 1 << 14;
+
// Speech state change types.
/** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 60ccb77..d3e7faa 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -756,6 +756,56 @@
public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
"android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+ // Expanded state types.
+
+ /**
+ * Expanded state for a non-expandable element
+ *
+ * @see #getExpandedState()
+ * @see #setExpandedState(int)
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public static final int EXPANDED_STATE_UNDEFINED = 0;
+
+ /**
+ * Expanded state for a collapsed expandable element.
+ *
+ * @see #getExpandedState()
+ * @see #setExpandedState(int)
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public static final int EXPANDED_STATE_COLLAPSED = 1;
+
+ /**
+ * Expanded state for an expanded expandable element that can still be expanded further.
+ *
+ * @see #getExpandedState()
+ * @see #setExpandedState(int)
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public static final int EXPANDED_STATE_PARTIAL = 2;
+
+ /**
+ * Expanded state for a expanded expandable element that cannot be expanded further.
+ *
+ * @see #getExpandedState()
+ * @see #setExpandedState(int)
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public static final int EXPANDED_STATE_FULL = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "EXPANDED_STATE_",
+ value = {
+ EXPANDED_STATE_UNDEFINED,
+ EXPANDED_STATE_COLLAPSED,
+ EXPANDED_STATE_PARTIAL,
+ EXPANDED_STATE_FULL,
+ })
+ public @interface ExpandedState {}
+
// Focus types.
/**
@@ -1048,6 +1098,10 @@
private int mMaxTextLength = -1;
private int mMovementGranularities;
+ // TODO(b/362782158) Initialize mExpandedState explicitly with
+ // the EXPANDED_STATE_UNDEFINED state when flagging is removed.
+ private int mExpandedState;
+
private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
private int mInputType = InputType.TYPE_NULL;
@@ -1931,6 +1985,47 @@
}
/**
+ * Sets the expanded state of the node.
+ *
+ * <p><strong>Note:</strong> Cannot be called from an {@link
+ * android.accessibilityservice.AccessibilityService}. This class is made immutable before being
+ * delivered to an {@link android.accessibilityservice.AccessibilityService}.
+ *
+ * @param state new expanded state of this node.
+ * @throws IllegalArgumentException If state is not one of:
+ * <ul>
+ * <li>{@link #EXPANDED_STATE_UNDEFINED}
+ * <li>{@link #EXPANDED_STATE_COLLAPSED}
+ * <li>{@link #EXPANDED_STATE_PARTIAL}
+ * <li>{@link #EXPANDED_STATE_FULL}
+ * </ul>
+ *
+ * @throws IllegalStateException If called from an AccessibilityService
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public void setExpandedState(@ExpandedState int state) {
+ enforceValidExpandedState(state);
+ enforceNotSealed();
+ mExpandedState = state;
+ }
+
+ /**
+ * Gets the expanded state for this node.
+ *
+ * @return The expanded state, one of:
+ * <ul>
+ * <li>{@link #EXPANDED_STATE_UNDEFINED}
+ * <li>{@link #EXPANDED_STATE_COLLAPSED}
+ * <li>{@link #EXPANDED_STATE_FULL}
+ * <li>{@link #EXPANDED_STATE_PARTIAL}
+ * </ul>
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
+ public @ExpandedState int getExpandedState() {
+ return mExpandedState;
+ }
+
+ /**
* Sets the minimum time duration between two content change events, which is used in throttling
* content change events in accessibility services.
*
@@ -4369,6 +4464,20 @@
}
}
+ private void enforceValidExpandedState(int state) {
+ if (Flags.a11yExpansionStateApi()) {
+ switch (state) {
+ case EXPANDED_STATE_UNDEFINED:
+ case EXPANDED_STATE_COLLAPSED:
+ case EXPANDED_STATE_PARTIAL:
+ case EXPANDED_STATE_FULL:
+ return;
+ default:
+ throw new IllegalArgumentException("Unknown expanded state: " + state);
+ }
+ }
+ }
+
/**
* Enforces that this instance is not sealed.
*
@@ -4623,6 +4732,11 @@
if (mChecked != DEFAULT.mChecked) {
nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (mExpandedState != DEFAULT.mExpandedState) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+
int totalFields = fieldIndex;
parcel.writeLong(nonDefaultFields);
@@ -4794,6 +4908,9 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mChecked);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mExpandedState);
+ }
if (DEBUG) {
fieldIndex--;
@@ -4883,6 +5000,7 @@
mLeashedParent = other.mLeashedParent;
mLeashedParentNodeId = other.mLeashedParentNodeId;
mChecked = other.mChecked;
+ mExpandedState = other.mExpandedState;
}
private void initCopyInfos(AccessibilityNodeInfo other) {
@@ -5075,6 +5193,9 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mChecked = parcel.readInt();
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mExpandedState = parcel.readInt();
+ }
mSealed = sealed;
}
@@ -5249,6 +5370,26 @@
}
}
+ private static String getExpandedStateSymbolicName(int state) {
+ if (Flags.a11yExpansionStateApi()) {
+ switch (state) {
+ case EXPANDED_STATE_UNDEFINED:
+ return "EXPANDED_STATE_UNDEFINED";
+ case EXPANDED_STATE_COLLAPSED:
+ return "EXPANDED_STATE_COLLAPSED";
+ case EXPANDED_STATE_PARTIAL:
+ return "EXPANDED_STATE_PARTIAL";
+ case EXPANDED_STATE_FULL:
+ return "EXPANDED_STATE_FULL";
+ default:
+ throw new IllegalArgumentException("Unknown expanded state: " + state);
+ }
+ } else {
+ // TODO(b/362782158) Remove when flag is removed.
+ return "";
+ }
+ }
+
private static boolean canPerformRequestOverConnection(int connectionId,
int windowId, long accessibilityNodeId) {
final boolean hasWindowId = windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
@@ -5346,6 +5487,7 @@
builder.append("; containerTitle: ").append(mContainerTitle);
builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; uniqueId: ").append(mUniqueId);
+ builder.append("; expandedState: ").append(getExpandedStateSymbolicName(mExpandedState));
builder.append("; checkable: ").append(isCheckable());
builder.append("; checked: ").append(isChecked());
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index da202b6..930e03d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 45;
+ private static final int NUM_MARSHALLED_PROPERTIES = 46;
/**
* The number of properties that are purposely not marshalled