summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Akihiro Ota <akihiroota@google.com> 2025-02-11 10:14:08 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-11 10:14:08 -0800
commit44a905df3323798545866ddefa1b8a222c5cb9b0 (patch)
tree5893f4aa4bb7813af775e59511aa8042a334f40b
parent26ceeeb8fb5a303ea830da9034464e25d315b8ba (diff)
parent7f15b86ae5aec19402d9b06058d398e244f4ac30 (diff)
Merge "[Selection API] Add Selection API implementation" into main
-rw-r--r--core/api/current.txt25
-rw-r--r--core/api/lint-baseline.txt17
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java405
-rw-r--r--core/res/res/values/ids.xml3
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java2
6 files changed, 439 insertions, 15 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 050cad4e1a52..d4ed533cad9e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2419,6 +2419,7 @@ package android {
field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final int accessibilityActionSetExtendedSelection;
field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
field public static final int accessibilityActionShowTextSuggestions = 16908376; // 0x1020058
@@ -56266,6 +56267,7 @@ package android.view.accessibility {
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
+ method @FlaggedApi("android.view.accessibility.a11y_selection_api") @Nullable public android.view.accessibility.AccessibilityNodeInfo.Selection getSelection();
method @Nullable public CharSequence getStateDescription();
method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
method public CharSequence getText();
@@ -56374,6 +56376,7 @@ package android.view.accessibility {
method public void setScreenReaderFocusable(boolean);
method public void setScrollable(boolean);
method public void setSelected(boolean);
+ method @FlaggedApi("android.view.accessibility.a11y_selection_api") public void setSelection(@Nullable android.view.accessibility.AccessibilityNodeInfo.Selection);
method public void setShowingHintText(boolean);
method public void setSource(android.view.View);
method public void setSource(android.view.View, int);
@@ -56406,6 +56409,7 @@ package android.view.accessibility {
field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE = "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
@@ -56503,6 +56507,7 @@ package android.view.accessibility {
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_TO_POSITION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SCROLL_UP;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SELECT;
+ field @FlaggedApi("android.view.accessibility.a11y_selection_api") @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_EXTENDED_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_PROGRESS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
@@ -56588,6 +56593,26 @@ package android.view.accessibility {
field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
}
+ @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.Selection implements android.os.Parcelable {
+ ctor public AccessibilityNodeInfo.Selection(@NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition, @NonNull android.view.accessibility.AccessibilityNodeInfo.SelectionPosition);
+ method public int describeContents();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getEnd();
+ method @NonNull public android.view.accessibility.AccessibilityNodeInfo.SelectionPosition getStart();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.Selection> CREATOR;
+ }
+
+ @FlaggedApi("android.view.accessibility.a11y_selection_api") public static final class AccessibilityNodeInfo.SelectionPosition implements android.os.Parcelable {
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.accessibility.AccessibilityNodeInfo, int);
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int);
+ ctor public AccessibilityNodeInfo.SelectionPosition(@NonNull android.view.View, int, int);
+ method public int describeContents();
+ method @Nullable public android.view.accessibility.AccessibilityNodeInfo getNode();
+ method public int getOffset();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.SelectionPosition> CREATOR;
+ }
+
public static final class AccessibilityNodeInfo.TouchDelegateInfo implements android.os.Parcelable {
ctor public AccessibilityNodeInfo.TouchDelegateInfo(@NonNull java.util.Map<android.graphics.Region,android.view.View>);
method public int describeContents();
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index e71dffaf152d..577113b80d84 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,3 +1,4 @@
+
// Baseline format: 1.0
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
@@ -243,8 +244,6 @@ BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_SPECIF
Field 'ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior
-
-
DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
@@ -381,8 +380,6 @@ DeprecationMismatch: android.webkit.WebViewDatabase#hasFormData():
Method android.webkit.WebViewDatabase.hasFormData(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
-
-
FlaggedApiLiteral: android.Manifest.permission#BIND_APP_FUNCTION_SERVICE:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER).
FlaggedApiLiteral: android.Manifest.permission#BIND_TV_AD_SERVICE:
@@ -405,26 +402,22 @@ FlaggedApiLiteral: android.R.attr#optional:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.content.pm.Flags.FLAG_SDK_LIB_INDEPENDENCE).
FlaggedApiLiteral: android.R.attr#supplementalDescription:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION).
+FlaggedApiLiteral: android.R.id#accessibilityActionSetExtendedSelection:
+ @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_SELECTION_API).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
FlaggedApiLiteral: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_SUCCESS:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS).
-
-
InvalidNullabilityOverride: android.app.Notification.TvExtender#extend(android.app.Notification.Builder) parameter #0:
Invalid nullability on parameter `builder` in method `extend`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.media.midi.MidiUmpDeviceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-
-
KotlinOperator: android.graphics.Matrix44#get(int, int):
Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
KotlinOperator: android.graphics.Matrix44#set(int, int, float):
Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
-
-
MissingGetterMatchingBuilder: android.os.RemoteCallbackList.Builder#setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>):
android.os.RemoteCallbackList does not declare a `getInterfaceDiedCallback()` method matching method android.os.RemoteCallbackList.Builder.setInterfaceDiedCallback(android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>)
RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
@@ -1117,14 +1110,10 @@ RequiresPermission: android.webkit.WebSettings#setBlockNetworkLoads(boolean):
Method 'setBlockNetworkLoads' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission
-
-
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
Documentation mentions 'TODO'
-
-
UnflaggedApi: android.R.color#on_surface_disabled_material:
New API must be flagged with @FlaggedApi: field android.R.color.on_surface_disabled_material
UnflaggedApi: android.R.color#outline_disabled_material:
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 8a10979eb3c9..578b7b6a63fa 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -541,6 +541,22 @@ public class AccessibilityNodeInfo implements Parcelable {
"ACTION_ARGUMENT_HTML_ELEMENT_STRING";
/**
+ * Argument for specifying the extended selection.
+ *
+ * <p><strong>Type:</strong> {@link AccessibilityNodeInfo.Selection}<br>
+ * <strong>Actions:</strong>
+ *
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final String ACTION_ARGUMENT_SELECTION_PARCELABLE =
+ "android.view.accessibility.action.ARGUMENT_SELECTION_PARCELABLE";
+
+ /**
* Argument for whether when moving at granularity to extend the selection
* or to move it otherwise.
* <p>
@@ -1146,6 +1162,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mConnectionId = UNDEFINED_CONNECTION_ID;
+ private Selection mSelection;
+
private RangeInfo mRangeInfo;
private CollectionInfo mCollectionInfo;
private CollectionItemInfo mCollectionItemInfo;
@@ -2660,6 +2678,56 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Sets the extended selection, which is a representation of selection that spans multiple nodes
+ * that exist within the subtree of the node defining selection.
+ *
+ * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+ * should be constructed with {@code this} node or a descendant of it.
+ *
+ * <p><b>Note:</b> {@link AccessibilityNodeInfo#setFocusable} and {@link
+ * AccessibilityNodeInfo#setFocused} should both be called with {@code true} before setting the
+ * selection in order to make {@code this} node a candidate to contain a selection.
+ *
+ * <p><b>Note:</b> Cannot be called from an AccessibilityService. This class is made immutable
+ * before being delivered to an AccessibilityService.
+ *
+ * @param selection The extended selection within the node's subtree, or {@code null} if no
+ * selection exists.
+ * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION
+ * @throws IllegalStateException If called from an AccessibilityService
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public void setSelection(@Nullable Selection selection) {
+ enforceNotSealed();
+ mSelection = selection;
+ }
+
+ /**
+ * Gets the extended selection, which is a representation of selection that spans multiple nodes
+ * that exist within the subtree of the node defining selection.
+ *
+ * <p><b>Note:</b> The start and end {@link SelectionPosition} of the provided {@link Selection}
+ * should be constructed with {@code this} node or a descendant of it.
+ *
+ * <p><b>Note:</b> In order for a node to be a candidate to contain a selection, {@link
+ * AccessibilityNodeInfo#isFocusable()} ()} and {@link AccessibilityNodeInfo#isFocused()} should
+ * both be return with {@code true}.
+ *
+ * @return The extended selection within the node's subtree, or {@code null} if no selection
+ * exists.
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public @Nullable Selection getSelection() {
+ if (mSelection != null) {
+ mSelection.getStart().setWindowId(mWindowId);
+ mSelection.getStart().setConnectionId(mConnectionId);
+ mSelection.getEnd().setWindowId(mWindowId);
+ mSelection.getEnd().setConnectionId(mConnectionId);
+ }
+ return mSelection;
+ }
+
+ /**
* Gets whether this node is visible to the user.
* <p>
* Between {@link Build.VERSION_CODES#JELLY_BEAN API 16} and
@@ -4168,6 +4236,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* there is no text selection and no cursor.
*/
public int getTextSelectionStart() {
+ if (Flags.a11ySelectionApi()) {
+ Selection current = getSelection();
+ if ((current != null)
+ && current.getStart().usesNode(this)
+ && current.getEnd().usesNode(this)) {
+ return current.getStart().getOffset();
+ }
+ return UNDEFINED_SELECTION_INDEX;
+ }
return mTextSelectionStart;
}
@@ -4183,6 +4260,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* there is no text selection and no cursor.
*/
public int getTextSelectionEnd() {
+ if (Flags.a11ySelectionApi()) {
+ Selection current = getSelection();
+ if ((current != null)
+ && current.getStart().usesNode(this)
+ && current.getEnd().usesNode(this)) {
+ return current.getEnd().getOffset();
+ }
+ return UNDEFINED_SELECTION_INDEX;
+ }
return mTextSelectionEnd;
}
@@ -4201,6 +4287,13 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public void setTextSelection(int start, int end) {
enforceNotSealed();
+ if (Flags.a11ySelectionApi()) {
+ Selection selection =
+ new Selection(
+ new SelectionPosition(this, start), new SelectionPosition(this, end));
+ setSelection(selection);
+ return;
+ }
mTextSelectionStart = start;
mTextSelectionEnd = end;
}
@@ -4875,6 +4968,10 @@ public class AccessibilityNodeInfo implements Parcelable {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mSelection, DEFAULT.mSelection)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (mChecked != DEFAULT.mChecked) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -5055,6 +5152,9 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mLeashedParentNodeId);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mSelection.writeToParcel(parcel, flags);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeInt(mChecked);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5172,6 +5272,17 @@ public class AccessibilityNodeInfo implements Parcelable {
ExtraRenderingInfo ti = other.mExtraRenderingInfo;
mExtraRenderingInfo = (ti == null) ? null
: new ExtraRenderingInfo(ti);
+
+ if (Flags.a11ySelectionApi()) {
+ if (other.getSelection() != null) {
+ SelectionPosition sps = other.getSelection().getStart();
+ SelectionPosition spe = other.getSelection().getEnd();
+ mSelection =
+ new Selection(
+ new SelectionPosition(sps.mSourceNodeId, sps.getOffset()),
+ new SelectionPosition(spe.mSourceNodeId, spe.getOffset()));
+ }
+ }
}
/**
@@ -5344,6 +5455,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mLeashedParentNodeId = parcel.readLong();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mSelection = Selection.CREATOR.createFromParcel(parcel);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
mChecked = parcel.readInt();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -5495,6 +5609,9 @@ public class AccessibilityNodeInfo implements Parcelable {
if (action == R.id.accessibilityActionScrollInDirection) {
return "ACTION_SCROLL_IN_DIRECTION";
}
+ if (action == R.id.accessibilityActionSetExtendedSelection) {
+ return "ACTION_SET_EXTENDED_SELECTION";
+ }
return "ACTION_UNKNOWN";
}
}
@@ -5696,6 +5813,271 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * A class which defines either the start or end of a selection that can span across multiple
+ * AccessibilityNodeInfo objects.
+ *
+ * @see AccessibilityNodeInfo.Selection
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final class SelectionPosition implements Parcelable {
+
+ private final int mOffset;
+ private final long mSourceNodeId;
+ private int mConnectionId;
+ private int mWindowId;
+
+ /**
+ * Instantiates a new SelectionPosition.
+ *
+ * @param node The {@link AccessibilityNodeInfo} for the node of this selection.
+ * @param offset The offset for a {@link SelectionPosition} within {@code view}'s text
+ * content, which should be a value between 0 and the length of {@code view}'s text.
+ */
+ public SelectionPosition(@NonNull AccessibilityNodeInfo node, int offset) {
+ this(node.mSourceNodeId, offset);
+ }
+
+ /**
+ * Instantiates a new SelectionPosition.
+ *
+ * @param view The {@link View} containing the virtual descendant associated with the
+ * selection position.
+ * @param offset The offset for a selection position within {@code view}'s text content,
+ * which should be a value between 0 and the length of {@code view}'s text.
+ */
+ public SelectionPosition(@NonNull View view, int offset) {
+ this(
+ makeNodeId(
+ view.getAccessibilityViewId(), AccessibilityNodeProvider.HOST_VIEW_ID),
+ offset);
+ }
+
+ /**
+ * Instantiates a new {@link SelectionPosition}.
+ *
+ * @param view The view whose virtual descendant is associated with the selection position.
+ * @param virtualDescendantId The ID of the virtual descendant within {@code view}'s virtual
+ * subtree that contains the selection position.
+ * @param offset The offset for a selection position within the virtual descendant's text
+ * content, which should be a value between 0 and the length of the descendant's text.
+ * @see AccessibilityNodeProvider
+ */
+ public SelectionPosition(@NonNull View view, int virtualDescendantId, int offset) {
+ this(makeNodeId(view.getAccessibilityViewId(), virtualDescendantId), offset);
+ }
+
+ private SelectionPosition(long sourceNodeId, int offset) {
+ mOffset = offset;
+ mSourceNodeId = sourceNodeId;
+ }
+
+ private SelectionPosition(Parcel in) {
+ mOffset = in.readInt();
+ mSourceNodeId = in.readLong();
+ }
+
+ private void setWindowId(int windowId) {
+ mWindowId = windowId;
+ }
+
+ private void setConnectionId(int connectionId) {
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Gets the node for {@code this} {@link SelectionPosition}
+ * <br>
+ * <strong>Note:</strong> This api can only be called from {@link AccessibilityService}.
+ *
+ * @return The node associated with {@code this} {@link SelectionPosition}
+ */
+ public @Nullable AccessibilityNodeInfo getNode() {
+ return getNodeForAccessibilityId(mConnectionId, mWindowId, mSourceNodeId);
+ }
+
+ /**
+ * Gets the offset for {@code this} {@link SelectionPosition}.
+ *
+ * @return A value from 0 to the length of {@link #getNode()}'s content representing the
+ * offset of the {@link SelectionPosition}
+ */
+ public int getOffset() {
+ return mOffset;
+ }
+
+ private boolean usesNode(@NonNull AccessibilityNodeInfo node) {
+ return this.mSourceNodeId == node.mSourceNodeId
+ && this.mConnectionId == node.mConnectionId
+ && this.mWindowId == node.mWindowId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null) {
+ return false;
+ }
+
+ if (other == this) {
+ return true;
+ }
+
+ if (getClass() != other.getClass()) {
+ return false;
+ }
+
+ SelectionPosition rhs = (SelectionPosition) other;
+ if (getOffset() != rhs.getOffset()) {
+ return false;
+ }
+
+ return mSourceNodeId == rhs.mSourceNodeId;
+ }
+
+ @Override
+ public int hashCode() {
+ final long prime = 877;
+ long result = 1;
+
+ if (mOffset != 0) {
+ result *= mOffset;
+ }
+
+ if (mSourceNodeId != UNDEFINED_NODE_ID) {
+ result *= mSourceNodeId;
+ }
+
+ return Long.hashCode(result * prime);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOffset);
+ dest.writeLong(mSourceNodeId);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SelectionPosition> CREATOR =
+ new Creator<SelectionPosition>() {
+ @Override
+ public SelectionPosition createFromParcel(Parcel in) {
+ return new SelectionPosition(in);
+ }
+
+ @Override
+ public SelectionPosition[] newArray(int size) {
+ return new SelectionPosition[size];
+ }
+ };
+ }
+
+ /**
+ * Represents a selection of content that may extend across more than one {@link
+ * AccessibilityNodeInfo} instance.
+ *
+ * @see AccessibilityNodeInfo.SelectionPosition
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ public static final class Selection implements Parcelable {
+
+ private final SelectionPosition mStart;
+ private final SelectionPosition mEnd;
+
+ /**
+ * Instantiates a new Selection.
+ *
+ * @param start The start of the extended selection.
+ * @param end The end of the extended selection.
+ */
+ public Selection(@NonNull SelectionPosition start, @NonNull SelectionPosition end) {
+ this.mStart = start;
+ this.mEnd = end;
+ }
+
+ private Selection(Parcel in) {
+ mStart = SelectionPosition.CREATOR.createFromParcel(in);
+ mEnd = SelectionPosition.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * @return The start of the extended selection.
+ */
+ public @NonNull SelectionPosition getStart() {
+ return mStart;
+ }
+
+ /**
+ * @return The end of the extended selection.
+ */
+ public @NonNull SelectionPosition getEnd() {
+ return mEnd;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (obj == this) {
+ return true;
+ }
+
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ Selection rhs = (Selection) obj;
+ return getStart().equals(rhs.getStart()) && getEnd().equals(rhs.getEnd());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 17;
+ return prime * getStart().hashCode() * getEnd().hashCode();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mStart.writeToParcel(dest, flags);
+ mEnd.writeToParcel(dest, flags);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @see android.os.Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<Selection> CREATOR =
+ new Creator<Selection>() {
+ @Override
+ public Selection createFromParcel(Parcel in) {
+ return new Selection(in);
+ }
+
+ @Override
+ public Selection[] newArray(int size) {
+ return new Selection[size];
+ }
+ };
+ }
+
+ /**
* A class defining an action that can be performed on an {@link AccessibilityNodeInfo}.
* Each action has a unique id that is mandatory and optional data.
* <p>
@@ -6419,6 +6801,29 @@ public class AccessibilityNodeInfo implements Parcelable {
@NonNull public static final AccessibilityAction ACTION_SHOW_TEXT_SUGGESTIONS =
new AccessibilityAction(R.id.accessibilityActionShowTextSuggestions);
+ /**
+ * Action to set the extended selection. Performing this action with no arguments clears the
+ * selection.
+ *
+ * <p><strong>Arguments:</strong> {@link
+ * AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE}<br>
+ * <strong>Example:</strong> <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * Selection selection = new Selection(null, null);
+ * arguments.setParcelable(
+ * AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_PARCELABLE, selection);
+ * info.performAction(
+ * AccessibilityAction.ACTION_SET_EXTENDED_SELECTION.getId(), arguments);
+ * </pre></code>
+ *
+ * @see AccessibilityNodeInfo#ACTION_ARGUMENT_SELECTION_PARCELABLE
+ */
+ @FlaggedApi(Flags.FLAG_A11Y_SELECTION_API)
+ @NonNull
+ public static final AccessibilityAction ACTION_SET_EXTENDED_SELECTION =
+ new AccessibilityAction(R.id.accessibilityActionSetExtendedSelection);
+
private final int mActionId;
private final CharSequence mLabel;
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 3b39a65b6795..5b5ef0954098 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -157,6 +157,9 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
<item type="id" name="accessibilityActionContextClick" />
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_EXTENDED_SELECTION}. -->
+ <item type="id" name="accessibilityActionSetExtendedSelection" />
+
<item type="id" name="remote_input_tag" />
<item type="id" name="pending_intent_tag" />
<item type="id" name="remote_checked_change_listener_tag" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 2d411d0268b3..e3137e2e77e3 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -129,6 +129,8 @@
<staging-public-group type="id" first-id="0x01b20000">
<!-- @FlaggedApi(android.appwidget.flags.Flags.FLAG_ENGAGEMENT_METRICS) -->
<public name="remoteViewsMetricsId"/>
+ <!-- @FlaggedApi("android.view.accessibility.a11y_selection_api") -->
+ <public name="accessibilityActionSetExtendedSelection"/>
</staging-public-group>
<staging-public-group type="style" first-id="0x01b10000">
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 3b0eab4661ff..cc5c6aff4232 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 @@ public class AccessibilityNodeInfoTest {
// 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 = 47;
+ private static final int NUM_MARSHALLED_PROPERTIES = 48;
/**
* The number of properties that are purposely not marshalled