blob: 324cf33c884226c4151bb5691b1fa403d3fec6f5 [file] [log] [blame]
package android.accessibilityservice.cts;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.test.InstrumentationTestCase;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
public class InstrumentedAccessibilityService extends AccessibilityService {
// Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
private static final String COMPONENT_NAME_SEPARATOR = ":";
private static final int TIMEOUT_SERVICE_ENABLE = 10000;
private static final int TIMEOUT_SERVICE_PERFORM_SYNC = 5000;
private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
sInstances = new HashMap<>();
private final Handler mHandler = new Handler();
final Object mInterruptWaitObject = new Object();
public boolean mOnInterruptCalled;
@Override
protected void onServiceConnected() {
synchronized (sInstances) {
sInstances.put(getClass(), new WeakReference<>(this));
sInstances.notifyAll();
}
}
@Override
public void onDestroy() {
synchronized (sInstances) {
sInstances.remove(getClass());
}
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// Stub method.
}
@Override
public void onInterrupt() {
synchronized (mInterruptWaitObject) {
mOnInterruptCalled = true;
mInterruptWaitObject.notifyAll();
}
}
public void disableSelfAndRemove() {
disableSelf();
synchronized (sInstances) {
sInstances.remove(getClass());
}
}
public void runOnServiceSync(Runnable runner) {
final SyncRunnable sr = new SyncRunnable(runner, TIMEOUT_SERVICE_PERFORM_SYNC);
mHandler.post(sr);
assertTrue("Timed out waiting for runOnServiceSync()", sr.waitForComplete());
}
public boolean wasOnInterruptCalled() {
synchronized (mInterruptWaitObject) {
return mOnInterruptCalled;
}
}
public Object getInterruptWaitObject() {
return mInterruptWaitObject;
}
private static final class SyncRunnable implements Runnable {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final Runnable mTarget;
private final long mTimeout;
public SyncRunnable(Runnable target, long timeout) {
mTarget = target;
mTimeout = timeout;
}
public void run() {
mTarget.run();
mLatch.countDown();
}
public boolean waitForComplete() {
try {
return mLatch.await(mTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
}
protected static <T extends InstrumentedAccessibilityService> T enableService(
Instrumentation instrumentation, Class<T> clazz) {
final String serviceName = clazz.getSimpleName();
final Context context = instrumentation.getContext();
final String enabledServices = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (enabledServices != null) {
assertFalse("Service is already enabled", enabledServices.contains(serviceName));
}
final AccessibilityManager manager = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> serviceInfos =
manager.getInstalledAccessibilityServiceList();
for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
final String serviceId = serviceInfo.getId();
if (serviceId.endsWith(serviceName)) {
ShellCommandBuilder.create(instrumentation)
.putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
.putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
.run();
final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
if (instance == null) {
ShellCommandBuilder.create(instrumentation)
.putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServices)
.run();
throw new RuntimeException("Starting accessibility service " + serviceName
+ " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms");
}
return instance;
}
}
throw new RuntimeException("Accessibility service " + serviceName + " not found");
}
private static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz,
long timeoutMillis) {
final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis;
while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
synchronized (sInstances) {
final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
if (ref != null) {
final T instance = (T) ref.get();
if (instance == null) {
sInstances.remove(clazz);
} else {
return instance;
}
}
try {
sInstances.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
} catch (InterruptedException e) {
return null;
}
}
}
return null;
}
public static void disableAllServices(Instrumentation instrumentation) {
final Object waitLockForA11yOff = new Object();
final Context context = instrumentation.getContext();
final AccessibilityManager manager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.addAccessibilityStateChangeListener(b -> {
synchronized (waitLockForA11yOff) {
waitLockForA11yOff.notifyAll();
}
});
ShellCommandBuilder.create(instrumentation)
.deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
.deleteSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED)
.run();
final long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_ENABLE;
while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
synchronized (waitLockForA11yOff) {
if (manager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
return;
}
try {
waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
} catch (InterruptedException e) {
// Ignored; loop again
}
}
}
throw new RuntimeException("Disabling all accessibility services took longer than "
+ TIMEOUT_SERVICE_ENABLE + "ms");
}
}