1 package android.accessibilityservice.cts; 2 3 import android.accessibilityservice.AccessibilityService; 4 import android.accessibilityservice.AccessibilityServiceInfo; 5 import android.content.Context; 6 import android.os.Handler; 7 import android.os.SystemClock; 8 import android.provider.Settings; 9 import android.test.InstrumentationTestCase; 10 import android.view.accessibility.AccessibilityEvent; 11 import android.view.accessibility.AccessibilityManager; 12 13 import java.lang.ref.WeakReference; 14 import java.util.HashMap; 15 import java.util.List; 16 import java.util.concurrent.CountDownLatch; 17 import java.util.concurrent.TimeUnit; 18 19 import static junit.framework.Assert.assertFalse; 20 import static junit.framework.Assert.assertTrue; 21 22 public class InstrumentedAccessibilityService extends AccessibilityService { 23 // Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR 24 private static final String COMPONENT_NAME_SEPARATOR = ":"; 25 26 private static final int TIMEOUT_SERVICE_ENABLE = 10000; 27 private static final int TIMEOUT_SERVICE_PERFORM_SYNC = 5000; 28 29 private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>> 30 sInstances = new HashMap<>(); 31 32 private final Handler mHandler = new Handler(); 33 34 @Override 35 protected void onServiceConnected() { 36 synchronized (sInstances) { 37 sInstances.put(getClass(), new WeakReference<>(this)); 38 sInstances.notifyAll(); 39 } 40 } 41 42 @Override 43 public void onDestroy() { 44 synchronized (sInstances) { 45 sInstances.remove(getClass()); 46 } 47 } 48 49 @Override 50 public void onAccessibilityEvent(AccessibilityEvent event) { 51 // Stub method. 52 } 53 54 @Override 55 public void onInterrupt() { 56 // Stub method. 57 } 58 59 public void disableSelfAndRemove() { 60 disableSelf(); 61 62 synchronized (sInstances) { 63 sInstances.remove(getClass()); 64 } 65 } 66 67 public void runOnServiceSync(Runnable runner) { 68 final SyncRunnable sr = new SyncRunnable(runner, TIMEOUT_SERVICE_PERFORM_SYNC); 69 mHandler.post(sr); 70 assertTrue("Timed out waiting for runOnServiceSync()", sr.waitForComplete()); 71 } 72 73 private static final class SyncRunnable implements Runnable { 74 private final CountDownLatch mLatch = new CountDownLatch(1); 75 private final Runnable mTarget; 76 private final long mTimeout; 77 78 public SyncRunnable(Runnable target, long timeout) { 79 mTarget = target; 80 mTimeout = timeout; 81 } 82 83 public void run() { 84 mTarget.run(); 85 mLatch.countDown(); 86 } 87 88 public boolean waitForComplete() { 89 try { 90 return mLatch.await(mTimeout, TimeUnit.MILLISECONDS); 91 } catch (InterruptedException e) { 92 return false; 93 } 94 } 95 } 96 97 protected static <T extends InstrumentedAccessibilityService> T enableService( 98 InstrumentationTestCase testCase, Class<T> clazz) { 99 final String serviceName = clazz.getSimpleName(); 100 final Context context = testCase.getInstrumentation().getContext(); 101 final String enabledServices = Settings.Secure.getString( 102 context.getContentResolver(), 103 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 104 if (enabledServices != null) { 105 assertFalse("Service is already enabled", enabledServices.contains(serviceName)); 106 } 107 108 final AccessibilityManager manager = (AccessibilityManager) context.getSystemService( 109 Context.ACCESSIBILITY_SERVICE); 110 final List<AccessibilityServiceInfo> serviceInfos = 111 manager.getInstalledAccessibilityServiceList(); 112 for (AccessibilityServiceInfo serviceInfo : serviceInfos) { 113 final String serviceId = serviceInfo.getId(); 114 if (serviceId.endsWith(serviceName)) { 115 ShellCommandBuilder.create(testCase) 116 .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 117 enabledServices + COMPONENT_NAME_SEPARATOR + serviceId) 118 .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1") 119 .run(); 120 121 final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE); 122 if (instance == null) { 123 ShellCommandBuilder.create(testCase) 124 .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 125 enabledServices) 126 .run(); 127 throw new RuntimeException("Starting accessibility service " + serviceName 128 + " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms"); 129 } 130 return instance; 131 } 132 } 133 throw new RuntimeException("Accessibility service " + serviceName + " not found"); 134 } 135 136 private static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz, 137 long timeoutMillis) { 138 final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis; 139 while (SystemClock.uptimeMillis() < timeoutTimeMillis) { 140 synchronized (sInstances) { 141 final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz); 142 if (ref != null) { 143 final T instance = (T) ref.get(); 144 if (instance == null) { 145 sInstances.remove(clazz); 146 } else { 147 return instance; 148 } 149 } 150 try { 151 sInstances.wait(timeoutTimeMillis - SystemClock.uptimeMillis()); 152 } catch (InterruptedException e) { 153 return null; 154 } 155 } 156 } 157 return null; 158 } 159 160 public static void disableAllServices(InstrumentationTestCase testCase) { 161 final Object waitLockForA11yOff = new Object(); 162 final Context context = testCase.getInstrumentation().getContext(); 163 final AccessibilityManager manager = 164 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 165 manager.addAccessibilityStateChangeListener(b -> { 166 synchronized (waitLockForA11yOff) { 167 waitLockForA11yOff.notifyAll(); 168 } 169 }); 170 171 ShellCommandBuilder.create(testCase) 172 .deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) 173 .deleteSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED) 174 .run(); 175 176 final long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_ENABLE; 177 while (SystemClock.uptimeMillis() < timeoutTimeMillis) { 178 synchronized (waitLockForA11yOff) { 179 if (manager.getEnabledAccessibilityServiceList( 180 AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) { 181 return; 182 } 183 try { 184 waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis()); 185 } catch (InterruptedException e) { 186 // Ignored; loop again 187 } 188 } 189 } 190 throw new RuntimeException("Disabling all accessibility services took longer than " 191 + TIMEOUT_SERVICE_ENABLE + "ms"); 192 } 193 } 194