blob: e9f3f52f8bd728468fc97113772abdb106eac06a [file] [log] [blame]
/*
* Copyright (C) 2016 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.accessibilityservice.cts;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_CHILDREN;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_REMOVED;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_TITLE;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* Tests that window changes produce the correct events and that AccessibilityWindowInfos are
* properly populated
*/
@RunWith(AndroidJUnit4.class)
public class AccessibilityWindowReportingTest {
private static final int TIMEOUT_ASYNC_PROCESSING = 5000;
private static final CharSequence TOP_WINDOW_TITLE =
"android.accessibilityservice.cts.AccessibilityWindowReportingTest.TOP_WINDOW_TITLE";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private Activity mActivity;
private CharSequence mActivityTitle;
@Rule
public ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation();
AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
sUiAutomation.setServiceInfo(info);
}
@AfterClass
public static void finalTearDown() throws Exception {
sUiAutomation.destroy();
}
@Before
public void setUp() throws Exception {
mActivity = launchActivityAndWaitForItToBeOnscreen(
sInstrumentation, sUiAutomation, mActivityRule);
mActivityTitle = getActivityTitle(sInstrumentation, mActivity);
}
@Test
public void testUpdatedWindowTitle_generatesEventAndIsReturnedByGetTitle() {
final String updatedTitle = "Updated Title";
try {
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> mActivity.setTitle(updatedTitle)),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_TITLE),
TIMEOUT_ASYNC_PROCESSING);
} catch (TimeoutException exception) {
throw new RuntimeException(
"Failed to get windows changed event for title update", exception);
}
final AccessibilityWindowInfo window = findWindowByTitle(sUiAutomation, updatedTitle);
assertNotNull("Updated window title not reported to accessibility", window);
window.recycle();
}
@Test
public void testWindowAddedMovedAndRemoved_generatesEventsForAllThree() throws Exception {
final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
final WindowManager.LayoutParams paramsForBottom = layoutParmsForWindowOnBottom();
final Button button = new Button(mActivity);
button.setText(R.string.button1);
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> mActivity.getWindowManager().addView(button, paramsForTop)),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
TIMEOUT_ASYNC_PROCESSING);
// Move window from top to bottom
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> mActivity.getWindowManager().updateViewLayout(button, paramsForBottom)),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_BOUNDS),
TIMEOUT_ASYNC_PROCESSING);
// Remove the view
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> mActivity.getWindowManager().removeView(button)),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED),
TIMEOUT_ASYNC_PROCESSING);
}
@Test
public void putWindowInPictureInPicture_generatesEventAndReportsProperty() throws Exception {
if (!sInstrumentation.getContext().getPackageManager()
.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
return;
}
sUiAutomation.executeAndWaitForEvent(
() -> sInstrumentation.runOnMainSync(() -> mActivity.enterPictureInPictureMode()),
(event) -> {
if (event.getEventType() != TYPE_WINDOWS_CHANGED) return false;
// Look for a picture-in-picture window
final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
if (windows.get(i).isInPictureInPictureMode()) {
return true;
}
}
return false;
}, TIMEOUT_ASYNC_PROCESSING);
// There should be exactly one picture-in-picture window now
int numPictureInPictureWindows = 0;
final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
final AccessibilityWindowInfo window = windows.get(i);
if (window.isInPictureInPictureMode()) {
numPictureInPictureWindows++;
}
}
assertTrue(numPictureInPictureWindows >= 1);
}
@Test
public void moveFocusToAnotherWindow_generatesEventsAndMovesActiveAndFocus() throws Exception {
final View topWindowView = showTopWindowAndWaitForItToShowUp();
final AccessibilityWindowInfo topWindow =
findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
final AccessibilityNodeInfo buttonNode =
topWindow.getRoot().findAccessibilityNodeInfosByText(
sInstrumentation.getContext().getString(R.string.button1)).get(0);
// Make sure activityWindow is not focused
if (activityWindow.isFocused()) {
sUiAutomation.executeAndWaitForEvent(
() -> buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED),
TIMEOUT_ASYNC_PROCESSING);
}
// Windows may have changed - refresh
activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
assertFalse(activityWindow.isActive());
assertFalse(activityWindow.isFocused());
// Find a focusable view in the main activity menu
final AccessibilityNodeInfo autoCompleteTextInfo = activityWindow.getRoot()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/autoCompleteLayout")
.get(0);
// Remove the top window and focus on the main activity
sUiAutomation.executeAndWaitForEvent(
() -> {
sInstrumentation.runOnMainSync(
() -> mActivity.getWindowManager().removeView(topWindowView));
buttonNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
},
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED | WINDOWS_CHANGE_ACTIVE),
TIMEOUT_ASYNC_PROCESSING);
}
@Test
public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
sUiAutomation.setServiceInfo(info);
try {
showTopWindowAndWaitForItToShowUp();
final AccessibilityWindowInfo activityWindow =
findWindowByTitle(sUiAutomation, mActivityTitle);
final AccessibilityWindowInfo topWindow =
findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE);
final AccessibilityNodeInfo win2Node =
topWindow.getRoot().findAccessibilityNodeInfosByText(
sInstrumentation.getContext().getString(R.string.button1)).get(0);
final AccessibilityNodeInfo win1Node = activityWindow.getRoot()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/autoCompleteLayout")
.get(0);
sUiAutomation.executeAndWaitForEvent(
() -> {
win2Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
win1Node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
},
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
TIMEOUT_ASYNC_PROCESSING);
} finally {
info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
sUiAutomation.setServiceInfo(info);
}
}
@Test
public void testGetAnchorForDropDownForAutoCompleteTextView_returnsTextViewNode() {
final AutoCompleteTextView autoCompleteTextView =
(AutoCompleteTextView) mActivity.findViewById(R.id.autoCompleteLayout);
final AccessibilityNodeInfo autoCompleteTextInfo = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/autoCompleteLayout")
.get(0);
// For the drop-down
final String[] COUNTRIES = new String[] {"Belgium", "France", "Italy", "Germany", "Spain"};
try {
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> {
final ArrayAdapter<String> adapter = new ArrayAdapter<>(
mActivity, android.R.layout.simple_dropdown_item_1line, COUNTRIES);
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.showDropDown();
}),
filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_CHILDREN),
TIMEOUT_ASYNC_PROCESSING);
} catch (TimeoutException exception) {
throw new RuntimeException(
"Failed to get window changed event when showing dropdown", exception);
}
// Find the pop-up window
boolean foundPopup = false;
final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
for (int i = 0; i < windows.size(); i++) {
final AccessibilityWindowInfo window = windows.get(i);
if (window.getAnchor() == null) {
continue;
}
assertEquals(autoCompleteTextInfo, window.getAnchor());
assertFalse("Found multiple pop-ups anchored to one text view", foundPopup);
foundPopup = true;
}
assertTrue("Failed to find accessibility window for auto-complete pop-up", foundPopup);
}
private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
final Button button = new Button(mActivity);
button.setText(R.string.button1);
sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
() -> mActivity.getWindowManager().addView(button, paramsForTop)),
(event) -> {
return (event.getEventType() == TYPE_WINDOWS_CHANGED)
&& (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
&& (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
},
TIMEOUT_ASYNC_PROCESSING);
return button;
}
private WindowManager.LayoutParams layoutParmsForWindowOnTop() {
final WindowManager.LayoutParams params = layoutParmsForTestWindow();
params.gravity = Gravity.TOP;
params.setTitle(TOP_WINDOW_TITLE);
sInstrumentation.runOnMainSync(() -> {
params.y = getStatusBarHeight(mActivity);
});
return params;
}
private WindowManager.LayoutParams layoutParmsForWindowOnBottom() {
final WindowManager.LayoutParams params = layoutParmsForTestWindow();
params.gravity = Gravity.BOTTOM;
return params;
}
private WindowManager.LayoutParams layoutParmsForTestWindow() {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
sInstrumentation.runOnMainSync(() -> {
params.token = mActivity.getWindow().getDecorView().getWindowToken();
});
return params;
}
}