aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJerome Gaillard <jgaillard@google.com>2023-11-23 16:10:11 +0000
committerCherrypicker Worker <android-build-cherrypicker-worker@google.com>2023-11-30 10:59:26 +0000
commit1cb93bced2afc95be8401ddcc646db4e4780c278 (patch)
treea8d54da94f0acdcd48ff42226523cb60cf9cde6d
parentcf1c3fe95c7a9200f975c22e4de1c9ccf8b3d703 (diff)
downloadlayoutlib-1cb93bced2afc95be8401ddcc646db4e4780c278.tar.gz
Ensure accessibility works with dialogs
Dialogs normally display in a different window, but in layoutlib they are simply added to the current view hierarchy. That causes a problem for accessibility, as the root view for dialogs is a DecorView, which is automatically considered to be the root for accessibility. This confuses the accessibility node info creation, resulting in a cycle. To prevent that, we set the decor view to be the root view as far as ViewRootImpl is concerned, which is enough to solve the accessibility cycle issue. But we need to make sure that Layout, which is the actual root of the hierarchy still considers itself visible (which requires overriding getChildVisibleRect and getGlobalVisibleRect, as those normally query ViewRootImpl which is not the parent of Layout anymore when there is a Dialog). Bug: 312418057 Test: test added (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:69e377a789407277ebb748f2c4e01216f2e9a92d) Merged-In: I0742c0bc9469fdf114853e686f9cf213ef676f2d Change-Id: I0742c0bc9469fdf114853e686f9cf213ef676f2d
-rw-r--r--bridge/src/android/view/AttachInfo_Accessor.java2
-rw-r--r--bridge/src/android/view/ViewRootImpl_Accessor.java10
-rw-r--r--bridge/src/android/view/WindowManagerImpl.java18
-rw-r--r--bridge/src/com/android/layoutlib/bridge/impl/Layout.java21
-rw-r--r--bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java7
-rw-r--r--bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java54
6 files changed, 102 insertions, 10 deletions
diff --git a/bridge/src/android/view/AttachInfo_Accessor.java b/bridge/src/android/view/AttachInfo_Accessor.java
index 2e38b31793..b8c8f0e060 100644
--- a/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/bridge/src/android/view/AttachInfo_Accessor.java
@@ -37,6 +37,8 @@ public class AttachInfo_Accessor {
info.mInTouchMode = false; // this is so that we can display selections.
info.mHardwareAccelerated = false;
info.mApplicationScale = 1.0f;
+ ViewRootImpl_Accessor.setChild(root, view);
+ view.assignParent(root);
view.dispatchAttachedToWindow(info, 0);
}
diff --git a/bridge/src/android/view/ViewRootImpl_Accessor.java b/bridge/src/android/view/ViewRootImpl_Accessor.java
index 691f59ae46..d15952ad3d 100644
--- a/bridge/src/android/view/ViewRootImpl_Accessor.java
+++ b/bridge/src/android/view/ViewRootImpl_Accessor.java
@@ -26,9 +26,13 @@ public class ViewRootImpl_Accessor {
public static void setChild(ViewRootImpl viewRoot, View child) {
viewRoot.mView = child;
- child.assignParent(viewRoot);
- viewRoot.mWidth = child.getWidth();
- viewRoot.mHeight = child.getHeight();
+ if (child != null) {
+ viewRoot.mWidth = child.getWidth();
+ viewRoot.mHeight = child.getHeight();
+ } else {
+ viewRoot.mWidth = -1;
+ viewRoot.mHeight = -1;
+ }
}
public static void detachFromWindow(ViewRootImpl viewRoot) {
diff --git a/bridge/src/android/view/WindowManagerImpl.java b/bridge/src/android/view/WindowManagerImpl.java
index eb1e22c736..285ca9e5e4 100644
--- a/bridge/src/android/view/WindowManagerImpl.java
+++ b/bridge/src/android/view/WindowManagerImpl.java
@@ -41,6 +41,8 @@ import com.android.internal.R;
import com.android.internal.policy.DecorView;
import com.android.layoutlib.bridge.Bridge;
+import java.util.ArrayList;
+
public class WindowManagerImpl implements WindowManager {
private final Context mContext;
@@ -179,10 +181,12 @@ public class WindowManagerImpl implements WindowManager {
}
}
mCurrentRootView.addView(arg0, frameLayoutParams);
+ ViewRootImpl_Accessor.setChild(mBaseRootView.getViewRootImpl(), arg0);
}
@Override
public void removeView(View arg0) {
+ ViewRootImpl viewRootImpl = arg0.getViewRootImpl();
if (mCurrentRootView != null) {
mCurrentRootView.removeView(arg0);
if (mBaseRootView != null && mCurrentRootView.getChildCount() == 0) {
@@ -190,6 +194,20 @@ public class WindowManagerImpl implements WindowManager {
mCurrentRootView = null;
}
}
+ if (viewRootImpl != null && viewRootImpl.getView() == arg0) {
+ View newRoot = null;
+ if (mCurrentRootView != null && mCurrentRootView.getChildCount() > 0) {
+ ArrayList<View> childrenList = mCurrentRootView.buildOrderedChildList();
+ newRoot = childrenList.get(childrenList.size() - 1);
+ } else if (mBaseRootView != null) {
+ View root = mBaseRootView;
+ while (root.getParent() instanceof View) {
+ root = (View)root.getParent();
+ }
+ newRoot = root;
+ }
+ ViewRootImpl_Accessor.setChild(viewRootImpl, newRoot);
+ }
}
@Override
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index c8e5009888..1ca3b9c2cc 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -38,6 +38,8 @@ import com.android.resources.ScreenOrientation;
import android.R.id;
import android.annotation.NonNull;
import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -196,6 +198,25 @@ class Layout extends FrameLayout {
mBuilder = null;
}
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, Point offset, boolean forceParentCheck) {
+ return r.intersect(0, 0, getWidth(), getHeight());
+ }
+
+ @Override
+ public boolean getGlobalVisibleRect(Rect r, Point globalOffset) {
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ if (width > 0 && height > 0) {
+ r.set(0, 0, width, height);
+ if (globalOffset != null) {
+ globalOffset.set(-mScrollX, -mScrollY);
+ }
+ return true;
+ }
+ return false;
+ }
+
@NonNull
private static View createSysUiOverlay(@NonNull BridgeContext context) {
SysUiOverlay overlay = new SysUiOverlay(context, 20, 10, 50, 40, 60);
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index de7f651bab..034a92df75 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -72,8 +72,6 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewParent;
-import android.view.ViewRootImpl;
-import android.view.ViewRootImpl_Accessor;
import android.view.WindowManagerImpl;
import android.widget.ActionMenuView;
import android.widget.FrameLayout;
@@ -375,11 +373,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
mViewRoot.getViewRootImpl().mTmpFrames.displayFrame.set(mViewRoot.getLeft(),
mViewRoot.getTop(), mViewRoot.getRight(), mViewRoot.getBottom());
- ViewRootImpl rootImpl = AttachInfo_Accessor.getRootView(mViewRoot);
- if (rootImpl != null) {
- ViewRootImpl_Accessor.setChild(rootImpl, mViewRoot);
- }
-
mSystemViewInfoList =
visitAllChildren(mViewRoot, 0, 0, params, false);
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java
index 149680868e..dc24d845ca 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java
@@ -19,6 +19,7 @@ package com.android.layoutlib.bridge.android;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.intensive.LayoutLibTestCallback;
@@ -117,4 +118,57 @@ public class AccessibilityTest extends RenderTestBase {
session.dispose();
}
}
+
+ @Test
+ public void testDialogAccessibility() throws Exception {
+ String layout =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"fill_parent\"\n" +
+ " android:layout_height=\"fill_parent\">\n" +
+ " <com.android.layoutlib.test.myapplication.widgets.DialogView\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_width=\"wrap_content\" />\n" +
+ "</LinearLayout>\n";
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+ RenderSession session = sBridge.createSession(params);
+ session.setElapsedFrameTimeNanos(1);
+ try {
+ Result renderResult = session.render(50000);
+ assertTrue(renderResult.isSuccess());
+ View rootView =
+ (View)((View) session.getSystemRootViews().get(1).getViewObject()).getParent();
+ int[] counter = {0};
+ session.execute(() -> {
+ AccessibilityNodeInfo rootNode = rootView.createAccessibilityNodeInfo();
+ assertNotNull(rootNode);
+ rootNode.setQueryFromAppProcessEnabled(rootView, true);
+ traverseAccessibilityTree(rootNode, counter);
+ });
+ assertEquals(17, counter[0]);
+ } finally {
+ session.dispose();
+ }
+ }
+
+ private void traverseAccessibilityTree(AccessibilityNodeInfo node, int[] counter) {
+ int childrenSize = node.getChildCount();
+ for (int i = 0; i < childrenSize; i++) {
+ AccessibilityNodeInfo child = node.getChild(i);
+ counter[0]++;
+ traverseAccessibilityTree(child, counter);
+ }
+ }
}