diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 3743483377b5da7c7dc3e85897caec0208d586d4..37967fa86b0f74db22cf0162049778e854bb2d8d 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -85,6 +85,7 @@ android_test { "ravenwood-junit", "net_flags_lib", "CtsVirtualDeviceCommonLib", + "com_android_server_accessibility_flags_lib", ], libs: [ 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 89d146da1b75384466ea729c71c9114fa604a4e6..8717a0500e574eb687e2e3f254670fa7a035b8ec 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -47,6 +47,9 @@ import android.os.IBinder; import android.os.LocaleList; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.SparseArray; import android.view.Display; import android.view.IWindow; @@ -67,6 +70,7 @@ import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -77,9 +81,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +// This test verifies deprecated codepath. Probably changing this file means +// AccessibilityWindowManagerWithAccessibilityWindowTest also needs to be updated. +// LINT.IfChange + /** - * Tests for the AccessibilityWindowManager + * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled. + * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest + * after completing the flag migration. */ +@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y) public class AccessibilityWindowManagerTest { private static final String PACKAGE_NAME = "com.android.server.accessibility"; private static final boolean FORCE_SEND = true; @@ -132,6 +143,9 @@ public class AccessibilityWindowManagerTest { @Mock private IBinder mMockEmbeddedToken; @Mock private IBinder mMockInvalidToken; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -1227,3 +1241,4 @@ public class AccessibilityWindowManagerTest { } } } +// LINT.ThenChange(/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java) diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4db27d272f8e84b9a056495f7504c7386a51db11 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java @@ -0,0 +1,1247 @@ +/* + * 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 com.android.server.accessibility; + +import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT; +import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId; +import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges; +import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.graphics.Region; +import android.os.IBinder; +import android.os.LocaleList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.SparseArray; +import android.view.Display; +import android.view.IWindow; +import android.view.WindowInfo; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowAttributes; +import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; + +import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; +import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback; + +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y + * TODO(b/322444245): Merge with AccessibilityWindowManagerTest + * after completing the flag migration. + */ +@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y) +public class AccessibilityWindowManagerWithAccessibilityWindowTest { + private static final String PACKAGE_NAME = "com.android.server.accessibility"; + private static final boolean FORCE_SEND = true; + private static final boolean SEND_ON_WINDOW_CHANGES = false; + private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM; + private static final int USER_PROFILE = 11; + private static final int USER_PROFILE_PARENT = 1; + private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; + private static final int NUM_GLOBAL_WINDOWS = 4; + private static final int NUM_APP_WINDOWS = 4; + private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS); + 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, + // i.e., each display would have its current focused window, and one of all focused windows + // would be top focused window. Otherwise, window manager only supports one focused window + // at all displays, and that focused window would be top focused window. + private boolean mSupportPerDisplayFocus = false; + private int mTopFocusedDisplayId = Display.INVALID_DISPLAY; + private IBinder mTopFocusedWindowToken = null; + + // List of window token, mapping from windowId -> window token. + private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>(); + // List of window info lists, mapping from displayId -> window info lists. + private final SparseArray<ArrayList<WindowInfo>> mWindowInfos = + new SparseArray<>(); + // List of callback, mapping from displayId -> callback. + private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows = + new SparseArray<>(); + // List of display ID. + private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList( + Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID)); + + private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null); + + @Mock + private WindowManagerInternal mMockWindowManagerInternal; + @Mock + private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender; + @Mock + private AccessibilitySecurityPolicy mMockA11ySecurityPolicy; + @Mock + private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock + private AccessibilityTraceManager mMockA11yTraceManager; + + @Mock + private IBinder mMockHostToken; + @Mock + private IBinder mMockEmbeddedToken; + @Mock + private IBinder mMockInvalidToken; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + when(mMockA11yUserManager.getCurrentUserIdLocked()).thenReturn(USER_SYSTEM_ID); + when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( + USER_PROFILE)).thenReturn(USER_PROFILE_PARENT); + when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked( + USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID); + when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked( + anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME); + + doAnswer((invocation) -> { + onWindowsForAccessibilityChanged(invocation.getArgument(0), false); + return null; + }).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt()); + + mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler, + mMockWindowManagerInternal, + mMockA11yEventSender, + mMockA11ySecurityPolicy, + mMockA11yUserManager, + mMockA11yTraceManager); + // Starts tracking window of default display and sets the default display + // as top focused display before each testing starts. + startTrackingPerDisplay(Display.DEFAULT_DISPLAY); + + // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged. + // Resets it for mockito verify of further test case. + Mockito.reset(mMockA11yEventSender); + + registerLeashedTokenAndWindowId(); + } + + @After + public void tearDown() { + mHandler.removeAllMessages(); + } + + @Test + public void startTrackingWindows_shouldEnableWindowManagerCallback() { + // AccessibilityWindowManager#startTrackingWindows already invoked in setup. + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); + final WindowsForAccessibilityCallback callbacks = + mCallbackOfWindows.get(Display.DEFAULT_DISPLAY); + verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback( + eq(Display.DEFAULT_DISPLAY), eq(callbacks)); + } + + @Test + public void stopTrackingWindows_shouldDisableWindowManagerCallback() { + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); + Mockito.reset(mMockWindowManagerInternal); + + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); + verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback( + eq(Display.DEFAULT_DISPLAY), isNull()); + + } + + @Test + public void stopTrackingWindows_shouldClearWindows() { + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)); + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)); + assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), + AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); + assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), + activeWindowId); + } + + @Test + public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID)); + // Stops tracking windows of second display. + mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID); + assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), + AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); + } + + @Test + public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() { + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + WindowInfo focusedWindowInfo = + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX); + assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, focusedWindowInfo.token)); + + focusedWindowInfo.focused = false; + focusedWindowInfo = + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1); + focusedWindowInfo.focused = true; + + mA11yWindowManager.onTouchInteractionStart(); + setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1); + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID)); + } + + @Test + public void + onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true. + mSupportPerDisplayFocus = true; + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + // Gets the active window. + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + // Gets the top focused window. + final int topFocusedWindowId = + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT); + // Changes the current focused window at second display. + changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID, + DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX); + + onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES); + // The active window should not be changed. + assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID)); + // The top focused window should not be changed. + assertEquals(topFocusedWindowId, + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT)); + } + + @Test + public void + onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow() + throws RemoteException { + // At setup, the default display sets be the top focused display and + // its current focused window sets be the top focused window. + // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is + // false. + mSupportPerDisplayFocus = false; + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + // Gets the active window. + final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + // Gets the top focused window. + final int topFocusedWindowId = + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT); + // Changes the current focused window from default display to second display. + changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID, + DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES); + // The active window should be changed. + assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID)); + // The top focused window should be changed. + assertNotEquals(topFocusedWindowId, + mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT)); + } + + @Test + public void onWindowsChanged_shouldReportCorrectLayer() { + // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup. + List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + for (int i = 0; i < a11yWindows.size(); i++) { + final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i); + final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i); + assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1, + is(a11yWindow.getLayer())); + } + } + + @Test + public void onWindowsChanged_shouldReportCorrectOrder() { + // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup. + List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + for (int i = 0; i < a11yWindows.size(); i++) { + final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i); + final IBinder windowToken = mA11yWindowManager + .getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId()); + final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i); + assertThat(windowToken, is(windowInfo.token)); + } + } + + @Test + public void onWindowsChangedAndForceSend_shouldUpdateWindows() { + final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + final int correctLayer = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer(); + windowInfo.layer += 1; + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND); + assertNotEquals(correctLayer, + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer()); + } + + @Test + public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() { + final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + final int correctLayer = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer(); + windowInfo.layer += 1; + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + assertEquals(correctLayer, + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer()); + } + + @Test + public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows() + throws RemoteException { + final AccessibilityWindowInfo oldWindow = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0); + final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + true, USER_SYSTEM_ID); + final WindowInfo windowInfo = WindowInfo.obtain(); + windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION; + windowInfo.token = token.asBinder(); + windowInfo.layer = 0; + windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + assertNotEquals(oldWindow, + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)); + } + + @Test + public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() { + final WindowInfo focusedWindowInfo = + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX); + final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + focusedWindowInfo.focused = false; + windowInfo.focused = true; + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0) + .isFocused()); + } + + @Test + public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() { + for (int i = 0; i < NUM_OF_WINDOWS; i++) { + final int windowId = mA11yWindowTokens.keyAt(i); + final IWindow windowToken = mA11yWindowTokens.valueAt(i); + assertNotNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId)); + + mA11yWindowManager.removeAccessibilityInteractionConnection(windowToken); + assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId)); + } + } + + @Test + public void remoteAccessibilityConnection_binderDied_shouldRemoveConnection() { + for (int i = 0; i < NUM_OF_WINDOWS; i++) { + final int windowId = mA11yWindowTokens.keyAt(i); + final RemoteAccessibilityConnection remoteA11yConnection = + mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId); + assertNotNull(remoteA11yConnection); + + remoteA11yConnection.binderDied(); + assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId)); + } + } + + @Test + public void getWindowTokenForUserAndWindowId_shouldNotNull() { + final List<AccessibilityWindowInfo> windows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + for (int i = 0; i < windows.size(); i++) { + final int windowId = windows.get(i).getId(); + + assertNotNull(mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( + USER_SYSTEM_ID, windowId)); + } + } + + @Test + public void findWindowId() { + final List<AccessibilityWindowInfo> windows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + for (int i = 0; i < windows.size(); i++) { + final int windowId = windows.get(i).getId(); + final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked( + USER_SYSTEM_ID, windowId); + + assertEquals(mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, windowToken), windowId); + } + } + + @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); + assertNotEquals(hostWindowId, resolvedWindowId); + assertEquals(embeddedWindowId, resolvedWindowId); + } + + @Test + public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() { + // Updates top 2 z-order WindowInfo are whole visible. + WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2); + windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1); + windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2, + SCREEN_WIDTH, SCREEN_HEIGHT); + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + final Region outBounds = new Region(); + int windowId = a11yWindows.get(0).getId(); + + mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds); + assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH)); + assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2)); + + windowId = a11yWindows.get(1).getId(); + + mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds); + assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH)); + assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2)); + } + + @Test + public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() { + // Updates z-order #1 WindowInfo is half visible. + WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + final Region outBounds = new Region(); + int windowId = a11yWindows.get(1).getId(); + + mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds); + assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH)); + assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2)); + } + + @Test + public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() { + // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible. + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + final Region outBounds = new Region(); + int windowId = a11yWindows.get(1).getId(); + + mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds); + assertTrue(outBounds.getBounds().isEmpty()); + } + + @Test + public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() { + // Updates z-order #0 WindowInfo to have two interact-able areas. + Region region = new Region(0, 0, SCREEN_WIDTH, 200); + region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION); + WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); + windowInfo.regionInScreen.set(region); + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + final Region outBounds = new Region(); + int windowId = a11yWindows.get(1).getId(); + + mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds); + assertFalse(outBounds.getBounds().isEmpty()); + assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH)); + assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT - 400)); + } + + @Test + public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() { + final IBinder eventWindowToken = + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token; + final int eventWindowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, eventWindowToken); + when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates()) + .thenReturn(eventWindowToken); + + final int noUse = 0; + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + noUse, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + noUse); + assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId)); + assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), + is(eventWindowId)); + } + + @Test + public void updateActiveAndA11yFocusedWindow_hoverEvent_touchInteract_shouldSetActiveWindow() { + final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, + DEFAULT_FOCUSED_INDEX + 1); + final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + assertThat(currentActiveWindowId, is(not(eventWindowId))); + + final int noUse = 0; + mA11yWindowManager.onTouchInteractionStart(); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + noUse, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, + noUse); + assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId)); + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(2)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(currentActiveWindowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); + assertThat(captor.getAllValues().get(1), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(eventWindowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); + } + + @Test + public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_shouldUpdateA11yFocus() { + final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, + DEFAULT_FOCUSED_INDEX); + final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); + assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)); + + final int noUse = 0; + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, + noUse); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId)); + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(1)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(eventWindowId), + a11yWindowChanges( + AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); + } + + @Test + public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary() + throws RemoteException { + runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest( + Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID); + } + + @Test + public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault() + throws RemoteException { + runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest( + SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY); + } + + private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest( + int initialDisplayId, int eventDisplayId) throws RemoteException { + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + final int initialWindowId = getWindowIdFromWindowInfosForDisplay( + initialDisplayId, DEFAULT_FOCUSED_INDEX); + final int noUse = 0; + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + initialWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, + noUse); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId)); + Mockito.reset(mMockA11yEventSender); + + final int eventWindowId = getWindowIdFromWindowInfosForDisplay( + eventDisplayId, DEFAULT_FOCUSED_INDEX); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, + noUse); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId)); + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(2)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(initialDisplayId), + a11yWindowId(initialWindowId), + a11yWindowChanges( + AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); + assertThat(captor.getAllValues().get(1), + allOf(displayId(eventDisplayId), + a11yWindowId(eventWindowId), + a11yWindowChanges( + AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); + } + + @Test + public void updateActiveAndA11yFocusedWindow_clearA11yFocusEvent_shouldClearA11yFocus() { + final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, + DEFAULT_FOCUSED_INDEX); + final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); + assertThat(currentA11yFocusedWindowId, is(not(eventWindowId))); + + final int noUse = 0; + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, + noUse); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId)); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, + noUse); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), + is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)); + } + + @Test + public void onTouchInteractionEnd_shouldRollbackActiveWindow() { + final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, + DEFAULT_FOCUSED_INDEX + 1); + final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID); + assertThat(currentActiveWindowId, is(not(eventWindowId))); + + final int noUse = 0; + mA11yWindowManager.onTouchInteractionStart(); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + eventWindowId, + noUse, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, + noUse); + assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId)); + // AccessibilityEventSender is invoked after active window changed. Reset it. + Mockito.reset(mMockA11yEventSender); + + mA11yWindowManager.onTouchInteractionEnd(); + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(2)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(eventWindowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); + assertThat(captor.getAllValues().get(1), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(currentActiveWindowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); + } + + @Test + public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus() + throws RemoteException { + final IBinder defaultFocusWinToken = + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token; + final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, defaultFocusWinToken); + when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates()) + .thenReturn(defaultFocusWinToken); + final int newFocusWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, + DEFAULT_FOCUSED_INDEX + 1); + final IAccessibilityInteractionConnection mockNewFocusConnection = + mA11yWindowManager.getConnectionLocked( + USER_SYSTEM_ID, newFocusWindowId).getRemote(); + + mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY); + final int noUse = 0; + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + defaultFocusWindowId, + noUse, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + noUse); + assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(defaultFocusWindowId)); + assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT), + is(defaultFocusWindowId)); + + mA11yWindowManager.onTouchInteractionStart(); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + newFocusWindowId, + noUse, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, + noUse); + mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID, + newFocusWindowId, + AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, + noUse); + assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(newFocusWindowId)); + assertThat(mA11yWindowManager.getFocusedWindowId( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(newFocusWindowId)); + + mA11yWindowManager.onTouchInteractionEnd(); + mHandler.sendLastMessage(); + verify(mockNewFocusConnection).clearAccessibilityFocus(); + } + + @Test + public void getPictureInPictureWindow_shouldNotNull() { + assertNull(mA11yWindowManager.getPictureInPictureWindowLocked()); + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true; + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked()); + } + + @Test + public void notifyOutsideTouch() throws RemoteException { + final int targetWindowId = + getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 1); + final int outsideWindowId = + getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0); + final IAccessibilityInteractionConnection mockRemoteConnection = + mA11yWindowManager.getConnectionLocked( + USER_SYSTEM_ID, outsideWindowId).getRemote(); + mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true; + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId); + verify(mockRemoteConnection).notifyOutsideTouch(); + } + + @Test + public void addAccessibilityInteractionConnection_profileUser_findInParentUser() + throws RemoteException { + final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, USER_PROFILE); + final int windowId = mA11yWindowManager.findWindowIdLocked( + USER_PROFILE_PARENT, token.asBinder()); + assertTrue(windowId >= 0); + } + + @Test + public void getDisplayList() throws RemoteException { + // Starts tracking window of second display. + startTrackingPerDisplay(SECONDARY_DISPLAY_ID); + + final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked( + DISPLAY_TYPE_DEFAULT); + assertTrue(displayList.equals(mExpectedDisplayList)); + } + + @Test + public void setAccessibilityWindowIdToSurfaceMetadata() + throws RemoteException { + final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + true, USER_SYSTEM_ID); + int windowId = -1; + for (int i = 0; i < mA11yWindowTokens.size(); i++) { + if (mA11yWindowTokens.valueAt(i).equals(token)) { + windowId = mA11yWindowTokens.keyAt(i); + } + } + assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId); + verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata( + token.asBinder(), windowId); + + mA11yWindowManager.removeAccessibilityInteractionConnection(token); + verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata( + 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.getLeashTokenLocked(HOST_WINDOW_ID); + assertEquals(token, mMockHostToken); + } + + @Test + public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() { + final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID); + assertNull(token); + } + + @Test + public void setAccessibilityWindowAttributes_windowIsNotRegistered_titleIsChanged() { + final int windowId = + getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0); + final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.accessibilityTitle = "accessibility window title"; + final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes( + layoutParams, new LocaleList()); + + mA11yWindowManager.setAccessibilityWindowAttributes(Display.DEFAULT_DISPLAY, windowId, + USER_SYSTEM_ID, attributes); + + final AccessibilityWindowInfo a11yWindow = mA11yWindowManager.findA11yWindowInfoByIdLocked( + windowId); + assertEquals(toString(layoutParams.accessibilityTitle), toString(a11yWindow.getTitle())); + } + + @Test + public void sendAccessibilityEventOnWindowRemoval() { + final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY); + + // Removing index 0 because it's not focused, and avoids unnecessary layer change. + final int windowId = + getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0); + infos.remove(0); + for (WindowInfo info : infos) { + // Adjust layer number because it should start from 0. + info.layer--; + } + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND); + + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(1)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(windowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED))); + } + + @Test + public void sendAccessibilityEventOnWindowAddition() throws RemoteException { + final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY); + + for (WindowInfo info : infos) { + // Adjust layer number because new window will have 0 so that layer number in + // A11yWindowInfo in window won't be changed. + info.layer++; + } + + final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, USER_SYSTEM_ID); + addWindowInfo(infos, token, 0); + final int windowId = + getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND); + + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(1)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(windowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED))); + } + + @Test + public void sendAccessibilityEventOnWindowChange() { + final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY); + infos.get(0).title = "new title"; + final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0); + + onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND); + + final ArgumentCaptor<AccessibilityEvent> captor = + ArgumentCaptor.forClass(AccessibilityEvent.class); + verify(mMockA11yEventSender, times(1)) + .sendAccessibilityEventForCurrentUserLocked(captor.capture()); + assertThat(captor.getAllValues().get(0), + allOf(displayId(Display.DEFAULT_DISPLAY), + a11yWindowId(windowId), + a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE))); + } + + private void registerLeashedTokenAndWindowId() { + mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID); + mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID); + } + + private void startTrackingPerDisplay(int displayId) throws RemoteException { + ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>(); + // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy + // mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos + // for the test. + int layer = 0; + for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) { + final IWindow token = addAccessibilityInteractionConnection(displayId, + true, USER_SYSTEM_ID); + addWindowInfo(windowInfosForDisplay, token, layer++); + + } + for (int i = 0; i < NUM_APP_WINDOWS; i++) { + final IWindow token = addAccessibilityInteractionConnection(displayId, + false, USER_SYSTEM_ID); + addWindowInfo(windowInfosForDisplay, token, layer++); + } + // Sets up current focused window of display. + // Each display has its own current focused window if config_perDisplayFocusEnabled is true. + // Otherwise only default display needs to current focused window. + if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) { + windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true; + } + // Turns on windows tracking, and update window info. + mA11yWindowManager.startTrackingWindows(displayId, false); + // Puts window lists into array. + mWindowInfos.put(displayId, windowInfosForDisplay); + // Sets the default display is the top focused display and + // its current focused window is the top focused window. + if (displayId == Display.DEFAULT_DISPLAY) { + setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX); + } + // Invokes callback for sending window lists to A11y framework. + onWindowsForAccessibilityChanged(displayId, FORCE_SEND); + + assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(), + windowInfosForDisplay.size()); + } + + private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) { + ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor = + ArgumentCaptor.forClass( + WindowsForAccessibilityCallback.class); + verify(mMockWindowManagerInternal) + .setWindowsForAccessibilityCallback(eq(displayId), + windowsForAccessibilityCallbacksCaptor.capture()); + return windowsForAccessibilityCallbacksCaptor.getValue(); + } + + private IWindow addAccessibilityInteractionConnection(int displayId, boolean bGlobal, + 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); + final IBinder mockLeashToken = Mockito.mock(IBinder.class); + when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); + when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); + when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) + .thenReturn(bGlobal); + when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder)) + .thenReturn(displayId); + + int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( + 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(mockWindowBinder)) + .thenReturn(displayId); + + int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( + mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId); + mA11yWindowTokens.put(windowId, mockWindowToken); + return windowId; + } + + private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) { + final WindowInfo windowInfo = WindowInfo.obtain(); + windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION; + windowInfo.token = windowToken.asBinder(); + windowInfo.layer = layer; + windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + windowInfos.add(windowInfo); + } + + private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) { + final IBinder windowToken = mWindowInfos.get(displayId).get(index).token; + return mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, windowToken); + } + + private void setTopFocusedWindowAndDisplay(int displayId, int index) { + // Sets the top focus window. + mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token; + // Sets the top focused display. + mTopFocusedDisplayId = displayId; + } + + private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) { + WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId); + if (callbacks == null) { + callbacks = getWindowsForAccessibilityCallbacks(displayId); + mCallbackOfWindows.put(displayId, callbacks); + } + callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId, + mTopFocusedWindowToken, mWindowInfos.get(displayId)); + } + + private void changeFocusedWindowOnDisplayPerDisplayFocusConfig( + int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId, + int oldFocusedWindowIndex) { + if (mSupportPerDisplayFocus) { + // Gets the old focused window of display which wants to change focused window. + WindowInfo focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex); + // Resets the focus of old focused window. + focusedWindowInfo.focused = false; + // Gets the new window of display which wants to change focused window. + focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex); + // Sets the focus of new focused window. + focusedWindowInfo.focused = true; + } else { + // Gets the window of display which wants to change focused window. + WindowInfo focusedWindowInfo = + mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex); + // Sets the focus of new focused window. + focusedWindowInfo.focused = true; + // Gets the old focused window of old top focused display. + focusedWindowInfo = + mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex); + // Resets the focus of old focused window. + focusedWindowInfo.focused = false; + // Changes the top focused display and window. + setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex); + } + } + + @Nullable + private static String toString(@Nullable CharSequence cs) { + return cs == null ? null : cs.toString(); + } + + static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { + private final int mDisplayId; + + DisplayIdMatcher(int displayId) { + super(); + mDisplayId = displayId; + } + + static DisplayIdMatcher displayId(int displayId) { + return new DisplayIdMatcher(displayId); + } + + @Override + protected boolean matchesSafely(AccessibilityEvent event) { + return event.getDisplayId() == mDisplayId; + } + + @Override + public void describeTo(Description description) { + description.appendText("Matching to displayId " + mDisplayId); + } + } + + static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { + private int mWindowId; + + WindowIdMatcher(int windowId) { + super(); + mWindowId = windowId; + } + + static WindowIdMatcher a11yWindowId(int windowId) { + return new WindowIdMatcher(windowId); + } + + @Override + protected boolean matchesSafely(AccessibilityEvent event) { + return event.getWindowId() == mWindowId; + } + + @Override + public void describeTo(Description description) { + description.appendText("Matching to windowId " + mWindowId); + } + } + + static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> { + private int mWindowChanges; + + WindowChangesMatcher(int windowChanges) { + super(); + mWindowChanges = windowChanges; + } + + static WindowChangesMatcher a11yWindowChanges(int windowChanges) { + return new WindowChangesMatcher(windowChanges); + } + + @Override + protected boolean matchesSafely(AccessibilityEvent event) { + return event.getWindowChanges() == mWindowChanges; + } + + @Override + public void describeTo(Description description) { + description.appendText("Matching to window changes " + mWindowChanges); + } + } +}