Support accessibility on embedded hierarchies (2/n)
Introduce AccessibilityEmbeddedConnection. This interface provides
to the host view to the latter can interact with the view hierarchy
in SurfaceControlViewHost.
Bug: 137593247
Test: a11y CTS & unit tests
Change-Id: I9c4f5478c5ecd3bda7f688fa23660f372b59512b
diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java
new file mode 100644
index 0000000..cc1e501
--- /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<ViewRootImpl> 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 203b087..3ca84c1 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -855,6 +855,36 @@
return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
}
+ private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> 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 @@
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 @@
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 a6f8fad..f99c965 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28989,6 +28989,18 @@
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 ebfe66f7..fa4fafa 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -135,6 +135,7 @@
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 @@
final W mWindow;
+ final IBinder mLeashToken;
+
final int mTargetSdkVersion;
int mSeq;
@@ -652,6 +655,8 @@
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
+ private IAccessibilityEmbeddedConnection mEmbeddedConnection;
+
static final class SystemUiVisibilityInfo {
int seq;
int globalVisibility;
@@ -685,6 +690,7 @@
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 @@
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 @@
if (!registered) {
mAttachInfo.mAccessibilityWindowId =
mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ mLeashToken,
mContext.getPackageName(),
new AccessibilityInteractionConnection(ViewRootImpl.this));
}
@@ -9355,6 +9366,17 @@
}
}
+ /**
+ * 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 02b098b..dc87453 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1086,6 +1086,50 @@
}
/**
+ * 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.
*
* @param stateFlags The state flags.
@@ -1147,11 +1191,12 @@
/**
* 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 @@
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 0000000..707099e
--- /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 7f8fdf8..97036f3 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -47,7 +47,7 @@
@UnsupportedAppUsage
List<AccessibilityServiceInfo> 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 @@
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);
}