Home | History | Annotate | Download | only in cts
      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