diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java new file mode 100644 index 0000000000000..cc1e5010edc77 --- /dev/null +++ b/core/java/android/view/AccessibilityEmbeddedConnection.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.os.IBinder; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityEmbeddedConnection; + +import java.lang.ref.WeakReference; + +/** + * This class is an interface this ViewRootImpl provides to the host view to the latter + * can interact with the view hierarchy in SurfaceControlViewHost. + * + * @hide + */ +final class AccessibilityEmbeddedConnection extends IAccessibilityEmbeddedConnection.Stub { + private final WeakReference mViewRootImpl; + + AccessibilityEmbeddedConnection(ViewRootImpl viewRootImpl) { + mViewRootImpl = new WeakReference<>(viewRootImpl); + } + + @Override + public @Nullable IBinder associateEmbeddedHierarchy(@NonNull IBinder host, int hostViewId) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = host; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = hostViewId; + if (accessibilityManager.isEnabled()) { + accessibilityManager.associateEmbeddedHierarchy(host, viewRootImpl.mLeashToken); + } + return viewRootImpl.mLeashToken; + } + return null; + } + + @Override + public void disassociateEmbeddedHierarchy() { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = null; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID; + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0); + if (accessibilityManager.isEnabled()) { + accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken); + } + } + } + + @Override + public void setScreenMatrix(float[] matrixValues) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + // TODO(b/148821260): Implement the rest of matrix values. + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set( + (int) matrixValues[Matrix.MTRANS_X], (int) matrixValues[Matrix.MTRANS_Y]); + } + } +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 203b08765663e..3ca84c1eb18d0 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -855,6 +855,36 @@ public final class AccessibilityInteractionController { return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0); } + private void associateLeashedParentIfNeeded(List infos) { + if (infos == null || shouldBypassAssociateLeashedParent()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + associateLeashedParentIfNeeded(info); + } + } + + private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { + if (info == null || shouldBypassAssociateLeashedParent()) { + return; + } + // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id + // with root view. + if (mViewRootImpl.mView.getAccessibilityViewId() + != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) { + return; + } + info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken, + mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId); + } + + private boolean shouldBypassAssociateLeashedParent() { + return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null + && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID); + } + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec) { if (info == null) { @@ -914,6 +944,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(infos); adjustBoundsInScreenIfNeeded(infos); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. @@ -935,6 +966,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(info); adjustBoundsInScreenIfNeeded(info); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a6f8fad817e15..f99c965845859 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28988,6 +28988,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener; + /** + * The leash token of this view's parent when it's in an embedded hierarchy that is + * re-parented to another window. + */ + IBinder mLeashedParentToken; + + /** + * The accessibility view id of this view's parent when it's in an embedded + * hierarchy that is re-parented to another window. + */ + int mLeashedParentAccessibilityViewId; + /** * Creates a new set of attachment information with the specified * events handler and thread. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ebfe66f7e7b71..fa4fafaf3b265 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -135,6 +135,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; @@ -355,6 +356,8 @@ public final class ViewRootImpl implements ViewParent, final W mWindow; + final IBinder mLeashToken; + final int mTargetSdkVersion; int mSeq; @@ -652,6 +655,8 @@ public final class ViewRootImpl implements ViewParent, private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); + private IAccessibilityEmbeddedConnection mEmbeddedConnection; + static final class SystemUiVisibilityInfo { int seq; int globalVisibility; @@ -685,6 +690,7 @@ public final class ViewRootImpl implements ViewParent, mVisRect = new Rect(); mWinFrame = new Rect(); mWindow = new W(this); + mLeashToken = new Binder(); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; mViewVisibility = View.GONE; mTransparentRegion = new Region(); @@ -9157,6 +9163,10 @@ public final class ViewRootImpl implements ViewParent, focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } } + if (mAttachInfo.mLeashedParentToken != null) { + mAccessibilityManager.associateEmbeddedHierarchy( + mAttachInfo.mLeashedParentToken, mLeashToken); + } } else { ensureNoConnection(); mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget(); @@ -9169,6 +9179,7 @@ public final class ViewRootImpl implements ViewParent, if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mLeashToken, mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } @@ -9355,6 +9366,17 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Gets an accessibility embedded connection interface for this ViewRootImpl. + * @hide + */ + public IAccessibilityEmbeddedConnection getEmbeddedConnection() { + if (mEmbeddedConnection == null) { + mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this); + } + return mEmbeddedConnection; + } + private class SendWindowContentChangedAccessibilityEvent implements Runnable { private int mChangeTypes = 0; diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 02b098b279747..dc87453bd867a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1085,6 +1085,50 @@ public final class AccessibilityManager { } } + /** + * Associate the connection between the host View and the embedded SurfaceControlViewHost. + * + * @hide + */ + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.associateEmbeddedHierarchy(host, embedded); + } catch (RemoteException e) { + return; + } + } + + /** + * Disassociate the connection between the host View and the embedded SurfaceControlViewHost. + * The given token could be either from host side or embedded side. + * + * @hide + */ + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + if (token == null) { + return; + } + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.disassociateEmbeddedHierarchy(token); + } catch (RemoteException e) { + return; + } + } + /** * Sets the current state and notifies listeners, if necessary. * @@ -1147,11 +1191,12 @@ public final class AccessibilityManager { /** * Adds an accessibility interaction connection interface for a given window. * @param windowToken The window token to which a connection is added. + * @param leashToken The leash token to which a connection is added. * @param connection The connection. * * @hide */ - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, String packageName, IAccessibilityInteractionConnection connection) { final IAccessibilityManager service; final int userId; @@ -1163,8 +1208,8 @@ public final class AccessibilityManager { userId = mUserId; } try { - return service.addAccessibilityInteractionConnection(windowToken, connection, - packageName, userId); + return service.addAccessibilityInteractionConnection(windowToken, leashToken, + connection, packageName, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl new file mode 100644 index 0000000000000..707099ef09f40 --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +/** + * Interface used by host View to talk to the view root of the embedded SurfaceControlViewHost + * that actually implements the functionality. + * + * @hide + */ +interface IAccessibilityEmbeddedConnection { + + IBinder associateEmbeddedHierarchy(IBinder hostToken, int sourceId); + + void disassociateEmbeddedHierarchy(); + + oneway void setScreenMatrix(in float[] matrixValues); +} diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 7f8fdf83995a5..97036f3f3a1b6 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -47,7 +47,7 @@ interface IAccessibilityManager { @UnsupportedAppUsage List getEnabledAccessibilityServiceList(int feedbackType, int userId); - int addAccessibilityInteractionConnection(IWindow windowToken, + int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, in IAccessibilityInteractionConnection connection, String packageName, int userId); @@ -88,4 +88,8 @@ interface IAccessibilityManager { oneway void registerSystemAction(in RemoteAction action, int actionId); oneway void unregisterSystemAction(int actionId); oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection); + + void associateEmbeddedHierarchy(IBinder host, IBinder embedded); + + void disassociateEmbeddedHierarchy(IBinder token); } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 565ee63d89abb..75ec4b04eeeda 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1128,8 +1128,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { synchronized (mLock) { - // TODO: Add a method to lookup window ID by given leash token. - return -1; + return mA11yWindowManager.getWindowIdLocked(token); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7b495ce190156..ba29bc88f145e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -761,11 +761,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { return mA11yWindowManager.addAccessibilityInteractionConnection( - windowToken, connection, packageName, userId); + windowToken, leashToken, connection, packageName, userId); } @Override @@ -2642,6 +2642,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @Override + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + synchronized (mLock) { + mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); + } + } + + @Override + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + synchronized (mLock) { + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); + } + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 96e345ea9b897..d98e31eadb225 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -443,8 +443,8 @@ public class AccessibilitySecurityPolicy { return false; } } - // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy. - if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) { + if (mAccessibilityWindowManager.resolveParentWindowIdLocked(windowId) + == mAccessibilityWindowManager.getActiveWindowId(userId)) { return true; } return mAccessibilityWindowManager.findA11yWindowInfoByIdLocked(windowId) != null; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index a6041e0ee91c7..8c0058130510c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -31,6 +31,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -99,6 +100,19 @@ public class AccessibilityWindowManager { private final SparseArray mDisplayWindowsObservers = new SparseArray<>(); + /** + * Map of host view and embedded hierarchy, mapping from leash token of its ViewRootImpl. + * The key is the token from embedded hierarchy, and the value is the token from its host. + */ + private final ArrayMap mHostEmbeddedMap = new ArrayMap<>(); + + /** + * Map of window id and view hierarchy. + * The key is the window id when the ViewRootImpl register to accessibility, and the value is + * its leash token. + */ + private final SparseArray mWindowIdMap = new SparseArray<>(); + /** * This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to * receive {@link WindowInfo}s from window manager when there's an accessibility change in @@ -913,19 +927,21 @@ public class AccessibilityWindowManager { * Adds accessibility interaction connection according to given window token, package name and * window token. * - * @param windowToken The window token of accessibility interaction connection + * @param window The window token of accessibility interaction connection + * @param leashToken The leash token of accessibility interaction connection * @param connection The accessibility interaction connection * @param packageName The package name * @param userId The userId * @return The windowId of added connection * @throws RemoteException */ - public int addAccessibilityInteractionConnection(@NonNull IWindow windowToken, - @NonNull IAccessibilityInteractionConnection connection, @NonNull String packageName, - int userId) throws RemoteException { + public int addAccessibilityInteractionConnection(@NonNull IWindow window, + @NonNull IBinder leashToken, @NonNull IAccessibilityInteractionConnection connection, + @NonNull String packageName, int userId) throws RemoteException { final int windowId; boolean shouldComputeWindows = false; - final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken.asBinder()); + final IBinder token = window.asBinder(); + final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -947,35 +963,33 @@ public class AccessibilityWindowManager { windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL); wrapper.linkToDeath(); mGlobalInteractionConnections.put(windowId, wrapper); - mGlobalWindowTokens.put(windowId, windowToken.asBinder()); + mGlobalWindowTokens.put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and token: " - + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } else { RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( windowId, connection, packageName, resolvedUid, resolvedUserId); wrapper.linkToDeath(); getInteractionConnectionsForUserLocked(resolvedUserId).put(windowId, wrapper); - getWindowTokensForUserLocked(resolvedUserId).put(windowId, windowToken.asBinder()); + getWindowTokensForUserLocked(resolvedUserId).put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId - + " and token: " + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } if (isTrackingWindowsLocked(displayId)) { shouldComputeWindows = true; } + registerIdLocked(leashToken, windowId); } if (shouldComputeWindows) { mWindowManagerInternal.computeWindowsForAccessibility(displayId); } - mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( - windowToken.asBinder(), windowId); + mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId); return windowId; } @@ -1098,6 +1112,7 @@ public class AccessibilityWindowManager { * Invoked when accessibility interaction connection of window is removed. * * @param windowId Removed windowId + * @param binder Removed window token */ private void onAccessibilityInteractionConnectionRemovedLocked( int windowId, @Nullable IBinder binder) { @@ -1110,6 +1125,7 @@ public class AccessibilityWindowManager { mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); } + unregisterIdLocked(windowId); } /** @@ -1132,7 +1148,7 @@ public class AccessibilityWindowManager { * Returns the userId that owns the given window token, {@link UserHandle#USER_NULL} * if not found. * - * @param windowToken The winodw token + * @param windowToken The window token * @return The userId */ public int getWindowOwnerUserId(@NonNull IBinder windowToken) { @@ -1160,6 +1176,50 @@ public class AccessibilityWindowManager { return -1; } + /** + * Establish the relationship between the host and the embedded view hierarchy. + * + * @param host The token of host hierarchy + * @param embedded The token of the embedded hierarchy + */ + public void associateEmbeddedHierarchyLocked(@NonNull IBinder host, @NonNull IBinder embedded) { + // Use embedded window as key, since one host window may have multiple embedded windows. + associateLocked(embedded, host); + } + + /** + * Clear the relationship by given token. + * + * @param token The token + */ + public void disassociateEmbeddedHierarchyLocked(@NonNull IBinder token) { + disassociateLocked(token); + } + + /** + * Gets the parent windowId of the window according to the specified windowId. + * + * @param windowId The windowId to check + * @return The windowId of the parent window, or self if no parent exists + */ + public int resolveParentWindowIdLocked(int windowId) { + final IBinder token = getTokenLocked(windowId); + if (token == null) { + return windowId; + } + final IBinder resolvedToken = resolveTopParentTokenLocked(token); + final int resolvedWindowId = getWindowIdLocked(resolvedToken); + return resolvedWindowId != -1 ? resolvedWindowId : windowId; + } + + private IBinder resolveTopParentTokenLocked(IBinder token) { + final IBinder hostToken = getHostTokenLocked(token); + if (hostToken == null) { + return token; + } + return resolveTopParentTokenLocked(hostToken); + } + /** * Computes partial interactive region of given windowId. * @@ -1357,6 +1417,7 @@ public class AccessibilityWindowManager { */ @Nullable public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) { + windowId = resolveParentWindowIdLocked(windowId); final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); if (observer != null) { return observer.findA11yWindowInfoByIdLocked(windowId); @@ -1583,6 +1644,88 @@ public class AccessibilityWindowManager { return null; } + /** + * Associate the token of the embedded view hierarchy to the host view hierarchy. + * + * @param embedded The leash token from the view root of embedded hierarchy + * @param host The leash token from the view root of host hierarchy + */ + void associateLocked(IBinder embedded, IBinder host) { + mHostEmbeddedMap.put(embedded, host); + } + + /** + * Clear the relationship of given token. + * + * @param token The leash token + */ + void disassociateLocked(IBinder token) { + mHostEmbeddedMap.remove(token); + for (int i = mHostEmbeddedMap.size() - 1; i >= 0; i--) { + if (mHostEmbeddedMap.valueAt(i).equals(token)) { + mHostEmbeddedMap.removeAt(i); + } + } + } + + /** + * Register the leash token with its windowId. + * + * @param token The token. + * @param windowId The windowID. + */ + void registerIdLocked(IBinder token, int windowId) { + mWindowIdMap.put(windowId, token); + } + + /** + * Unregister the windowId and also disassociate its token. + * + * @param windowId The windowID + */ + void unregisterIdLocked(int windowId) { + final IBinder token = mWindowIdMap.get(windowId); + if (token == null) { + return; + } + disassociateLocked(token); + mWindowIdMap.remove(windowId); + } + + /** + * Get the leash token by given windowID. + * + * @param windowId The windowID. + * @return The token, or {@code NULL} if this windowID doesn't exist + */ + IBinder getTokenLocked(int windowId) { + return mWindowIdMap.get(windowId); + } + + /** + * Get the windowId by given leash token. + * + * @param token The token + * @return The windowID, or -1 if the token doesn't exist + */ + int getWindowIdLocked(IBinder token) { + final int index = mWindowIdMap.indexOfValue(token); + if (index == -1) { + return index; + } + return mWindowIdMap.keyAt(index); + } + + /** + * Get the leash token of the host hierarchy by given token. + * + * @param token The token + * @return The token of host hierarchy, or {@code NULL} if no host exists + */ + IBinder getHostTokenLocked(IBinder token) { + return mHostEmbeddedMap.get(token); + } + /** * Dumps all {@link AccessibilityWindowInfo}s here. */ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index fbb55fdeeb8f0..5a96347c4ae1a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -25,7 +25,9 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -133,6 +135,8 @@ public class AccessibilitySecurityPolicyTest { mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager); mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager); mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager); + + when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 9db5a080c093f..10a86f9ea5276 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -88,6 +88,10 @@ public class AccessibilityWindowManagerTest { private static final int DEFAULT_FOCUSED_INDEX = 1; private static final int SCREEN_WIDTH = 1080; private static final int SCREEN_HEIGHT = 1920; + private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + private static final int HOST_WINDOW_ID = 10; + private static final int EMBEDDED_WINDOW_ID = 11; + private static final int OTHER_WINDOW_ID = 12; private AccessibilityWindowManager mA11yWindowManager; // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true, @@ -117,6 +121,10 @@ public class AccessibilityWindowManagerTest { @Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy; @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock private IBinder mMockHostToken; + @Mock private IBinder mMockEmbeddedToken; + @Mock private IBinder mMockInvalidToken; + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -140,6 +148,8 @@ public class AccessibilityWindowManagerTest { // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged. // Resets it for mockito verify of further test case. Mockito.reset(mMockA11yEventSender); + + registerLeashedTokenAndWindowId(); } @After @@ -406,6 +416,51 @@ public class AccessibilityWindowManagerTest { } } + @Test + public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId() + throws RemoteException { + final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false, + Mockito.mock(IBinder.class), USER_SYSTEM_ID); + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() { + final int windowId = -1; + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(hostWindowId, resolvedWindowId); + } + + @Test + public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(embeddedWindowId, resolvedWindowId); + } + @Test public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() { // Updates top 2 z-order WindowInfo are whole visible. @@ -726,6 +781,64 @@ public class AccessibilityWindowManagerTest { token.asBinder(), -1); } + @Test + public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertEquals(hostToken, mMockHostToken); + } + + @Test + public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() { + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockEmbeddedToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken); + assertNull(hostToken); + } + + @Test + public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken); + assertEquals(windowId, HOST_WINDOW_ID); + } + + @Test + public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken); + assertEquals(windowId, INVALID_ID); + } + + @Test + public void getTokenLocked_windowIsRegistered_shouldReturnToken() { + final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID); + assertEquals(token, mMockHostToken); + } + + @Test + public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() { + final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID); + assertNull(token); + } + + private void registerLeashedTokenAndWindowId() { + mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID); + mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID); + } + private void startTrackingPerDisplay(int displayId) throws RemoteException { ArrayList windowInfosForDisplay = new ArrayList<>(); // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy @@ -784,6 +897,7 @@ public class AccessibilityWindowManagerTest { IAccessibilityInteractionConnection.class); final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + final IBinder mockLeashToken = Mockito.mock(IBinder.class); when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) @@ -792,11 +906,31 @@ public class AccessibilityWindowManagerTest { .thenReturn(displayId); int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( - mockWindowToken, mockA11yConnection, PACKAGE_NAME, userId); + mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId); mA11yWindowTokens.put(windowId, mockWindowToken); return mockWindowToken; } + private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal, + IBinder leashToken, int userId) throws RemoteException { + final IWindow mockWindowToken = Mockito.mock(IWindow.class); + final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock( + IAccessibilityInteractionConnection.class); + final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); + final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); + when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); + when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) + .thenReturn(bGlobal); + when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowToken.asBinder())) + .thenReturn(displayId); + + int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( + mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId); + mA11yWindowTokens.put(windowId, mockWindowToken); + return windowId; + } + private void addWindowInfo(ArrayList windowInfos, IWindow windowToken, int layer) { final WindowInfo windowInfo = WindowInfo.obtain(); windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;