Remove manual steps from the accessibility CTS tests.

1. Added a host side test that serves as a runer for the CTS
   test which are instrumentation tests. The runner is responsible
   for installing all needed packages, enabling accessibility
   and the delegating accessibility service via shell commands,
   running the instrumentation tests on the device, and cleaning up.
   This test is pretty much a driver for running the remote tests.
   To fit the current state of the CTS test framework the driver
   has to declare as empty test method all remotely run tests so
   description of these tests to be added in the CTS test description
   XML because CTS allows only reporting results of declared tests.

2. Updated the JarHost test to use the same class loader for each
   method of a test it ruts to enable sharing static state between
   test methods - this is useful to avoid doing expensive operations
   before every test.

bug:5946699

Change-Id: I41f4016c386c16c2ce517641f5b18045f67f46f1
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 76eb814..50124b2 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -27,6 +27,7 @@
 cts_support_packages := \
 	CtsAccelerationTestStubs \
 	CtsDelegatingAccessibilityService \
+	CtsAccessibilityServiceTestCases \
 	CtsDeviceAdmin \
 	CtsMonkeyApp \
 	CtsMonkeyApp2 \
@@ -47,7 +48,6 @@
 # Test packages that require an associated test package XML.
 cts_test_packages := \
 	CtsAccelerationTestCases \
-	CtsAccessibilityServiceTestCases \
 	CtsAccountManagerTestCases \
 	CtsAdminTestCases \
 	CtsAnimationTestCases \
@@ -98,7 +98,8 @@
 # Host side only tests
 cts_host_libraries := \
 	CtsAppSecurityTests \
-	CtsMonkeyTestCases
+	CtsMonkeyTestCases \
+	CtsAccessibilityServiceTestRunner \
 
 # Native test executables that need to have associated test XMLs.
 cts_native_exes := \
diff --git a/hostsidetests/accessibilityservice/Android.mk b/hostsidetests/accessibilityservice/Android.mk
new file mode 100644
index 0000000..e79c40b
--- /dev/null
+++ b/hostsidetests/accessibilityservice/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsAccessibilityServiceTestRunner
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt ddmlib-prebuilt junit
+
+LOCAL_CTS_TEST_PACKAGE := android.accessibilityservice
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceTestsRunnerTest.java b/hostsidetests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceTestsRunnerTest.java
new file mode 100644
index 0000000..9b1e951
--- /dev/null
+++ b/hostsidetests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceTestsRunnerTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestResult;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Running the accessibility tests requires modification of secure
+ * settings. Secure settings cannot be changed from device CTS tests
+ * since system signature permission is required. Such settings can
+ * be modified by the shell user, so a host side test is used for
+ * enabling accessibility, installing, and running the accessibility
+ * instrumentation tests.
+ */
+public class AccessibilityServiceTestsRunnerTest extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME =
+        "android.accessibilityservice.delegate";
+
+    private static final String ACCESSIBLITY_TESTS_PACKAGE_NAME =
+        "com.android.cts.accessibilityservice";
+
+    private static final String DELEGATING_ACCESSIBLITY_TESTS_SERVICE_NAME =
+        "android.accessibilityservice.delegate.DelegatingAccessibilityService";
+
+    private static final String DELEGATING_ACCESSIBLITY_SERVICE_APK =
+        "CtsDelegatingAccessibilityService.apk";
+
+    private static final String ACCESSIBLITY_TESTS_APK = "CtsAccessibilityServiceTestCases.apk";
+
+    private CtsBuildHelper mCtsBuildHelper;
+
+    private static boolean sTestsHaveRun;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuildHelper = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @Override
+    public void run(TestResult result) {
+        if (!sTestsHaveRun) {
+            sTestsHaveRun = true;
+            try {
+                installPackages();
+                enableAccessibilityAndDelegatingService();
+                runRemoteTests(result);
+                disableAccessibilityAndDelegatingService();
+                uninstallPackages();
+            } catch (DeviceNotAvailableException dnfe) {
+                /* ignore */
+            }
+        }
+    }
+
+    // The test runner accepts only results for known tests,
+    // so we have to declare locally all tests that are to
+    // be run remotely. I really really do not like this.
+
+    // AccessibilityWindowQueryActivityTest
+
+    public void testFindByText() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testFindByContentDescription() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTraverseWindow() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testPerformActionFocus()  {
+        /* do nothing - executed remotely */
+    }
+
+    public void testPerformActionClearFocus() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testPerformActionSelect() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testPerformActionClearSelection() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testGetEventSource() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testObjectContract() {
+        /* do nothing - executed remotely */
+    }
+
+    // AccessibilitySettingsTest
+
+    public void testAccessibilitySettingsIntentHandled() {
+        /* do nothing - executed remotely */
+    }
+
+    // AccessibilityServiceInfoTest
+
+    public void testMarshalling() {
+        /* do nothing - executed remotely */
+    }
+
+    // AccessibilityEndToEndTest
+
+    public void testTypeNotificationStateChangedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeViewClickedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeViewFocusedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeViewLongClickedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeViewSelectedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeViewTextChangedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    public void testTypeWindowStateChangedAccessibilityEvent() {
+        /* do nothing - executed remotely */
+    }
+
+    private void installPackages() throws DeviceNotAvailableException {
+        File delegatingServiceFile = FileUtil.getFileForPath(mCtsBuildHelper.getTestCasesDir(),
+                DELEGATING_ACCESSIBLITY_SERVICE_APK);
+        getDevice().installPackage(delegatingServiceFile, false);
+        File accessibilityTestsFile = FileUtil.getFileForPath(mCtsBuildHelper.getTestCasesDir(),
+                ACCESSIBLITY_TESTS_APK);
+        getDevice().installPackage(accessibilityTestsFile, false);
+    }
+
+    private void uninstallPackages() throws DeviceNotAvailableException {
+        getDevice().uninstallPackage(DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME);
+        getDevice().uninstallPackage(ACCESSIBLITY_TESTS_PACKAGE_NAME);
+    }
+
+    private void enableAccessibilityAndDelegatingService() throws DeviceNotAvailableException {
+        // The properties may not be in the database, therefore they are first removed
+        // and then added with the right value. This avoid inserting the same setting
+        // more than once and also avoid parsing the result of a query shell command.
+        String componentName = DELEGATING_ACCESSIBLITY_SERVICE_PACKAGE_NAME + "/"
+            + DELEGATING_ACCESSIBLITY_TESTS_SERVICE_NAME;
+        getDevice().executeShellCommand(
+                "content delete"
+                + " --uri content://settings/secure"
+                + " --where \"name='enabled_accessibility_services'\"");
+        getDevice().executeShellCommand(
+                "content insert"
+                + " --uri content://settings/secure"
+                + " --bind name:s:enabled_accessibility_services"
+                + " --bind value:s:" + componentName);
+        getDevice().executeShellCommand(
+                "content delete"
+                + " --uri content://settings/secure"
+                + " --where \"name='accessibility_enabled'\"");
+        getDevice().executeShellCommand(
+                "content insert"
+                + " --uri content://settings/secure"
+                + " --bind name:s:accessibility_enabled"
+                + " --bind value:i:1");
+    }
+
+    private void disableAccessibilityAndDelegatingService() throws DeviceNotAvailableException {
+        getDevice().executeShellCommand(
+                "content update"
+                + " --uri content://settings/secure"
+                + " --bind value:s:"
+                + " --where \"name='enabled_accessibility_services'\"");
+        getDevice().executeShellCommand(
+                "content update"
+                + " --uri content://settings/secure"
+                + " --bind value:s:0"
+                + " --where \"name='accessibility_enabled'\"");
+    }
+
+    private void runRemoteTests(final TestResult result)
+            throws DeviceNotAvailableException  {
+        RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
+                ACCESSIBLITY_TESTS_PACKAGE_NAME, getDevice().getIDevice());
+        getDevice().runInstrumentationTests(runner, new ITestRunListener() {
+            @Override
+            public void testStarted(final TestIdentifier test) {
+                setName(test.getTestName());
+                result.startTest(AccessibilityServiceTestsRunnerTest.this);
+            }
+
+            @Override
+            public void testRunStopped(long elapsedTime) {
+                /* do nothing */
+            }
+
+            @Override
+            public void testRunStarted(String runName, int testCount) {
+                /* do nothing */
+            }
+
+            @Override
+            public void testRunFailed(String errorMessage) {
+                /* do nothing */
+            }
+
+            @Override
+            public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+                /* do nothing */
+            }
+
+            @Override
+            public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+                setName(test.getTestName());
+                switch (status) {
+                    case FAILURE:
+                        result.addFailure(AccessibilityServiceTestsRunnerTest.this,
+                                new AssertionFailedError(trace));
+                        break;
+                    case ERROR:
+                        result.addError(AccessibilityServiceTestsRunnerTest.this,
+                                new Error(trace));
+                        break;
+                }
+            }
+
+            @Override
+            public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+                setName(test.getTestName());
+                result.endTest(AccessibilityServiceTestsRunnerTest.this);
+            }
+        });
+    }
+}
diff --git a/tests/tests/accessibilityservice/Android.mk b/tests/tests/accessibilityservice/Android.mk
index 4cac305..40aef68 100644
--- a/tests/tests/accessibilityservice/Android.mk
+++ b/tests/tests/accessibilityservice/Android.mk
@@ -22,9 +22,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-# TODO: include the core runner source as a library instead
-LOCAL_SRC_FILES := $(call all-java-files-under, src)\
-              $(call all-java-files-under, ../../core/runner/src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_SRC_FILES += \
   src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl \
@@ -32,6 +30,4 @@
 
 LOCAL_PACKAGE_NAME := CtsAccessibilityServiceTestCases
 
-#LOCAL_SDK_VERSION := current
-
-include $(BUILD_CTS_PACKAGE)
+include $(BUILD_PACKAGE)
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index ddf38c7..16063bc 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -33,7 +33,7 @@
 
   </application>
 
-  <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
+  <instrumentation android:name="android.test.InstrumentationTestRunner"
                    android:targetPackage="com.android.cts.accessibilityservice"
                    android:label="Tests for the accessibility APIs."/>
 
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 5a565ec..7112dfd 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -61,7 +61,8 @@
  * whose source is located at <strong>cts/tests/accessibility</strong> is required.
  * Once the former package has been installed the service must be enabled
  * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
- * in this package can be successfully run.
+ * in this package can be successfully run or running a command from the shell to
+ * enable the service.
  * </p>
  */
 public class AccessibilityEndToEndTest extends
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
index c9daaab..870c32b 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
@@ -61,7 +61,8 @@
  * whose source is located at <strong>cts/tests/accessibility</strong> is required.
  * Once the former package has been installed the service must be enabled
  * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
- * in this package can be successfully run.
+ * in this package can be successfully run or running a command from the shell to
+ * enable the service.
  * </p>
  */
 public class AccessibilityWindowQueryActivityTest
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java
index d322724..1c59b69 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java
@@ -38,6 +38,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Collection;
@@ -57,6 +58,8 @@
     private CtsBuildHelper mCtsBuild = null;
     private IBuildInfo mBuildInfo = null;
 
+    private ClassLoader mClassLoader;
+
     /**
      * {@inheritDoc}
      */
@@ -258,10 +261,10 @@
      */
     private Test loadTest(String className, String testName) {
         try {
-            File jarFile = mCtsBuild.getTestApp(mJarFileName);
-            URL urls[] = {jarFile.getCanonicalFile().toURI().toURL()};
-            Class<?> testClass = loadClass(className, urls);
-
+            Class<?> testClass = loadClass(className);
+            if (testClass == null) {
+                return null;
+            }
             if (TestCase.class.isAssignableFrom(testClass)) {
                 TestCase testCase = (TestCase)testClass.newInstance();
                 testCase.setName(testName);
@@ -273,18 +276,34 @@
                 Log.e(LOG_TAG, String.format("Class '%s' from jar '%s' is not a Test",
                         className, mJarFileName));
             }
-        } catch (ClassNotFoundException e) {
-            reportLoadError(mJarFileName, className, e);
         } catch (IllegalAccessException e) {
             reportLoadError(mJarFileName, className, e);
-        } catch (IOException e) {
-            reportLoadError(mJarFileName, className, e);
         } catch (InstantiationException e) {
             reportLoadError(mJarFileName, className, e);
         }
         return null;
     }
 
+    private Class<?> loadClass(String className) {
+        try {
+            if (mClassLoader == null) {
+                File jarFile = mCtsBuild.getTestApp(mJarFileName);
+                URL urls[] = {jarFile.getCanonicalFile().toURI().toURL()};
+                mClassLoader = new URLClassLoader(urls);
+            }
+            return mClassLoader.loadClass(className);
+        } catch (FileNotFoundException fnfe) {
+            reportLoadError(mJarFileName, className, fnfe);
+        } catch (MalformedURLException mue) {
+            reportLoadError(mJarFileName, className, mue);
+        } catch (IOException ioe) {
+            reportLoadError(mJarFileName, className, ioe);
+        } catch (ClassNotFoundException cnfe) {
+            reportLoadError(mJarFileName, className, cnfe);
+        }
+        return null;
+    }
+
     /**
      * Loads a class from given URLs.
      * <p/>