blob: 7855ef9c49ad34904d9b72f2ad0f619d3989573c [file] [log] [blame]
/*
* Copyright (C) 2018 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.view;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Service;
import android.app.UiAutomation;
import android.graphics.Rect;
import android.os.SystemClock;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityTestActivity;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.TestUtils;
import com.android.frameworks.coretests.R;
import org.junit.After;
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;
@RunWith(AndroidJUnit4.class)
public class AccessibilityInteractionControllerTest {
static final long TIMEOUT_DEFAULT = 10000; // 10 seconds
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
@Rule
public ActivityTestRule<AccessibilityTestActivity> mActivityRule = new ActivityTestRule<>(
AccessibilityTestActivity.class, false, false);
private AccessibilityInteractionController mAccessibilityInteractionController;
private ViewRootImpl mViewRootImpl;
private View mButton;
@BeforeClass
public static void oneTimeSetup() {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation();
}
@AfterClass
public static void postTestTearDown() {
sUiAutomation.destroy();
}
@Before
public void setUp() throws Throwable {
launchActivity();
enableTouchExploration(true);
mActivityRule.runOnUiThread(() -> {
mViewRootImpl = mActivityRule.getActivity().getWindow().getDecorView()
.getViewRootImpl();
mButton = mActivityRule.getActivity().findViewById(R.id.appNameBtn);
});
mAccessibilityInteractionController =
mViewRootImpl.getAccessibilityInteractionController();
}
@After
public void tearDown() {
enableTouchExploration(false);
}
@Test
public void clearAccessibilityFocus_shouldClearFocus() throws Exception {
performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
assertTrue("Button should have a11y focus",
mButton.isAccessibilityFocused());
mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
sInstrumentation.waitForIdleSync();
assertFalse("Button should not have a11y focus",
mButton.isAccessibilityFocused());
}
@Test
public void clearAccessibilityFocus_uiThread_shouldClearFocus() throws Exception {
performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
assertTrue("Button should have a11y focus",
mButton.isAccessibilityFocused());
sInstrumentation.runOnMainSync(() -> {
mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
});
assertFalse("Button should not have a11y focus",
mButton.isAccessibilityFocused());
}
private void launchActivity() {
final Object waitObject = new Object();
final int[] location = new int[2];
final StringBuilder activityPackage = new StringBuilder();
final Rect bounds = new Rect();
final StringBuilder activityTitle = new StringBuilder();
try {
final long executionStartTimeMillis = SystemClock.uptimeMillis();
sUiAutomation.setOnAccessibilityEventListener((event) -> {
if (event.getEventTime() < executionStartTimeMillis) {
return;
}
synchronized (waitObject) {
waitObject.notifyAll();
}
});
enableRetrieveAccessibilityWindows();
final Activity activity = mActivityRule.launchActivity(null);
sInstrumentation.runOnMainSync(() -> {
activity.getWindow().getDecorView().getLocationOnScreen(location);
activityPackage.append(activity.getPackageName());
activityTitle.append(activity.getTitle());
});
sInstrumentation.waitForIdleSync();
TestUtils.waitOn(waitObject, () -> {
final AccessibilityWindowInfo window = findWindowByTitle(activityTitle);
if (window == null) return false;
window.getBoundsInScreen(bounds);
activity.getWindow().getDecorView().getLocationOnScreen(location);
if (bounds.isEmpty()) {
return false;
}
return (!bounds.isEmpty())
&& (bounds.left == location[0]) && (bounds.top == location[1]);
}, TIMEOUT_DEFAULT, "Launch Activity");
} finally {
sUiAutomation.setOnAccessibilityEventListener(null);
}
}
private void enableRetrieveAccessibilityWindows() {
AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
sUiAutomation.setServiceInfo(info);
}
private void enableTouchExploration(boolean enabled) {
final Object waitObject = new Object();
final AccessibilityManager accessibilityManager =
(AccessibilityManager) sInstrumentation.getContext().getSystemService(
Service.ACCESSIBILITY_SERVICE);
final AccessibilityManager.TouchExplorationStateChangeListener listener = status -> {
synchronized (waitObject) {
waitObject.notifyAll();
}
};
try {
accessibilityManager.addTouchExplorationStateChangeListener(listener);
final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
if (enabled) {
info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
} else {
info.flags &= ~AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
}
sUiAutomation.setServiceInfo(info);
TestUtils.waitOn(waitObject,
() -> accessibilityManager.isTouchExplorationEnabled() == enabled,
TIMEOUT_DEFAULT,
(enabled ? "Enable" : "Disable") + "touch exploration");
} finally {
accessibilityManager.removeTouchExplorationStateChangeListener(listener);
}
}
private void performAccessibilityFocus(String viewId) throws TimeoutException {
final AccessibilityNodeInfo node = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(viewId).get(0);
// Perform an action and wait for an event
sUiAutomation.executeAndWaitForEvent(
() -> node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS),
event -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
TIMEOUT_DEFAULT);
node.refresh();
}
private AccessibilityWindowInfo findWindowByTitle(CharSequence title) {
final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
AccessibilityWindowInfo returnValue = null;
for (int i = 0; i < windows.size(); i++) {
final AccessibilityWindowInfo window = windows.get(i);
if (TextUtils.equals(title, window.getTitle())) {
returnValue = window;
} else {
window.recycle();
}
}
return returnValue;
}
}