diff options
| author | 2025-02-11 10:14:08 -0800 | |
|---|---|---|
| committer | 2025-02-11 10:14:08 -0800 | |
| commit | 44a905df3323798545866ddefa1b8a222c5cb9b0 (patch) | |
| tree | 5893f4aa4bb7813af775e59511aa8042a334f40b | |
| parent | 26ceeeb8fb5a303ea830da9034464e25d315b8ba (diff) | |
| parent | 7f15b86ae5aec19402d9b06058d398e244f4ac30 (diff) | |
Merge "[Selection API] Add Selection API implementation" into main
| -rw-r--r-- | core/api/current.txt | 25 | ||||
| -rw-r--r-- | core/api/lint-baseline.txt | 17 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityNodeInfo.java | 405 | ||||
| -rw-r--r-- | core/res/res/values/ids.xml | 3 | ||||
| -rw-r--r-- | core/res/res/values/public-staging.xml | 2 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java | 2 |
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 |