DO NOT MERGE CTS device-side preconditions for MNC

bug:23939594
Change-Id: I93245401a7136b5aee0a757a48eb80e063a523c3
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 975ac47..62fdbdd 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -102,6 +102,7 @@
     CtsMonkeyApp2 \
     CtsPackageInstallerApp \
     CtsPermissionApp \
+    CtsPreconditionsApp \
     CtsSimpleApp \
     CtsSimplePreMApp \
     CtsSomeAccessibilityServices \
diff --git a/tools/Android.mk b/tools/Android.mk
index 8377036..8cb90db 100644
--- a/tools/Android.mk
+++ b/tools/Android.mk
@@ -19,12 +19,13 @@
 TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/tradefed-prebuilt.jar
 CTS_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/cts-tradefed.jar
 CTS_TF_EXEC_PATH ?= $(HOST_OUT_EXECUTABLES)/cts-tradefed
+PRECONDITIONS_APK := $(CTS_TESTCASES_OUT)/CtsPreconditionsApp.apk
 
 cts_prebuilt_jar := $(HOST_OUT)/cts/android-cts/tools/cts-prebuilt.jar
 $(cts_prebuilt_jar): PRIVATE_TESTS_DIR := $(HOST_OUT)/cts/android-cts/repository/testcases
 $(cts_prebuilt_jar): PRIVATE_PLANS_DIR := $(HOST_OUT)/cts/android-cts/repository/plans
 $(cts_prebuilt_jar): PRIVATE_TOOLS_DIR := $(HOST_OUT)/cts/android-cts/tools
-$(cts_prebuilt_jar): $(JUNIT_HOST_JAR) $(HOSTTESTLIB_JAR) $(TF_JAR) $(CTS_TF_JAR) $(CTS_TF_EXEC_PATH) $(ADDITIONAL_TF_JARS) | $(ACP) $(HOST_OUT_EXECUTABLES)/adb
+$(cts_prebuilt_jar): $(JUNIT_HOST_JAR) $(HOSTTESTLIB_JAR) $(TF_JAR) $(CTS_TF_JAR) $(CTS_TF_EXEC_PATH) $(ADDITIONAL_TF_JARS) $(PRECONDITIONS_APK) | $(ACP) $(HOST_OUT_EXECUTABLES)/adb
 	mkdir -p $(PRIVATE_TESTS_DIR)
 	mkdir -p $(PRIVATE_PLANS_DIR)
 	mkdir -p $(PRIVATE_TOOLS_DIR)
diff --git a/tools/tradefed-host/preconditions/Android.mk b/tools/tradefed-host/preconditions/Android.mk
new file mode 100644
index 0000000..bcd7b49
--- /dev/null
+++ b/tools/tradefed-host/preconditions/Android.mk
@@ -0,0 +1,36 @@
+# Copyright (C) 2015 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)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsPreconditionsApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tools/tradefed-host/preconditions/AndroidManifest.xml b/tools/tradefed-host/preconditions/AndroidManifest.xml
new file mode 100644
index 0000000..02b3534
--- /dev/null
+++ b/tools/tradefed-host/preconditions/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.preconditions">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS device-side preconditions test"
+        android:targetPackage="com.android.cts.preconditions" >
+    </instrumentation>
+</manifest>
diff --git a/tools/tradefed-host/preconditions/src/com/android/cts/preconditions/PreconditionsTest.java b/tools/tradefed-host/preconditions/src/com/android/cts/preconditions/PreconditionsTest.java
new file mode 100644
index 0000000..64a2b31
--- /dev/null
+++ b/tools/tradefed-host/preconditions/src/com/android/cts/preconditions/PreconditionsTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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 com.android.cts.preconditions;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+/**
+ * An AndroidTestCase class to verify that device-side preconditions are met for CTS
+ */
+public class PreconditionsTest extends AndroidTestCase {
+
+    /**
+     * Test if device has no screen lock
+     * @throws Exception
+     */
+    public void testScreenUnlocked() throws Exception {
+        KeyguardManager km =
+                (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
+        assertFalse("Device must have screen lock disabled", km.isDeviceSecure());
+    }
+
+    /**
+     * Test if device has accessible external storage
+     * @throws Exception
+     */
+    public void testExternalStoragePresent() throws Exception {
+        String state = Environment.getExternalStorageState();
+        assertTrue("Device must have writable external storage mounted in order to run CTS",
+                Environment.MEDIA_MOUNTED.equals(state));
+    }
+
+}
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
index 416b400..3f89630 100644
--- a/tools/tradefed-host/res/config/cts.xml
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -19,6 +19,7 @@
     <option name="enable-root" value="false" />
     <build_provider class="com.android.cts.tradefed.build.CtsBuildProvider" />
     <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+    <target_preparer class="com.android.cts.tradefed.targetprep.DevicePreconditionPreparer" />
     <test class="com.android.cts.tradefed.testtype.CtsTest" />
     <logger class="com.android.tradefed.log.FileLogger" />
     <result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/DevicePreconditionPreparer.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/DevicePreconditionPreparer.java
new file mode 100644
index 0000000..039470b
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetprep/DevicePreconditionPreparer.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2015 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 com.android.cts.tradefed.targetprep;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.tradefed.testtype.Abi;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IFolderBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.InstrumentationTest;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.TargetSetupError;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A {@link ITargetPreparer} that performs precondition checks on the device-side for CTS.
+ * <p/>
+ * This class instruments an APK containing tests verifying that the device meets CTS
+ * preconditions. At present, the APK contains tests to ensure that the device's screen is not
+ * locked, and that the device's external storage is present and writable. The test lives under
+ * //cts/tools/tradefed-host/preconditions, and can be modified to perform further checks and tasks
+ * from the device-side.
+ */
+@OptionClass(alias="device-precondition-preparer")
+public class DevicePreconditionPreparer implements ITargetPreparer {
+
+    /* This option also exists in the HostPreconditionPreparer */
+    @Option(name = "skip-preconditions",
+            description = "Whether to skip precondition checks and automation")
+    protected boolean mSkipPreconditions = false;
+
+    /* Constants for the InstrumentationTest */
+    private static final String APK_NAME = "CtsPreconditionsApp.apk";
+    private static final String PACKAGE_NAME = "com.android.cts.preconditions";
+    private static final String RUNNER_NAME = "android.support.test.runner.AndroidJUnitRunner";
+
+    /* Map used to track test failures */
+    private ConcurrentHashMap<TestIdentifier, String> testFailures = new ConcurrentHashMap<>();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+            BuildError, DeviceNotAvailableException {
+        if (mSkipPreconditions) {
+            return; // skipping device-side preconditions
+        }
+
+        try {
+            if (!instrument(device, buildInfo)) {
+                throw new TargetSetupError("Not all device-side preconditions met");
+            }
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError(
+                    String.format("Couldn't find %s to instrument", APK_NAME), e);
+        }
+    }
+
+    /* Instruments the APK on the device, and logs precondition test failures, if any are found.
+     * Returns true if all tests pass, and otherwise returns false */
+    private boolean instrument(ITestDevice device, IBuildInfo buildInfo)
+            throws DeviceNotAvailableException, FileNotFoundException {
+        ITestInvocationListener listener = new PreconditionPreparerListener();
+        CtsBuildHelper mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+        File apkFile = mCtsBuild.getTestApp(APK_NAME); // get the APK file with the CtsBuildHelper
+        InstrumentationTest instrTest = new InstrumentationTest();
+        instrTest.setDevice(device);
+        instrTest.setInstallFile(apkFile);
+        instrTest.setPackageName(PACKAGE_NAME);
+        instrTest.setRunnerName(RUNNER_NAME);
+        instrTest.run(listener);
+        boolean success = true;
+        if (!testFailures.isEmpty()) {
+            success = false; // at least one precondition has failed
+            for (TestIdentifier test : testFailures.keySet()) {
+                String trace = testFailures.get(test);
+                CLog.e("Precondition test %s failed.\n%s", test.getTestName(), trace);
+            }
+        }
+        return success;
+    }
+
+    /**
+     * The PreconditionPreparerListener is an implementation of ITestInvocationListener
+     * that adds entries to the ConcurrentHashMap 'testFailures' of the outer class whenever
+     * a test fails. The listener also logs information if the test run fails, for debugging
+     * purposes.
+     */
+    public class PreconditionPreparerListener implements ITestInvocationListener {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testFailed(TestIdentifier test, String trace) {
+            testFailures.put(test, trace);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testRunFailed(String errorMessage) {
+            CLog.e("Device-side preconditions test run failed: %s", errorMessage);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testEnded(TestIdentifier test, Map<String, String> metrics) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invocationStarted(IBuildInfo buildInfo) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invocationEnded(long elapsedTime) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void invocationFailed(Throwable cause) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public TestSummary getSummary() {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testRunStarted(String runName, int testCount) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testStarted(TestIdentifier test) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testAssumptionFailure(TestIdentifier test, String trace) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testIgnored(TestIdentifier test) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testRunStopped(long elapsedTime) {}
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {}
+    }
+}