A11y support for windows in a re-parented display
Currently, accessibility only supports windows in default display.
The windows in other displays aren't recongnized by accessibility
even they're re-parented to default display. Besides, we need to
offset the bound after re-parented since the original is from its
own display.
Bug: 129098348
Test: atest WindowStateTests
Test: atest DisplayContentTests
Test: a11y CTS & unit tests
Change-Id: I41a84a4c02e3c1be1dab4bd420d504b85787c4fb
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index b6a5be8..4cfc1d1 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1065,8 +1065,6 @@
private final long mRecurringAccessibilityEventsIntervalMillis;
- private int mTempLayer = 0;
-
public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
WindowsForAccessibilityCallback callback) {
mContext = windowManagerService.mContext;
@@ -1091,7 +1089,7 @@
}
/**
- * Check if windows have changed, and send them to the accessibilty subsystem if they have.
+ * Check if windows have changed, and send them to the accessibility subsystem if they have.
*
* @param forceSend Send the windows the accessibility even if they haven't changed.
*/
@@ -1108,8 +1106,7 @@
// the window manager is still looking for where to put it.
// We will do the work when we get a focus change callback.
// TODO(b/112273690): Support multiple displays
- // TODO(b/129098348): Support embedded displays
- if (mService.getDefaultDisplayContentLocked().mCurrentFocus == null) {
+ if (!isCurrentFocusWindowOnDefaultDisplay()) {
return;
}
@@ -1395,21 +1392,35 @@
}
private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+ final List<WindowState> tempWindowStatesList = new ArrayList<>();
final DisplayContent dc = mService.getDefaultDisplayContentLocked();
- mTempLayer = 0;
dc.forAllWindows((w) -> {
if (w.isVisibleLw()) {
- outWindows.put(mTempLayer++, w);
+ tempWindowStatesList.add(w);
}
}, false /* traverseTopToBottom */);
+ // Insert the re-parented windows in another display on top of their parents in
+ // default display.
mService.mRoot.forAllWindows(w -> {
- final WindowState win = findRootDisplayParentWindow(w);
- if (win != null && win.getDisplayContent().isDefaultDisplay && w.isVisibleLw()) {
- // TODO(b/129098348): insert windows on child displays into outWindows based on
- // root-display-parent window.
- outWindows.put(mTempLayer++, w);
+ final WindowState parentWindow = findRootDisplayParentWindow(w);
+ if (parentWindow == null) {
+ return;
}
- }, false /* traverseTopToBottom */);
+
+ // TODO: Use Region instead to get rid of this complicated logic.
+ // Check the tap exclude region of the parent window. If the tap exclude region
+ // is empty, it means there is another can-receive-pointer-event view on top of
+ // the region. Hence, we don't count the window as visible.
+ if (w.isVisibleLw() && parentWindow.getDisplayContent().isDefaultDisplay
+ && parentWindow.hasTapExcludeRegion()
+ && tempWindowStatesList.contains(parentWindow)) {
+ tempWindowStatesList.add(
+ tempWindowStatesList.lastIndexOf(parentWindow) + 1, w);
+ }
+ }, true /* traverseTopToBottom */);
+ for (int i = 0; i < tempWindowStatesList.size(); i++) {
+ outWindows.put(i, tempWindowStatesList.get(i));
+ }
}
private WindowState findRootDisplayParentWindow(WindowState win) {
@@ -1425,6 +1436,23 @@
return displayParentWindow;
}
+ private boolean isCurrentFocusWindowOnDefaultDisplay() {
+ final WindowState focusedWindow =
+ mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
+ if (focusedWindow == null) {
+ return false;
+ }
+
+ final WindowState rootDisplayParentWindow = findRootDisplayParentWindow(focusedWindow);
+ if (!focusedWindow.isDefaultDisplay()
+ && (rootDisplayParentWindow == null
+ || !rootDisplayParentWindow.isDefaultDisplay())) {
+ return false;
+ }
+
+ return true;
+ }
+
private class MyHandler extends Handler {
public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82ea4fe..13cbb6e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2540,6 +2540,9 @@
void removeImmediately() {
mRemovingDisplay = true;
try {
+ if (mParentWindow != null) {
+ mParentWindow.removeEmbeddedDisplayContent(this);
+ }
// Clear all transitions & screen frozen states when removing display.
mOpeningApps.clear();
mClosingApps.clear();
@@ -5019,6 +5022,7 @@
*/
void reparentDisplayContent(WindowState win, SurfaceControl sc) {
mParentWindow = win;
+ mParentWindow.addEmbeddedDisplayContent(this);
mParentSurfaceControl = sc;
if (mPortalWindowHandle == null) {
mPortalWindowHandle = createPortalWindowHandle(sc.toString());
@@ -5049,12 +5053,12 @@
throw new IllegalArgumentException(
"The given window is not the parent window of this display.");
}
- if (mLocationInParentWindow.x != x || mLocationInParentWindow.y != y) {
- mLocationInParentWindow.x = x;
- mLocationInParentWindow.y = y;
+ if (!mLocationInParentWindow.equals(x, y)) {
+ mLocationInParentWindow.set(x, y);
if (mWmService.mAccessibilityController != null) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
+ notifyLocationInParentDisplayChanged();
}
}
@@ -5062,6 +5066,30 @@
return mLocationInParentWindow;
}
+ Point getLocationInParentDisplay() {
+ final Point location = new Point();
+ if (mParentWindow != null) {
+ // LocationInParentWindow indicates the offset to (0,0) of window, but what we need is
+ // the offset to (0,0) of display.
+ DisplayContent dc = this;
+ do {
+ final WindowState displayParent = dc.getParentWindow();
+ location.x += displayParent.getFrameLw().left
+ + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
+ location.y += displayParent.getFrameLw().top
+ + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
+ dc = displayParent.getDisplayContent();
+ } while (dc != null && dc.getParentWindow() != null);
+ }
+ return location;
+ }
+
+ void notifyLocationInParentDisplayChanged() {
+ forAllWindows(w -> {
+ w.updateLocationInParentDisplayIfNeeded();
+ }, false /* traverseTopToBottom */);
+ }
+
@VisibleForTesting
SurfaceControl getWindowingLayer() {
return mWindowingLayer;
diff --git a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
index 22f529b..8f72cda 100644
--- a/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
+++ b/services/core/java/com/android/server/wm/TapExcludeRegionHolder.java
@@ -52,4 +52,11 @@
region.op(r, Region.Op.UNION);
}
}
+
+ /**
+ * Return true if tap exclude region is empty.
+ */
+ boolean isEmpty() {
+ return mTapExcludeRegions.size() == 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 47df839..82054320 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4541,8 +4541,11 @@
AccessibilityController accessibilityController = null;
synchronized (mGlobalLock) {
- // TODO(multidisplay): Accessibility supported only of default desiplay.
- if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
+ // TODO(multidisplay): Accessibility supported only of default display and
+ // embedded displays.
+ if (mAccessibilityController != null
+ && (displayContent.isDefaultDisplay
+ || displayContent.getParentWindow() != null)) {
accessibilityController = mAccessibilityController;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5ef184a..89fd33b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -167,6 +167,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -316,6 +317,9 @@
int mLayoutSeq = -1;
+ /** @see #addEmbeddedDisplayContent(DisplayContent dc) */
+ private final ArraySet<DisplayContent> mEmbeddedDisplayContents = new ArraySet<>();
+
/**
* Used to store last reported to client configuration and check if we have newer available.
* We'll send configuration to client only if it is different from the last applied one and
@@ -536,6 +540,12 @@
private final Point mTmpPoint = new Point();
/**
+ * If a window is on a display which has been re-parented to a view in another window,
+ * use this offset to indicate the correct location.
+ */
+ private final Point mLastReportedDisplayOffset = new Point();
+
+ /**
* Whether the window was resized by us while it was gone for layout.
*/
boolean mResizedWhileGone = false;
@@ -1777,11 +1787,13 @@
startMoveAnimation(left, top);
}
- //TODO (multidisplay): Accessibility supported only for the default display.
- if (mWmService.mAccessibilityController != null
- && getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+ // TODO (multidisplay): Accessibility supported only for the default display and
+ // embedded displays
+ if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
+ || getDisplayContent().getParentWindow() != null)) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
+ updateLocationInParentDisplayIfNeeded();
try {
mClient.moved(left, top);
@@ -3143,11 +3155,13 @@
displayCutout);
}
- //TODO (multidisplay): Accessibility supported only for the default display.
+ // TODO (multidisplay): Accessibility supported only for the default display and
+ // embedded displays
if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
|| getDisplayContent().getParentWindow() != null)) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
}
+ updateLocationInParentDisplayIfNeeded();
mWindowFrames.resetInsetsChanged();
mWinAnimator.mSurfaceResized = false;
@@ -3165,6 +3179,36 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ void updateLocationInParentDisplayIfNeeded() {
+ final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size();
+ // If there is any embedded display which is re-parented to this window, we need to
+ // notify all windows in the embedded display about the location change.
+ if (embeddedDisplayContentsSize != 0) {
+ for (int i = embeddedDisplayContentsSize - 1; i >= 0; i--) {
+ final DisplayContent edc = mEmbeddedDisplayContents.valueAt(i);
+ edc.notifyLocationInParentDisplayChanged();
+ }
+ }
+ // If this window is in a embedded display which is re-parented to another window,
+ // we may need to update its correct on-screen location.
+ final DisplayContent dc = getDisplayContent();
+ if (dc.getParentWindow() == null) {
+ return;
+ }
+
+ final Point offset = dc.getLocationInParentDisplay();
+ if (mLastReportedDisplayOffset.equals(offset)) {
+ return;
+ }
+
+ mLastReportedDisplayOffset.set(offset.x, offset.y);
+ try {
+ mClient.locationInParentDisplayChanged(mLastReportedDisplayOffset);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to update offset from DisplayContent", e);
+ }
+ }
+
/**
* Called when the insets state changed.
*/
@@ -3584,6 +3628,7 @@
}
pw.println(prefix + "isOnScreen=" + isOnScreen());
pw.println(prefix + "isVisible=" + isVisible());
+ pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents);
}
@Override
@@ -4209,7 +4254,8 @@
return;
}
- //TODO (multidisplay): Accessibility is supported only for the default display.
+ // TODO (multidisplay): Accessibility supported only for the default display and
+ // embedded displays
if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
|| getDisplayContent().getParentWindow() != null)) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
@@ -4581,6 +4627,28 @@
}
/**
+ * Add the DisplayContent of the embedded display which is re-parented to this window to
+ * the list of embedded displays.
+ *
+ * @param dc DisplayContent of the re-parented embedded display.
+ * @return {@code true} if the giving DisplayContent is added, {@code false} otherwise.
+ */
+ boolean addEmbeddedDisplayContent(DisplayContent dc) {
+ return mEmbeddedDisplayContents.add(dc);
+ }
+
+ /**
+ * Remove the DisplayContent of the embedded display which is re-parented to this window from
+ * the list of embedded displays.
+ *
+ * @param dc DisplayContent of the re-parented embedded display.
+ * @return {@code true} if the giving DisplayContent is removed, {@code false} otherwise.
+ */
+ boolean removeEmbeddedDisplayContent(DisplayContent dc) {
+ return mEmbeddedDisplayContents.remove(dc);
+ }
+
+ /**
* Updates the last inset values to the current ones.
*/
void updateLastInsetValues() {
@@ -5002,6 +5070,10 @@
tempRegion.recycle();
}
+ boolean hasTapExcludeRegion() {
+ return mTapExcludeRegionHolder != null && !mTapExcludeRegionHolder.isEmpty();
+ }
+
@Override
public boolean isInputMethodTarget() {
return getDisplayContent().mInputMethodTarget == this;