Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 540a3a93 authored by Yinglei Wang's avatar Yinglei Wang Committed by Yinglei Wang
Browse files

Add API for multiple labels in AccesibilityNodeInfo#labeledby

Design doc: https://docs.google.com/document/d/1x95lWKg5QMBLXNCXQ2XaF3p9cggC2NcPOoPfSzICmrA/edit?usp=sharing&resourcekey=0-ThSUOEc57Ugy4B_yeiW7Sw

Test: CTS test added
Flag: android.view.accessibility.support_multiple_labeledby

Bug: 333780959
Change-Id: Id251f88f84ef9a2d26fe15345f70e659de212c67
parent c51b65b7
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -54850,6 +54850,8 @@ package android.view.accessibility {
    method @Deprecated public void addAction(int);
    method public void addChild(android.view.View);
    method public void addChild(android.view.View, int);
    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
    method public boolean canOpenPopup();
    method public int describeContents();
    method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54878,6 +54880,7 @@ package android.view.accessibility {
    method public int getInputType();
    method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
    method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
    method public int getLiveRegion();
    method public int getMaxTextLength();
    method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -54938,6 +54941,8 @@ package android.view.accessibility {
    method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
    method public boolean removeChild(android.view.View);
    method public boolean removeChild(android.view.View, int);
    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
    method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
    method public void setAccessibilityDataSensitive(boolean);
    method public void setAccessibilityFocused(boolean);
    method public void setAvailableExtraData(java.util.List<java.lang.String>);
+173 −0
Original line number Diff line number Diff line
@@ -982,6 +982,7 @@ public class AccessibilityNodeInfo implements Parcelable {
    private long mParentNodeId = UNDEFINED_NODE_ID;
    private long mLabelForId = UNDEFINED_NODE_ID;
    private long mLabeledById = UNDEFINED_NODE_ID;
    private LongArray mLabeledByIds;
    private long mTraversalBefore = UNDEFINED_NODE_ID;
    private long mTraversalAfter = UNDEFINED_NODE_ID;

@@ -3598,6 +3599,131 @@ public class AccessibilityNodeInfo implements Parcelable {
        return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabelForId);
    }

    /**
     * Adds the view which serves as the label of the view represented by
     * this info for accessibility purposes. When more than one labels are
     * added, the content from each label is combined in the order that
     * they are added.
     * <p>
     * If visible text can be used to describe or give meaning to this UI,
     * this method is preferred. For example, a TextView before an EditText
     * in the UI usually specifies what information is contained in the
     * EditText. Hence, the EditText is labelled by the TextView.
     * </p>
     *
     * @param label A view that labels this node's source.
     */
    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
    public void addLabeledBy(@NonNull View label) {
        addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
    }

    /**
     * Adds the view which serves as the label of the view represented by
     * this info for accessibility purposes. If <code>virtualDescendantId</code>
     * is {@link View#NO_ID} the root is set as the label. When more than one
     * labels are added, the content from each label is combined in the order
     * that they are added.
     * <p>
     * A virtual descendant is an imaginary View that is reported as a part of the view
     * hierarchy for accessibility purposes. This enables custom views that draw complex
     * content to report themselves as a tree of virtual views, thus conveying their
     * logical structure.
     * </p>
     * <p>
     * If visible text can be used to describe or give meaning to this UI,
     * this method is preferred. For example, a TextView before an EditText
     * in the UI usually specifies what information is contained in the
     * EditText. Hence, the EditText is labelled by the TextView.
     * </p>
     * <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 root A root whose virtual descendant labels this node's source.
     * @param virtualDescendantId The id of the virtual descendant.
     */
    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
    public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
        enforceNotSealed();
        Preconditions.checkNotNull(root, "%s must not be null", root);
        if (mLabeledByIds == null) {
            mLabeledByIds = new LongArray();
        }
        mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
        mLabeledByIds.add(mLabeledById);
    }

    /**
     * Gets the list of node infos which serve as the labels of the view represented by
     * this info for accessibility purposes.
     *
     * @return The list of labels in the order that they were added.
     */
    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
    public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
        enforceSealed();
        List<AccessibilityNodeInfo> labels = new ArrayList<>();
        if (mLabeledByIds == null) {
            return labels;
        }
        for (int i = 0; i < mLabeledByIds.size(); i++) {
            labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
        }
        return labels;
    }

    /**
     * Removes a label. If the label was not previously added to the node,
     * calling this method has no effect.
     * <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 label The node which serves as this node's label.
     * @return true if the label was present
     * @see #addLabeledBy(View)
     */
    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
    public boolean removeLabeledBy(@NonNull View label) {
        return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
    }

    /**
     * Removes a virtual label which is a descendant of the given
     * <code>root</code>. If the label was not previously added to the node,
     * calling this method has no effect.
     *
     * @param root The root of the virtual subtree.
     * @param virtualDescendantId The id of the virtual node which serves as this node's label.
     * @return true if the label was present
     * @see #addLabeledBy(View, int)
     */
    @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
    public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
        enforceNotSealed();
        final LongArray labeledByIds = mLabeledByIds;
        if (labeledByIds == null) {
            return false;
        }
        final int rootAccessibilityViewId =
                (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
        final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
        if (mLabeledById == labeledById) {
            mLabeledById = UNDEFINED_NODE_ID;
        }
        final int index = labeledByIds.indexOf(labeledById);
        if (index < 0) {
            return false;
        }
        labeledByIds.remove(index);
        return true;
    }

    /**
     * Sets the view which serves as the label of the view represented by
     * this info for accessibility purposes.
@@ -3631,7 +3757,17 @@ public class AccessibilityNodeInfo implements Parcelable {
        enforceNotSealed();
        final int rootAccessibilityViewId = (root != null)
                ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
        if (Flags.supportMultipleLabeledby()) {
            if (mLabeledByIds == null) {
                mLabeledByIds = new LongArray();
            } else {
                mLabeledByIds.clear();
            }
        }
        mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
        if (Flags.supportMultipleLabeledby()) {
            mLabeledByIds.add(mLabeledById);
        }
    }

    /**
@@ -4242,6 +4378,12 @@ public class AccessibilityNodeInfo implements Parcelable {
        fieldIndex++;
        if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
        fieldIndex++;
        if (Flags.supportMultipleLabeledby()) {
            if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
                nonDefaultFields |= bitAt(fieldIndex);
            }
            fieldIndex++;
        }
        if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
        fieldIndex++;
        if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4383,6 +4525,20 @@ public class AccessibilityNodeInfo implements Parcelable {
        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
        if (Flags.supportMultipleLabeledby()) {
            if (isBitSet(nonDefaultFields, fieldIndex++)) {
                final LongArray labeledByIds = mLabeledByIds;
                if (labeledByIds == null) {
                    parcel.writeInt(0);
                } else {
                    final int labeledByIdsSize = labeledByIds.size();
                    parcel.writeInt(labeledByIdsSize);
                    for (int i = 0; i < labeledByIdsSize; i++) {
                        parcel.writeLong(labeledByIds.get(i));
                    }
                }
            }
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4550,6 +4706,9 @@ public class AccessibilityNodeInfo implements Parcelable {
        mParentNodeId = other.mParentNodeId;
        mLabelForId = other.mLabelForId;
        mLabeledById = other.mLabeledById;
        if (Flags.supportMultipleLabeledby()) {
            mLabeledByIds = other.mLabeledByIds;
        }
        mTraversalBefore = other.mTraversalBefore;
        mTraversalAfter = other.mTraversalAfter;
        mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4656,6 +4815,20 @@ public class AccessibilityNodeInfo implements Parcelable {
        if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
        if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
        if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
        if (Flags.supportMultipleLabeledby()) {
            if (isBitSet(nonDefaultFields, fieldIndex++)) {
                final int labeledByIdsSize = parcel.readInt();
                if (labeledByIdsSize <= 0) {
                    mLabeledByIds = null;
                } else {
                    mLabeledByIds = new LongArray(labeledByIdsSize);
                    for (int i = 0; i < labeledByIdsSize; i++) {
                        final long labeledById = parcel.readLong();
                        mLabeledByIds.add(labeledById);
                    }
                }
            }
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
        if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+7 −0
Original line number Diff line number Diff line
@@ -162,6 +162,13 @@ flag {
    }
}

flag {
    name: "support_multiple_labeledby"
    namespace: "accessibility"
    description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
    bug: "333780959"
}

flag {
    name: "support_system_pinch_zoom_opt_out_apis"
    namespace: "accessibility"
+1 −1
Original line number Diff line number Diff line
@@ -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 = 43;
    private static final int NUM_MARSHALLED_PROPERTIES = 44;

    /**
     * The number of properties that are purposely not marshalled