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

Commit 7fd8e937 authored by Jackal Guo's avatar Jackal Guo Committed by Android (Google) Code Review
Browse files

Merge "Support accessibility on embedded hierarchies (1/n)"

parents cac0014a 79b182e7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4502,6 +4502,8 @@ package android.view.accessibility {
  }

  public class AccessibilityNodeInfo implements android.os.Parcelable {
    method public void addChild(@NonNull android.os.IBinder);
    method public void setLeashedParent(@Nullable android.os.IBinder, int);
    method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
    method public void writeToParcelNoRecycle(android.os.Parcel, int);
  }
+2 −0
Original line number Diff line number Diff line
@@ -102,4 +102,6 @@ interface IAccessibilityServiceConnection {
    boolean isFingerprintGestureDetectionAvailable();

    IBinder getOverlayWindowToken(int displayid);

    int getWindowIdForLeashToken(IBinder token);
}
+27 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
import android.view.accessibility.AccessibilityNodeInfo;

import com.android.internal.view.SurfaceCallbackHelper;

@@ -201,6 +202,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
    private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
    private int mParentSurfaceGenerationId;

    // The token of embedded windowless view hierarchy.
    private IBinder mEmbeddedViewHierarchy;

    public SurfaceView(Context context) {
        this(context, null);
    }
@@ -1531,4 +1535,27 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
        if (viewRoot == null) return;
        viewRoot.setUseBLASTSyncTransaction();
    }

    /**
     * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
     * hierarchy.
     *
     * @param token IBinder token.
     * @hide
     */
    public void setEmbeddedViewHierarchy(IBinder token) {
        mEmbeddedViewHierarchy = token;
    }

    /** @hide */
    @Override
    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfoInternal(info);
        if (mEmbeddedViewHierarchy == null) {
            return;
        }
        // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
        // leashed child would return the root node in the embedded hierarchy
        info.addChild(mEmbeddedViewHierarchy);
    }
}
+45 −0
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@
package android.view.accessibility;

import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -337,6 +340,48 @@ public final class AccessibilityInteractionClient
        return emptyWindows;
    }


    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
     * window id. This method is used to find the leashed node on the embedded view hierarchy.
     *
     * @param connectionId The id of a connection for interacting with the system.
     * @param leashToken The token of the embedded hierarchy.
     * @param accessibilityNodeId A unique view id or virtual descendant id from
     *     where to start the search. Use
     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
     *     to start from the root.
     * @param bypassCache Whether to bypass the cache while looking for the node.
     * @param prefetchFlags flags to guide prefetching.
     * @param arguments Optional action arguments.
     * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
     */
    public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
            int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
            boolean bypassCache, int prefetchFlags, Bundle arguments) {
        if (leashToken == null) {
            return null;
        }
        int windowId = -1;
        try {
            IAccessibilityServiceConnection connection = getConnection(connectionId);
            if (connection != null) {
                windowId = connection.getWindowIdForLeashToken(leashToken);
            } else {
                if (DEBUG) {
                    Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
                }
            }
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
        }
        if (windowId == -1) {
            return null;
        }
        return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
                accessibilityNodeId, bypassCache, prefetchFlags, arguments);
    }

    /**
     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
     *
+173 −1
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
@@ -109,6 +110,9 @@ public class AccessibilityNodeInfo implements Parcelable {
    /** @hide */
    public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;

    /** @hide */
    public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;

    /** @hide */
    public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);

@@ -116,6 +120,10 @@ public class AccessibilityNodeInfo implements Parcelable {
    public static final long ROOT_NODE_ID = makeNodeId(ROOT_ITEM_ID,
            AccessibilityNodeProvider.HOST_VIEW_ID);

    /** @hide */
    public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
            AccessibilityNodeProvider.HOST_VIEW_ID);

    /** @hide */
    public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;

@@ -788,6 +796,10 @@ public class AccessibilityNodeInfo implements Parcelable {

    private TouchDelegateInfo mTouchDelegateInfo;

    private IBinder mLeashedChild;
    private IBinder mLeashedParent;
    private long mLeashedParentNodeId = UNDEFINED_NODE_ID;

    /**
     * Creates a new {@link AccessibilityNodeInfo}.
     */
@@ -1039,7 +1051,12 @@ public class AccessibilityNodeInfo implements Parcelable {
            return null;
        }
        final long childId = mChildNodeIds.get(index);
        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
            return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
                    ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
        }

        return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
                childId, false, FLAG_PREFETCH_DESCENDANTS, null);
    }
@@ -1061,6 +1078,43 @@ public class AccessibilityNodeInfo implements Parcelable {
        addChildInternal(child, AccessibilityNodeProvider.HOST_VIEW_ID, true);
    }

    /**
     * Adds a view root from leashed content as a child. This method is used to embedded another
     * view hierarchy.
     * <p>
     * <strong>Note:</strong> Only one leashed child is permitted.
     * </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.
     * Note that a view cannot be made its own child.
     * </p>
     *
     * @param token The token to which a view root is added.
     *
     * @throws IllegalStateException If called from an AccessibilityService.
     * @hide
     */
    @TestApi
    public void addChild(@NonNull IBinder token) {
        enforceNotSealed();
        if (token == null) {
            return;
        }
        if (mChildNodeIds == null) {
            mChildNodeIds = new LongArray();
        }

        mLeashedChild = token;
        // Checking uniqueness.
        // Since only one leashed child is permitted, skip adding ID if the ID already exists.
        if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
            return;
        }
        mChildNodeIds.add(LEASHED_NODE_ID);
    }

    /**
     * Unchecked version of {@link #addChild(View)} that does not verify
     * uniqueness. For framework use only.
@@ -1089,6 +1143,38 @@ public class AccessibilityNodeInfo implements Parcelable {
        return removeChild(child, AccessibilityNodeProvider.HOST_VIEW_ID);
    }

    /**
     * Removes a leashed child. If the child 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 token The token of the leashed child
     * @return true if the child was present
     *
     * @throws IllegalStateException If called from an AccessibilityService.
     * @hide
     */
    public boolean removeChild(IBinder token) {
        enforceNotSealed();
        if (mChildNodeIds == null || mLeashedChild == null) {
            return false;
        }
        if (!mLeashedChild.equals(token)) {
            return false;
        }
        final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
        mLeashedChild = null;
        if (index < 0) {
            return false;
        }
        mChildNodeIds.remove(index);
        return true;
    }

    /**
     * Adds a virtual child which is a descendant of the given <code>root</code>.
     * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
@@ -1668,6 +1754,9 @@ public class AccessibilityNodeInfo implements Parcelable {
     */
    public AccessibilityNodeInfo getParent() {
        enforceSealed();
        if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
            return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
        }
        return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
    }

@@ -3256,6 +3345,40 @@ public class AccessibilityNodeInfo implements Parcelable {
        return mSourceNodeId;
    }

    /**
     * Sets the token and node id of the leashed parent.
     *
     * @param token The token.
     * @param viewId The accessibility view id.
     * @hide
     */
    @TestApi
    public void setLeashedParent(@Nullable IBinder token, int viewId) {
        enforceNotSealed();
        mLeashedParent = token;
        mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
    }

    /**
     * Gets the token of the leashed parent.
     *
     * @return The token.
     * @hide
     */
    public @Nullable IBinder getLeashedParent() {
        return mLeashedParent;
    }

    /**
     * Gets the node id of the leashed parent.
     *
     * @return The accessibility node id.
     * @hide
     */
    public long getLeashedParentNodeId() {
        return mLeashedParentNodeId;
    }

    /**
     * Sets if this instance is sealed.
     *
@@ -3559,6 +3682,18 @@ public class AccessibilityNodeInfo implements Parcelable {
        if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
            nonDefaultFields |= bitAt(fieldIndex);
        }
        fieldIndex++;
        if (mLeashedChild != DEFAULT.mLeashedChild) {
            nonDefaultFields |= bitAt(fieldIndex);
        }
        fieldIndex++;
        if (mLeashedParent != DEFAULT.mLeashedParent) {
            nonDefaultFields |= bitAt(fieldIndex);
        }
        fieldIndex++;
        if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
            nonDefaultFields |= bitAt(fieldIndex);
        }
        int totalFields = fieldIndex;
        parcel.writeLong(nonDefaultFields);

@@ -3685,6 +3820,16 @@ public class AccessibilityNodeInfo implements Parcelable {
            mTouchDelegateInfo.writeToParcel(parcel, flags);
        }

        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            parcel.writeStrongBinder(mLeashedChild);
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            parcel.writeStrongBinder(mLeashedParent);
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            parcel.writeLong(mLeashedParentNodeId);
        }

        if (DEBUG) {
            fieldIndex--;
            if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@ public class AccessibilityNodeInfo implements Parcelable {
        final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
        mTouchDelegateInfo = (otherInfo != null)
                ? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;

        mLeashedChild = other.mLeashedChild;
        mLeashedParent = other.mLeashedParent;
        mLeashedParentNodeId = other.mLeashedParentNodeId;
    }

    private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@ public class AccessibilityNodeInfo implements Parcelable {
            mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
        }

        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            mLeashedChild = parcel.readStrongBinder();
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            mLeashedParent = parcel.readStrongBinder();
        }
        if (isBitSet(nonDefaultFields, fieldIndex++)) {
            mLeashedParentNodeId = parcel.readLong();
        }

        mSealed = sealed;
    }

@@ -4200,6 +4359,19 @@ public class AccessibilityNodeInfo implements Parcelable {
                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
    }

    private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
            IBinder leashToken, long accessibilityId) {
        if (!((leashToken != null)
                && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
                && (connectionId != UNDEFINED_CONNECTION_ID))) {
            return null;
        }
        AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
        return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
                leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
                        | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
    }

    /** @hide */
    public static String idToString(long accessibilityId) {
        int accessibilityViewId = getAccessibilityViewId(accessibilityId);
Loading