Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.accessibilityservice.cts;
     18 
     19 import android.accessibilityservice.AccessibilityService;
     20 import android.accessibilityservice.AccessibilityServiceInfo;
     21 import android.app.Instrumentation;
     22 import android.content.Context;
     23 import android.os.Handler;
     24 import android.os.SystemClock;
     25 import android.provider.Settings;
     26 import androidx.annotation.CallSuper;
     27 import android.test.InstrumentationTestCase;
     28 import android.view.accessibility.AccessibilityEvent;
     29 import android.view.accessibility.AccessibilityManager;
     30 
     31 import java.lang.ref.WeakReference;
     32 import java.util.HashMap;
     33 import java.util.List;
     34 import java.util.concurrent.CountDownLatch;
     35 import java.util.concurrent.TimeUnit;
     36 
     37 import static junit.framework.Assert.assertFalse;
     38 import static junit.framework.Assert.assertTrue;
     39 
     40 public class InstrumentedAccessibilityService extends AccessibilityService {
     41 
     42     private static final boolean DEBUG = false;
     43 
     44     // Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
     45     private static final String COMPONENT_NAME_SEPARATOR = ":";
     46 
     47     // Timeout disabled in #DEBUG mode to prevent breakpoint-related failures
     48     private static final int TIMEOUT_SERVICE_ENABLE = DEBUG ? Integer.MAX_VALUE : 10000;
     49     private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
     50 
     51     private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
     52             sInstances = new HashMap<>();
     53 
     54     private final Handler mHandler = new Handler();
     55     final Object mInterruptWaitObject = new Object();
     56     public boolean mOnInterruptCalled;
     57 
     58 
     59     @Override
     60     @CallSuper
     61     protected void onServiceConnected() {
     62         synchronized (sInstances) {
     63             sInstances.put(getClass(), new WeakReference<>(this));
     64             sInstances.notifyAll();
     65         }
     66     }
     67 
     68     @Override
     69     public void onDestroy() {
     70         synchronized (sInstances) {
     71             sInstances.remove(getClass());
     72         }
     73     }
     74 
     75     @Override
     76     public void onAccessibilityEvent(AccessibilityEvent event) {
     77         // Stub method.
     78     }
     79 
     80     @Override
     81     public void onInterrupt() {
     82         synchronized (mInterruptWaitObject) {
     83             mOnInterruptCalled = true;
     84             mInterruptWaitObject.notifyAll();
     85         }
     86     }
     87 
     88     public void disableSelfAndRemove() {
     89         disableSelf();
     90 
     91         synchronized (sInstances) {
     92             sInstances.remove(getClass());
     93         }
     94     }
     95 
     96     public void runOnServiceSync(Runnable runner) {
     97         final SyncRunnable sr = new SyncRunnable(runner, TIMEOUT_SERVICE_PERFORM_SYNC);
     98         mHandler.post(sr);
     99         assertTrue("Timed out waiting for runOnServiceSync()", sr.waitForComplete());
    100     }
    101 
    102     public boolean wasOnInterruptCalled() {
    103         synchronized (mInterruptWaitObject) {
    104             return mOnInterruptCalled;
    105         }
    106     }
    107 
    108     public Object getInterruptWaitObject() {
    109         return mInterruptWaitObject;
    110     }
    111 
    112     private static final class SyncRunnable implements Runnable {
    113         private final CountDownLatch mLatch = new CountDownLatch(1);
    114         private final Runnable mTarget;
    115         private final long mTimeout;
    116 
    117         public SyncRunnable(Runnable target, long timeout) {
    118             mTarget = target;
    119             mTimeout = timeout;
    120         }
    121 
    122         public void run() {
    123             mTarget.run();
    124             mLatch.countDown();
    125         }
    126 
    127         public boolean waitForComplete() {
    128             try {
    129                 return mLatch.await(mTimeout, TimeUnit.MILLISECONDS);
    130             } catch (InterruptedException e) {
    131                 return false;
    132             }
    133         }
    134     }
    135 
    136     protected static <T extends InstrumentedAccessibilityService> T enableService(
    137             Instrumentation instrumentation, Class<T> clazz) {
    138         final String serviceName = clazz.getSimpleName();
    139         final Context context = instrumentation.getContext();
    140         final String enabledServices = Settings.Secure.getString(
    141                 context.getContentResolver(),
    142                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
    143         if (enabledServices != null) {
    144             assertFalse("Service is already enabled", enabledServices.contains(serviceName));
    145         }
    146         final AccessibilityManager manager = (AccessibilityManager) context.getSystemService(
    147                 Context.ACCESSIBILITY_SERVICE);
    148         final List<AccessibilityServiceInfo> serviceInfos =
    149                 manager.getInstalledAccessibilityServiceList();
    150         for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
    151             final String serviceId = serviceInfo.getId();
    152             if (serviceId.endsWith(serviceName)) {
    153                 ShellCommandBuilder.create(instrumentation)
    154                         .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
    155                                 enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
    156                         .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
    157                         .run();
    158 
    159                 final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
    160                 if (instance == null) {
    161                     ShellCommandBuilder.create(instrumentation)
    162                             .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
    163                                     enabledServices)
    164                             .run();
    165                     throw new RuntimeException("Starting accessibility service " + serviceName
    166                             + " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms");
    167                 }
    168                 return instance;
    169             }
    170         }
    171         throw new RuntimeException("Accessibility service " + serviceName + " not found");
    172     }
    173 
    174     private static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz,
    175             long timeoutMillis) {
    176         final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis;
    177         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
    178             synchronized (sInstances) {
    179                 final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
    180                 if (ref != null) {
    181                     final T instance = (T) ref.get();
    182                     if (instance == null) {
    183                         sInstances.remove(clazz);
    184                     } else {
    185                         return instance;
    186                     }
    187                 }
    188                 try {
    189                     sInstances.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
    190                 } catch (InterruptedException e) {
    191                     return null;
    192                 }
    193             }
    194         }
    195         return null;
    196     }
    197 
    198     public static void disableAllServices(Instrumentation instrumentation) {
    199         final Object waitLockForA11yOff = new Object();
    200         final Context context = instrumentation.getContext();
    201         final AccessibilityManager manager =
    202                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
    203         manager.addAccessibilityStateChangeListener(b -> {
    204             synchronized (waitLockForA11yOff) {
    205                 waitLockForA11yOff.notifyAll();
    206             }
    207         });
    208 
    209         ShellCommandBuilder.create(instrumentation)
    210                 .deleteSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
    211                 .deleteSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED)
    212                 .run();
    213 
    214         final long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_ENABLE;
    215         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
    216             synchronized (waitLockForA11yOff) {
    217                 if (manager.getEnabledAccessibilityServiceList(
    218                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
    219                     return;
    220                 }
    221                 try {
    222                     waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
    223                 } catch (InterruptedException e) {
    224                     // Ignored; loop again
    225                 }
    226             }
    227         }
    228         throw new RuntimeException("Disabling all accessibility services took longer than "
    229                 + TIMEOUT_SERVICE_ENABLE + "ms");
    230     }
    231 }
    232