| 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"); |
| } |
| } |