diff options
| author | 2020-01-15 05:40:36 +0000 | |
|---|---|---|
| committer | 2020-01-15 05:40:36 +0000 | |
| commit | 7fd8e937bf66464f3e398c31ce29cd0cf9689abd (patch) | |
| tree | fb6d5f92f9374120685b1ce769010b6017031c19 | |
| parent | cac0014a95c124185c0b2450e2ca5bcc3e31ee0b (diff) | |
| parent | 79b182e75250bc378cbe6f83d0d6043f5e2064d9 (diff) | |
Merge "Support accessibility on embedded hierarchies (1/n)"
9 files changed, 269 insertions, 2 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 1e6b249a9f14..10028f20aad3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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); } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 656f87fe435b..932d0a4d65bd 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -102,4 +102,6 @@ interface IAccessibilityServiceConnection { boolean isFingerprintGestureDetectionAvailable(); IBinder getOverlayWindowToken(int displayid); + + int getWindowIdForLeashToken(IBinder token); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 17825444a524..0de1a4f038ff 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -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); + } } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 914ff1871d69..b4c87953567b 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -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. * diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 92aa7d523da0..184f3302ae8d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -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; @@ -110,6 +111,9 @@ public class AccessibilityNodeInfo implements Parcelable { 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); /** @hide */ @@ -117,6 +121,10 @@ public class AccessibilityNodeInfo implements Parcelable { 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; /** @hide */ @@ -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); } @@ -1062,6 +1079,43 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * 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. * @@ -1090,6 +1144,38 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * 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 * is added as a child. @@ -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); } @@ -3257,6 +3346,40 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * 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. * * @param sealed Whether 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); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 1bd52af09a4d..ade1e0de7102 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 = 35; + private static final int NUM_MARSHALLED_PROPERTIES = 38; /** * The number of properties that are purposely not marshalled diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 93f4b5143c57..836cb95992ff 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -143,4 +143,8 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public IBinder getOverlayWindowToken(int displayId) { return null; } + + public int getWindowIdForLeashToken(IBinder token) { + return -1; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 5eaa80a5143b..016028604902 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1018,6 +1018,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + /** + * Gets windowId of given token. + * + * @param token The token + * @return window id + */ + @Override + public int getWindowIdForLeashToken(@NonNull IBinder token) { + synchronized (mLock) { + // TODO: Add a method to lookup window ID by given leash token. + return -1; + } + } + public void resetLocked() { mSystemSupport.getKeyEventDispatcher().flush(this); try { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 203210998c48..0151a52c8a6b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -426,6 +426,7 @@ public class AccessibilitySecurityPolicy { return false; } } + // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy. if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) { return true; } |