From 5e1c1caabf17cbfab7b39d3a82a6a2fc65e87318 Mon Sep 17 00:00:00 2001 From: Qasid Ahmad Sadiq Date: Thu, 3 Jan 2019 21:55:55 -0800 Subject: [PATCH] Fixed bug where certain focusable windows are reported to accessibility out of z order. It is possible to have a window that is focusable but not touchable. This was initially addressed with the fix in b/15432989, with the informative commit message: "The APIs for introspecting interactive windows were reporting only the touchable windows but were missing the focused window. The user can interact with the latter by typing, hence it should always be reported. Also this was breaking backwards compatibility as if the focused window is covered by a modal one, the focused window was not reporeted and this was putting the active window in a bad state as the latter is either the focused window or the one the user is touching." But the focusable window was added after all of the other windows were added, so that broke the expectation that the reported windows are in z-order. I can imagine beyond the test this probably also incorrectly reported a situation where a touchable window was obscured by a focusable one. So now we add it in the correct order, and we update the unaccounted regions appropriately. The reason that this broke the test is that the overlay is displayed ABOVE the the installer, but it is reported as BELOW the installer. This only broke on certain devices because the relative screen bounds of those two windows differed, in some situations the installer window obscured the overlay window. Also, make this a little cleaner. Fix: 115739549 Test: atest CtsPackageInstallerTapjackingTestCases, atest CtsAccessibilityServiceTestCases, atest CtsUiAutomationTestCases Change-Id: Ia3b7ae01a7f8ef76eec79e6f1640c3464c4fe89a --- .../server/wm/AccessibilityController.java | 155 +++++++++--------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index fe0b5c250da85..caebf15282709 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1066,93 +1066,19 @@ final class AccessibilityController { final int visibleWindowCount = visibleWindows.size(); HashSet skipRemainingWindowsForTasks = new HashSet<>(); - for (int i = visibleWindowCount - 1; i >= 0; i--) { + + // Iterate until we figure out what is touchable for the entire screen. + for (int i = visibleWindowCount - 1; i >= 0 && !unaccountedSpace.isEmpty(); i--) { final WindowState windowState = visibleWindows.valueAt(i); - final int flags = windowState.mAttrs.flags; - final Task task = windowState.getTask(); - // If the window is part of a task that we're finished with - ignore. - if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) { - continue; - } - - // Ignore non-touchable windows, except the split-screen divider, which is - // occasionally non-touchable but still useful for identifying split-screen - // mode. - if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) - && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) { - continue; - } - - // Compute the bounds in the screen. final Rect boundsInScreen = mTempRect; computeWindowBoundsInScreen(windowState, boundsInScreen); - // If the window is completely covered by other windows - ignore. - if (unaccountedSpace.quickReject(boundsInScreen)) { - continue; - } - - // Add windows of certain types not covered by modal windows. - if (isReportedWindowType(windowState.mAttrs.type)) { - // Add the window to the ones to be reported. + if (windowMattersToAccessibility(windowState, boundsInScreen, unaccountedSpace, + skipRemainingWindowsForTasks)) { addPopulatedWindowInfo(windowState, boundsInScreen, windows, addedWindows); - if (windowState.isFocused()) { - focusedWindowAdded = true; - } - } - - if (windowState.mAttrs.type != - WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { - - // Account for the space this window takes if the window - // is not an accessibility overlay which does not change - // the reported windows. - unaccountedSpace.op(boundsInScreen, unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); - - // If a window is modal it prevents other windows from being touched - if ((flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { - // Account for all space in the task, whether the windows in it are - // touchable or not. The modal window blocks all touches from the task's - // area. - unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); - - if (task != null) { - // If the window is associated with a particular task, we can skip the - // rest of the windows for that task. - skipRemainingWindowsForTasks.add(task.mTaskId); - continue; - } else { - // If the window is not associated with a particular task, then it is - // globally modal. In this case we can skip all remaining windows. - break; - } - } - } - - // We figured out what is touchable for the entire screen - done. - if (unaccountedSpace.isEmpty()) { - break; - } - } - - // Always report the focused window. - if (!focusedWindowAdded) { - for (int i = visibleWindowCount - 1; i >= 0; i--) { - WindowState windowState = visibleWindows.valueAt(i); - if (windowState.isFocused()) { - // Compute the bounds in the screen. - Rect boundsInScreen = mTempRect; - computeWindowBoundsInScreen(windowState, boundsInScreen); - - // Add the window to the ones to be reported. - addPopulatedWindowInfo( - windowState, boundsInScreen, windows, addedWindows); - break; - } + updateUnaccountedSpace(windowState, boundsInScreen, unaccountedSpace, + skipRemainingWindowsForTasks); } } @@ -1221,6 +1147,73 @@ final class AccessibilityController { clearAndRecycleWindows(windows); } + private boolean windowMattersToAccessibility(WindowState windowState, Rect boundsInScreen, + Region unaccountedSpace, HashSet skipRemainingWindowsForTasks) { + if (windowState.isFocused()) { + return true; + } + + // If the window is part of a task that we're finished with - ignore. + final Task task = windowState.getTask(); + if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) { + return false; + } + + // Ignore non-touchable windows, except the split-screen divider, which is + // occasionally non-touchable but still useful for identifying split-screen + // mode. + if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) + && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) { + return false; + } + + // If the window is completely covered by other windows - ignore. + if (unaccountedSpace.quickReject(boundsInScreen)) { + return false; + } + + // Add windows of certain types not covered by modal windows. + if (isReportedWindowType(windowState.mAttrs.type)) { + return true; + } + + return false; + } + + private void updateUnaccountedSpace(WindowState windowState, Rect boundsInScreen, + Region unaccountedSpace, HashSet skipRemainingWindowsForTasks) { + if (windowState.mAttrs.type + != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { + + // Account for the space this window takes if the window + // is not an accessibility overlay which does not change + // the reported windows. + unaccountedSpace.op(boundsInScreen, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + + // If a window is modal it prevents other windows from being touched + if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) { + // Account for all space in the task, whether the windows in it are + // touchable or not. The modal window blocks all touches from the task's + // area. + unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + + final Task task = windowState.getTask(); + if (task != null) { + // If the window is associated with a particular task, we can skip the + // rest of the windows for that task. + skipRemainingWindowsForTasks.add(task.mTaskId); + } else { + // If the window is not associated with a particular task, then it is + // globally modal. In this case we can skip all remaining windows. + unaccountedSpace.setEmpty(); + } + } + } + } + private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) { // Get the touchable frame. Region touchableRegion = mTempRegion1;