Merge "Fix failures of BYOD Managed Provisioning" into marshmallow-cts-dev
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index efeb665..53a7638 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -115,7 +115,7 @@
     plot_rotations(cam_rots, gyro_rots)
 
     # Pass/fail based on the offset and also the correlation distance.
-    dist = scipy.spatial.distance.correlation(cam_rots,gyro_rots)
+    dist = scipy.spatial.distance.correlation(cam_rots, gyro_rots)
     print "Best correlation of %f at shift of %.2fms"%(dist, offset*SEC_TO_MSEC)
     assert(dist < THRESH_MAX_CORR_DIST)
     assert(abs(offset) < THRESH_MAX_SHIFT_MS*MSEC_TO_SEC)
@@ -141,7 +141,7 @@
     for shift in candidates:
         times = cam_times + shift*MSEC_TO_NSEC
         gyro_rots = get_gyro_rotations(gyro_events, times)
-        dists.append(scipy.spatial.distance.correlation(cam_rots,gyro_rots))
+        dists.append(scipy.spatial.distance.correlation(cam_rots, gyro_rots))
     best_corr_dist = min(dists)
     best_shift = candidates[dists.index(best_corr_dist)]
 
@@ -181,9 +181,10 @@
         gyro_rots: Array of N-1 gyro rotation measurements (rad).
     """
     # For the plot, scale the rotations to be in degrees.
+    scale = 360/(2*math.pi)
     fig = matplotlib.pyplot.figure()
-    cam_rots = cam_rots * (360/(2*math.pi))
-    gyro_rots = gyro_rots * (360/(2*math.pi))
+    cam_rots = cam_rots * scale
+    gyro_rots = gyro_rots * scale
     pylab.plot(range(len(cam_rots)), cam_rots, 'r', label="camera")
     pylab.plot(range(len(gyro_rots)), gyro_rots, 'b', label="gyro")
     pylab.legend()
@@ -196,8 +197,7 @@
     """Get the rotation values of the gyro.
 
     Integrates the gyro data between each camera frame to compute an angular
-    displacement. Uses simple Euler approximation to implement the
-    integration.
+    displacement.
 
     Args:
         gyro_events: List of gyro event objects.
@@ -219,20 +219,16 @@
         sgyro = 0
         # Integrate samples within the window.
         for igyro in range(igyrowindow0, igyrowindow1):
-            vgyro0 = all_rots[igyro]
-            vgyro1 = all_rots[igyro+1]
+            vgyro = all_rots[igyro+1]
             tgyro0 = all_times[igyro]
             tgyro1 = all_times[igyro+1]
-            vgyro = 0.5 * (vgyro0 + vgyro1)
             deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
             sgyro += vgyro * deltatgyro
         # Handle the fractional intervals at the sides of the window.
         for side,igyro in enumerate([igyrowindow0-1, igyrowindow1]):
-            vgyro0 = all_rots[igyro]
-            vgyro1 = all_rots[igyro+1]
+            vgyro = all_rots[igyro+1]
             tgyro0 = all_times[igyro]
             tgyro1 = all_times[igyro+1]
-            vgyro = 0.5 * (vgyro0 + vgyro1)
             deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
             if side == 0:
                 f = (tcam0 - tgyro0) / (tgyro1 - tgyro0)
@@ -388,4 +384,3 @@
 
 if __name__ == '__main__':
     main()
-
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 7c43321..ea855e5 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -118,6 +118,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_device_admin" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.device_admin" />
         </activity>
 
         <activity android:name=".admin.RedactedNotificationKeyguardDisabledFeaturesActivity"
@@ -128,6 +130,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_device_admin" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.device_admin" />
         </activity>
 
         <activity android:name=".admin.ScreenLockTestActivity"
diff --git a/apps/CtsVerifier/res/xml/device_admin_byod.xml b/apps/CtsVerifier/res/xml/device_admin_byod.xml
index ce44794..ff1eb95 100644
--- a/apps/CtsVerifier/res/xml/device_admin_byod.xml
+++ b/apps/CtsVerifier/res/xml/device_admin_byod.xml
@@ -17,6 +17,8 @@
 <!-- BEGIN_INCLUDE(meta_data) -->
 <device-admin xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-policies>
+        <limit-password />
+        <watch-login />
         <encrypted-storage />
         <wipe-data />
         <reset-password />
diff --git a/common/device-side/device-info/Android.mk b/common/device-side/device-info/Android.mk
index 0f5e8d3..26b2cb8 100644
--- a/common/device-side/device-info/Android.mk
+++ b/common/device-side/device-info/Android.mk
@@ -20,6 +20,8 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util_v2
+
 LOCAL_MODULE := compatibility-device-info
 
 # uncomment when b/13282254 is fixed
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
new file mode 100644
index 0000000..be44243
--- /dev/null
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfo.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.compatibility.common.deviceinfo;
+
+/**
+ * DeviceInfoActivity wrapper class. All EDI collectors should extend this
+ * class instead of DeviceInfoActivity.
+ */
+public abstract class DeviceInfo extends DeviceInfoActivity {
+}
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
index f9de6eb..1f1e287 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
@@ -20,17 +20,13 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
-import android.text.TextUtils;
-import android.util.JsonWriter;
 import android.util.Log;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 
+import com.android.compatibility.common.util.InfoStore;
+
 /**
  * Collect device information on target device and write to a JSON file.
  */
@@ -51,7 +47,6 @@
     private static final String LOG_TAG = "DeviceInfoActivity";
 
     private CountDownLatch mDone = new CountDownLatch(1);
-    private JsonWriter mJsonWriter = null;
     private String mResultFilePath = null;
     private String mErrorMessage = "Collector has started.";
     private int mResultCode = DEVICE_INFO_RESULT_STARTED;
@@ -59,17 +54,28 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        if (createFilePath()) {
-            createJsonWriter();
-            startJsonWriter();
-            collectDeviceInfo();
-            closeJsonWriter();
 
-            if (mResultCode == DEVICE_INFO_RESULT_STARTED) {
-                mResultCode = DEVICE_INFO_RESULT_OK;
+        final File dir = new File(Environment.getExternalStorageDirectory(), "device-info-files");
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            failed("External storage is not mounted");
+        } else if (!dir.mkdirs() && !dir.isDirectory()) {
+            failed("Cannot create directory for device info files");
+        } else {
+            try {
+                File jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
+                jsonFile.createNewFile();
+                mResultFilePath = jsonFile.getAbsolutePath();
+                InfoStore store = new InfoStore(jsonFile);
+                store.open();
+                collectDeviceInfo(store);
+                store.close();
+                if (mResultCode == DEVICE_INFO_RESULT_STARTED) {
+                    mResultCode = DEVICE_INFO_RESULT_OK;
+                }
+            } catch (Exception e) {
+                failed("Could not collect device info: " + e.getMessage());
             }
         }
-
         Intent data = new Intent();
         if (mResultCode == DEVICE_INFO_RESULT_OK) {
             data.setData(Uri.parse(mResultFilePath));
@@ -86,7 +92,7 @@
     /**
      * Method to collect device information.
      */
-    protected abstract void collectDeviceInfo();
+    protected abstract void collectDeviceInfo(InfoStore store) throws Exception;
 
     void waitForActivityToFinish() {
         try {
@@ -128,323 +134,5 @@
         Log.e(LOG_TAG, message);
     }
 
-    private boolean createFilePath() {
-        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            failed("External storage is not mounted");
-            return false;
-        }
-        final File dir = new File(Environment.getExternalStorageDirectory(), "device-info-files");
-        if (!dir.mkdirs() && !dir.isDirectory()) {
-            failed("Cannot create directory for device info files");
-            return false;
-        }
-
-        // Create file at /sdcard/device-info-files/<class_name>.deviceinfo.json
-        final File jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
-        try {
-            jsonFile.createNewFile();
-        } catch (Exception e) {
-            failed("Cannot create file to collect device info");
-            return false;
-        }
-        mResultFilePath = jsonFile.getAbsolutePath();
-        return true;
-    }
-
-    private void createJsonWriter() {
-        try {
-            FileOutputStream out = new FileOutputStream(mResultFilePath);
-            mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
-            // TODO(agathaman): remove to make json output less pretty
-            mJsonWriter.setIndent("  ");
-        } catch (Exception e) {
-            failed("Failed to create JSON writer: " + e.getMessage());
-        }
-    }
-
-    private void startJsonWriter() {
-        try {
-            mJsonWriter.beginObject();
-        } catch (Exception e) {
-            failed("Failed to begin JSON object: " + e.getMessage());
-        }
-    }
-
-    private void closeJsonWriter() {
-        try {
-            mJsonWriter.endObject();
-            mJsonWriter.close();
-        } catch (Exception e) {
-            failed("Failed to close JSON object: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Start a new group of result.
-     */
-    public void startGroup() {
-        try {
-            mJsonWriter.beginObject();
-        } catch (Exception e) {
-            error("Failed to begin JSON group: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Start a new group of result with specified name.
-     */
-    public void startGroup(String name) {
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginObject();
-        } catch (Exception e) {
-            error("Failed to begin JSON group: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Complete adding result to the last started group.
-     */
-    public void endGroup() {
-        try {
-            mJsonWriter.endObject();
-        } catch (Exception e) {
-            error("Failed to end JSON group: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Start a new array of result.
-     */
-    public void startArray() {
-        try {
-            mJsonWriter.beginArray();
-        } catch (Exception e) {
-            error("Failed to begin JSON array: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Start a new array of result with specified name.
-     */
-    public void startArray(String name) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginArray();
-        } catch (Exception e) {
-            error("Failed to begin JSON array: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Complete adding result to the last started array.
-     */
-    public void endArray() {
-        try {
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to end JSON group: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a double value result.
-     */
-    public void addResult(String name, double value) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name).value(value);
-        } catch (Exception e) {
-            error("Failed to add result for type double: " + e.getMessage());
-        }
-    }
-
-    /**
-    * Add a long value result.
-    */
-    public void addResult(String name, long value) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name).value(value);
-        } catch (Exception e) {
-            error("Failed to add result for type long: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add an int value result.
-     */
-    public void addResult(String name, int value) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name).value((Number) value);
-        } catch (Exception e) {
-            error("Failed to add result for type int: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a boolean value result.
-     */
-    public void addResult(String name, boolean value) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name).value(value);
-        } catch (Exception e) {
-            error("Failed to add result for type boolean: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a String value result.
-     */
-    public void addResult(String name, String value) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name).value(checkString(value));
-        } catch (Exception e) {
-            error("Failed to add result for type String: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a double array result.
-     */
-    public void addArray(String name, double[] list) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginArray();
-            for (double value : checkArray(list)) {
-                mJsonWriter.value(value);
-            }
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to add result array for type double: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a long array result.
-     */
-    public void addArray(String name, long[] list) {
-        checkName(name);
-        try {
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (long value : checkArray(list)) {
-            mJsonWriter.value(value);
-        }
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to add result array for type long: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add an int array result.
-     */
-    public void addArray(String name, int[] list) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginArray();
-            for (int value : checkArray(list)) {
-                mJsonWriter.value((Number) value);
-            }
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to add result array for type int: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a boolean array result.
-     */
-    public void addArray(String name, boolean[] list) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginArray();
-            for (boolean value : checkArray(list)) {
-                mJsonWriter.value(value);
-            }
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to add result array for type boolean: " + e.getMessage());
-        }
-    }
-
-    /**
-     * Add a String array result.
-     */
-    public void addArray(String name, String[] list) {
-        checkName(name);
-        try {
-            mJsonWriter.name(name);
-            mJsonWriter.beginArray();
-            for (String value : checkArray(list)) {
-                mJsonWriter.value(checkString(value));
-            }
-            mJsonWriter.endArray();
-        } catch (Exception e) {
-            error("Failed to add result array for type Sting: " + e.getMessage());
-        }
-    }
-
-    private static boolean[] checkArray(boolean[] values) {
-        if (values.length > MAX_ARRAY_LENGTH) {
-            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
-        } else {
-            return values;
-        }
-    }
-
-    private static double[] checkArray(double[] values) {
-        if (values.length > MAX_ARRAY_LENGTH) {
-            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
-        } else {
-            return values;
-        }
-    }
-
-    private static int[] checkArray(int[] values) {
-        if (values.length > MAX_ARRAY_LENGTH) {
-            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
-        } else {
-            return values;
-        }
-    }
-
-    private static long[] checkArray(long[] values) {
-        if (values.length > MAX_ARRAY_LENGTH) {
-            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
-        } else {
-            return values;
-        }
-    }
-
-    private static String[] checkArray(String[] values) {
-        if (values.length > MAX_ARRAY_LENGTH) {
-            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
-        } else {
-            return values;
-        }
-    }
-
-    private static String checkString(String value) {
-        if (value.length() > MAX_STRING_VALUE_LENGTH) {
-            return value.substring(0, MAX_STRING_VALUE_LENGTH);
-        }
-        return value;
-    }
-
-    private static String checkName(String value) {
-        if (TextUtils.isEmpty(value)) {
-            throw new NullPointerException();
-        }
-        return value;
-    }
 }
 
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
index 7df3dae..304b74c 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
@@ -45,12 +45,12 @@
 import java.util.Scanner;
 import java.util.Set;
 
-import com.android.compatibility.common.deviceinfo.DeviceInfoActivity;
+import com.android.compatibility.common.util.InfoStore;
 
 /**
  * Generic device info collector.
  */
-public class GenericDeviceInfo extends DeviceInfoActivity {
+public class GenericDeviceInfo extends DeviceInfo {
 
     public static final String BUILD_ID = "build_id";
     public static final String BUILD_PRODUCT = "build_product";
@@ -78,38 +78,38 @@
     }
 
     @Override
-    protected void collectDeviceInfo() {
-        addResult(BUILD_ID, Build.ID);
-        addResult(BUILD_PRODUCT, Build.PRODUCT);
-        addResult(BUILD_DEVICE, Build.DEVICE);
-        addResult(BUILD_BOARD, Build.BOARD);
-        addResult(BUILD_MANUFACTURER, Build.MANUFACTURER);
-        addResult(BUILD_BRAND, Build.BRAND);
-        addResult(BUILD_MODEL, Build.MODEL);
-        addResult(BUILD_TYPE, Build.TYPE);
-        addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
-        addResult(BUILD_ABI, Build.CPU_ABI);
-        addResult(BUILD_ABI2, Build.CPU_ABI2);
-        addResult(BUILD_SERIAL, Build.SERIAL);
-        addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
-        addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
+    protected void collectDeviceInfo(InfoStore store) throws Exception {
+        store.addResult(BUILD_ID, Build.ID);
+        store.addResult(BUILD_PRODUCT, Build.PRODUCT);
+        store.addResult(BUILD_DEVICE, Build.DEVICE);
+        store.addResult(BUILD_BOARD, Build.BOARD);
+        store.addResult(BUILD_MANUFACTURER, Build.MANUFACTURER);
+        store.addResult(BUILD_BRAND, Build.BRAND);
+        store.addResult(BUILD_MODEL, Build.MODEL);
+        store.addResult(BUILD_TYPE, Build.TYPE);
+        store.addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
+        store.addResult(BUILD_ABI, Build.CPU_ABI);
+        store.addResult(BUILD_ABI2, Build.CPU_ABI2);
+        store.addResult(BUILD_SERIAL, Build.SERIAL);
+        store.addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
+        store.addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
 
          // Collect build fields available in API level 21
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            addResult(BUILD_ABIS, TextUtils.join(",", Build.SUPPORTED_ABIS));
-            addResult(BUILD_ABIS_32, TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS));
-            addResult(BUILD_ABIS_64, TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS));
+            store.addResult(BUILD_ABIS, TextUtils.join(",", Build.SUPPORTED_ABIS));
+            store.addResult(BUILD_ABIS_32, TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS));
+            store.addResult(BUILD_ABIS_64, TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS));
         }
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            addResult(BUILD_VERSION_BASE_OS, Build.VERSION.BASE_OS);
-            addResult(BUILD_VERSION_SECURITY_PATCH, Build.VERSION.SECURITY_PATCH);
+            store.addResult(BUILD_VERSION_BASE_OS, Build.VERSION.BASE_OS);
+            store.addResult(BUILD_VERSION_SECURITY_PATCH, Build.VERSION.SECURITY_PATCH);
         } else {
             // Access system properties directly because Build.Version.BASE_OS and
             // Build.Version.SECURITY_PATCH are not defined pre-M.
-            addResult(BUILD_VERSION_BASE_OS,
+            store.addResult(BUILD_VERSION_BASE_OS,
                     SystemProperties.get("ro.build.version.base_os", ""));
-            addResult(BUILD_VERSION_SECURITY_PATCH,
+            store.addResult(BUILD_VERSION_SECURITY_PATCH,
                     SystemProperties.get("ro.build.version.security_patch", ""));
         }
     }
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
index 4d9ad46..1452040 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
@@ -19,12 +19,12 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 
-import com.android.compatibility.common.deviceinfo.DeviceInfoActivity;
+import com.android.compatibility.common.util.InfoStore;
 
 /**
  * PackageDeviceInfo collector.
  */
-public class PackageDeviceInfo extends DeviceInfoActivity {
+public class PackageDeviceInfo extends DeviceInfo {
 
     private static final String PACKAGE = "package";
     private static final String NAME = "name";
@@ -33,18 +33,18 @@
     private static final String PRIV_APP_DIR = "/system/priv-app";
 
     @Override
-    protected void collectDeviceInfo() {
+    protected void collectDeviceInfo(InfoStore store) throws Exception {
         PackageManager pm = this.getPackageManager();
-        startArray(PACKAGE);
+        store.startArray(PACKAGE);
         for (PackageInfo pkg : pm.getInstalledPackages(0)) {
-            startGroup();
-            addResult(NAME, pkg.packageName);
-            addResult(VERSION_NAME, pkg.versionName);
+            store.startGroup();
+            store.addResult(NAME, pkg.packageName);
+            store.addResult(VERSION_NAME, pkg.versionName);
 
             String dir = pkg.applicationInfo.sourceDir;
-            addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
-            endGroup();
+            store.addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
+            store.endGroup();
         }
-        endArray(); // Package
+        store.endArray(); // Package
     }
 }
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
deleted file mode 100644
index 7da9951..0000000
--- a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.compatibility.common.deviceinfo;
-
-import android.os.Bundle;
-
-import java.lang.StringBuilder;
-
-/**
- * Sample device info collector.
- */
-public class SampleDeviceInfo extends DeviceInfoActivity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void collectDeviceInfo() {
-        boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
-        double[] doubles = {Double.MAX_VALUE, Double.MIN_VALUE};
-        int[] ints = {Integer.MAX_VALUE, Integer.MIN_VALUE};
-        long[] longs = {Long.MAX_VALUE, Long.MIN_VALUE};
-
-        // Group Foo
-        startGroup("foo");
-        addResult("foo_boolean", Boolean.TRUE);
-
-        // Group Bar
-        startGroup("bar");
-        addArray("bar_string", new String[] {
-                "bar-string-1",
-                "bar-string-2",
-                "bar-string-3"});
-
-        addArray("bar_boolean", booleans);
-        addArray("bar_double", doubles);
-        addArray("bar_int", ints);
-        addArray("bar_long", longs);
-        endGroup(); // bar
-
-        addResult("foo_double", Double.MAX_VALUE);
-        addResult("foo_int", Integer.MAX_VALUE);
-        addResult("foo_long", Long.MAX_VALUE);
-        addResult("foo_string", "foo-string");
-
-        StringBuilder sb = new StringBuilder();
-        int[] arr = new int[1001];
-        for (int i = 0; i < 1001; i++) {
-            sb.append("a");
-            arr[i] = i;
-        }
-        addResult("long_string", sb.toString());
-        addArray("long_int_array", arr);
-
-        endGroup(); // foo
-    }
-}
-
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/TestDeviceInfo.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/TestDeviceInfo.java
index 7f82942..96814f5 100644
--- a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/TestDeviceInfo.java
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/TestDeviceInfo.java
@@ -18,6 +18,11 @@
 import android.os.Bundle;
 
 import java.lang.StringBuilder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import com.android.compatibility.common.util.InfoStore;
 
 /**
  * Collector for testing DeviceInfoActivity
@@ -30,47 +35,47 @@
     }
 
     @Override
-    protected void collectDeviceInfo() {
+    protected void collectDeviceInfo(InfoStore store) throws Exception {
 
         // Test primitive results
-        addResult("test_boolean", true);
-        addResult("test_double", 1.23456789);
-        addResult("test_int", 123456789);
-        addResult("test_long", Long.MAX_VALUE);
-        addResult("test_string", "test string");
-        addArray("test_strings", new String[] {
-            "test string 1",
-            "test string 2",
-            "test string 3",
-        });
+        store.addResult("test_boolean", true);
+        store.addResult("test_double", 1.23456789);
+        store.addResult("test_int", 123456789);
+        store.addResult("test_long", Long.MAX_VALUE);
+        store.addResult("test_string", "test string");
+        List<String> list = new ArrayList<>();
+        list.add("test string 1");
+        list.add("test string 2");
+        list.add("test string 3");
+        store.addListResult("test_strings", list);
 
         // Test group
-        startGroup("test_group");
-        addResult("test_boolean", false);
-        addResult("test_double", 9.87654321);
-        addResult("test_int", 987654321);
-        addResult("test_long", Long.MAX_VALUE);
-        addResult("test_string", "test group string");
-        addArray("test_strings", new String[] {
-            "test group string 1",
-            "test group string 2",
-            "test group string 3"
-        });
-        endGroup(); // test_group
+        store.startGroup("test_group");
+        store.addResult("test_boolean", false);
+        store.addResult("test_double", 9.87654321);
+        store.addResult("test_int", 987654321);
+        store.addResult("test_long", Long.MAX_VALUE);
+        store.addResult("test_string", "test group string");
+        list = new ArrayList<>();
+        list.add("test group string 1");
+        list.add("test group string 2");
+        list.add("test group string 3");
+        store.addListResult("test_strings", list);
+        store.endGroup(); // test_group
 
         // Test array of groups
-        startArray("test_groups");
+        store.startArray("test_groups");
         for (int i = 1; i < 4; i++) {
-            startGroup();
-            addResult("test_string", "test groups string " + i);
-            addArray("test_strings", new String[] {
-                "test groups string " + i + "-1",
-                "test groups string " + i + "-2",
-                "test groups string " + i + "-3"
-            });
-            endGroup();
+            store.startGroup();
+            store.addResult("test_string", "test groups string " + i);
+            list = new ArrayList<>();
+            list.add("test groups string " + i + "-1");
+            list.add("test groups string " + i + "-2");
+            list.add("test groups string " + i + "-3");
+            store.addListResult("test_strings", list);
+            store.endGroup();
         }
-        endArray(); // test_groups
+        store.endArray(); // test_groups
 
         // Test max
         StringBuilder sb = new StringBuilder();
@@ -79,7 +84,7 @@
             sb.append("a");
             arr[i] = i;
         }
-        addResult("max_length_string", sb.toString());
-        addArray("max_num_ints", arr);
+        store.addResult("max_length_string", sb.toString());
+        store.addArrayResult("max_num_ints", arr);
     }
 }
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/InfoStore.java b/common/device-side/util/src/com/android/compatibility/common/util/InfoStore.java
new file mode 100644
index 0000000..62decdc
--- /dev/null
+++ b/common/device-side/util/src/com/android/compatibility/common/util/InfoStore.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2016 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.compatibility.common.util;
+
+import android.util.JsonWriter;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+
+public class InfoStore {
+
+    private static final int MAX_STRING_LENGTH = 1000;
+    private static final int MAX_ARRAY_LENGTH = 1000;
+    private static final int MAX_LIST_LENGTH = 1000;
+
+    private final File mJsonFile;
+    private JsonWriter mJsonWriter = null;
+
+    public InfoStore(File file) throws Exception {
+        mJsonFile = file;
+    }
+
+    /**
+     * Opens the file for storage and creates the writer.
+     */
+    public void open() throws IOException {
+        FileOutputStream out = new FileOutputStream(mJsonFile);
+        mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+        // TODO(agathaman): remove to make json output less pretty
+        mJsonWriter.setIndent("  ");
+        mJsonWriter.beginObject();
+    }
+
+    /**
+     * Closes the writer.
+     */
+    public void close() throws IOException {
+        mJsonWriter.endObject();
+        mJsonWriter.close();
+    }
+
+    /**
+     * Start a new group of result.
+     */
+    public void startGroup() throws IOException {
+        mJsonWriter.beginObject();
+    }
+
+    /**
+     * Start a new group of result with specified name.
+     */
+    public void startGroup(String name) throws IOException {
+        mJsonWriter.name(name);
+        mJsonWriter.beginObject();
+    }
+
+    /**
+     * Complete adding result to the last started group.
+     */
+    public void endGroup() throws IOException {
+        mJsonWriter.endObject();
+    }
+
+    /**
+     * Start a new array of result.
+     */
+    public void startArray() throws IOException {
+        mJsonWriter.beginArray();
+    }
+
+    /**
+     * Start a new array of result with specified name.
+     */
+    public void startArray(String name) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+    }
+
+    /**
+     * Complete adding result to the last started array.
+     */
+    public void endArray() throws IOException {
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a int value to the InfoStore
+     */
+    public void addResult(String name, int value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(value);
+    }
+
+    /**
+     * Adds a long value to the InfoStore
+     */
+    public void addResult(String name, long value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(value);
+    }
+
+    /**
+     * Adds a float value to the InfoStore
+     */
+    public void addResult(String name, float value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(value);
+    }
+
+    /**
+     * Adds a double value to the InfoStore
+     */
+    public void addResult(String name, double value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(value);
+    }
+
+    /**
+     * Adds a boolean value to the InfoStore
+     */
+    public void addResult(String name, boolean value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(value);
+    }
+
+    /**
+     * Adds a String value to the InfoStore
+     */
+    public void addResult(String name, String value) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.value(checkString(value));
+    }
+
+    /**
+     * Adds a int array to the InfoStore
+     */
+    public void addArrayResult(String name, int[] array) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (int value : checkArray(array)) {
+            mJsonWriter.value(value);
+        }
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a long array to the InfoStore
+     */
+    public void addArrayResult(String name, long[] array) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (long value : checkArray(array)) {
+            mJsonWriter.value(value);
+        }
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a float array to the InfoStore
+     */
+    public void addArrayResult(String name, float[] array) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (float value : checkArray(array)) {
+            mJsonWriter.value(value);
+        }
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a double array to the InfoStore
+     */
+    public void addArrayResult(String name, double[] array) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (double value : checkArray(array)) {
+            mJsonWriter.value(value);
+        }
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a boolean array to the InfoStore
+     */
+    public void addArrayResult(String name, boolean[] array) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (boolean value : checkArray(array)) {
+            mJsonWriter.value(value);
+        }
+        mJsonWriter.endArray();
+    }
+
+    /**
+     * Adds a List of String to the InfoStore
+     */
+    public void addListResult(String name, List<String> list) throws IOException {
+        checkName(name);
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (String value : checkStringList(list)) {
+            mJsonWriter.value(checkString(value));
+        }
+        mJsonWriter.endArray();
+    }
+
+    private static int[] checkArray(int[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static long[] checkArray(long[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static float[] checkArray(float[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static double[] checkArray(double[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static boolean[] checkArray(boolean[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static List<String> checkStringList(List<String> list) {
+        if (list.size() > MAX_LIST_LENGTH) {
+            return list.subList(0, MAX_LIST_LENGTH);
+        }
+        return list;
+    }
+
+    private static String checkString(String value) {
+        if (value == null || value.isEmpty()) {
+            return "null";
+        }
+        if (value.length() > MAX_STRING_LENGTH) {
+            return value.substring(0, MAX_STRING_LENGTH);
+        }
+        return value;
+    }
+
+    private static String checkName(String value) {
+        if (value == null || value.isEmpty()) {
+            throw new NullPointerException();
+        }
+        return value;
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 547b205..a82c17c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -76,7 +76,20 @@
     @Override
     public void tearDown() throws Exception {
         // Ensure that we leave WiFi in its previous state.
-        mWifiManager.setWifiEnabled(mInitialWiFiState);
+        NetworkInfo.State expectedState = mInitialWiFiState ?
+            NetworkInfo.State.CONNECTED : NetworkInfo.State.DISCONNECTED;
+        ConnectivityActionReceiver receiver =
+            new ConnectivityActionReceiver(ConnectivityManager.TYPE_WIFI,
+                                           expectedState);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(receiver, filter);
+
+        assertTrue(mWifiManager.setWifiEnabled(mInitialWiFiState));
+        assertTrue("Failure to restore previous WiFi state.",
+                    receiver.waitForStateChange());
+
+        mContext.unregisterReceiver(receiver);
     }
 
     // --------------------------------------------------------------------------------------------
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index dd4e3e3..ffbe432 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -41,6 +41,8 @@
 import android.util.Rational;
 import android.util.Size;
 
+import com.android.cts.util.TimeoutReq;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -303,6 +305,7 @@
      * API specifications.
      * </p>
      */
+    @TimeoutReq(minutes = 40)
     public void testAeModeAndLock() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
diff --git a/tests/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/tests/app/src/android/app/cts/NotificationManagerTest.java
index fbb3060..c5a41c3 100644
--- a/tests/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -126,6 +126,8 @@
         // we will check for it for up to 200ms before giving up
         boolean found = false;
         for (int tries=3; tries-->0;) {
+            // Need reset flag.
+            found = false;
             final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
             for (StatusBarNotification sbn : sbns) {
                 if (sbn.getId() == id) {
diff --git a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
index 14cd6d5..1ea6ff0 100644
--- a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.test.InstrumentationTestCase;
 
+import java.lang.Math;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Random;
@@ -170,15 +171,28 @@
     }
 
     public void testOnMove() {
-        mTestAbstractCursor.resetOnMoveRet();
         assertFalse(mTestAbstractCursor.getOnMoveRet());
         mTestAbstractCursor.moveToFirst();
+        assertTrue(mTestAbstractCursor.getOnMoveRet());
+        assertEquals(1, mTestAbstractCursor.getRowsMovedSum());
+
         mTestAbstractCursor.moveToPosition(5);
         assertTrue(mTestAbstractCursor.getOnMoveRet());
+        assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
         assertEquals(0, mTestAbstractCursor.getOldPos());
         assertEquals(5, mTestAbstractCursor.getNewPos());
     }
 
+    public void testOnMove_samePosition() {
+        mTestAbstractCursor.moveToFirst();
+        mTestAbstractCursor.moveToPosition(5);
+        assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
+        mTestAbstractCursor.moveToPosition(5);
+        // Moving to the same position should either call onMove(5, 5)
+        // or be a no-op. It should no change the RowsMovedSum.
+        assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
+    }
+
     public void testMoveToPrevious() {
         // Test moveToFirst, isFirst, moveToNext, getPosition
         assertTrue(mDatabaseCursor.moveToFirst());
@@ -426,6 +440,8 @@
         private boolean mOnMoveReturnValue;
         private int mOldPosition;
         private int mNewPosition;
+        /** The accumulated number of rows this cursor has moved over. */
+        private int mRowsMovedSum;
         private String[] mColumnNames;
         private ArrayList<Object>[] mRows;
         private boolean mHadCalledOnChange = false;
@@ -481,11 +497,16 @@
             return mNewPosition;
         }
 
+        public int getRowsMovedSum() {
+            return mRowsMovedSum;
+        }
+
         @Override
         public boolean onMove(int oldPosition, int newPosition) {
             mOnMoveReturnValue = super.onMove(oldPosition, newPosition);
             mOldPosition = oldPosition;
             mNewPosition = newPosition;
+            mRowsMovedSum += Math.abs(newPosition - oldPosition);
             return mOnMoveReturnValue;
         }
 
@@ -618,3 +639,4 @@
         }
     }
 }
+
diff --git a/tests/tests/os/src/android/os/cts/SecurityPatchTest.java b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
new file mode 100644
index 0000000..7b3ac14
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SecurityPatchTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.os.cts;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+/**
+ * Tests for Security Patch String settings
+ */
+public class SecurityPatchTest extends InstrumentationTestCase {
+
+    private static final String TAG = SecurityPatchTest.class.getSimpleName();
+    private static final String SECURITY_PATCH_ERROR =
+            "ro.build.version.security_patch should be in the format \"YYYY-MM-DD\". Found \"%s\"";
+    private static final String SECURITY_PATCH_DATE_ERROR =
+            "ro.build.version.security_patch should be \"%d-%02d\" or later. Found \"%s\"";
+    private static final int SECURITY_PATCH_YEAR = 2016;
+    private static final int SECURITY_PATCH_MONTH = 02;
+
+    private boolean mSkipTests = false;
+
+    @Override
+    protected void setUp() {
+        mSkipTests = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M);
+    }
+
+    /** Security patch string must exist in M or higher **/
+    public void testSecurityPatchFound() {
+        if (mSkipTests) {
+            Log.w(TAG, "Skipping M+ Test.");
+            return;
+        }
+
+        String buildSecurityPatch = SystemProperties.get("ro.build.version.security_patch", "");
+        String error = String.format(SECURITY_PATCH_ERROR, buildSecurityPatch);
+        assertTrue(error, !buildSecurityPatch.isEmpty());
+    }
+
+    /** Security patch should be of the form YYYY-MM-DD in M or higher */
+    public void testSecurityPatchFormat() {
+        if (mSkipTests) {
+            Log.w(TAG, "Skipping M+ Test.");
+            return;
+        }
+
+        String buildSecurityPatch = SystemProperties.get("ro.build.version.security_patch", "");
+        String error = String.format(SECURITY_PATCH_ERROR, buildSecurityPatch);
+
+        assertEquals(error, 10, buildSecurityPatch.length());
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(0)));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(1)));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(2)));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(3)));
+        assertEquals(error, '-', buildSecurityPatch.charAt(4));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(5)));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(6)));
+        assertEquals(error, '-', buildSecurityPatch.charAt(7));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(8)));
+        assertTrue(error, Character.isDigit(buildSecurityPatch.charAt(9)));
+    }
+
+    /** Security patch should no older than the month this test was updated in M or higher **/
+    public void testSecurityPatchDate() {
+        if (mSkipTests) {
+            Log.w(TAG, "Skipping M+ Test.");
+            return;
+        }
+
+        String buildSecurityPatch = SystemProperties.get("ro.build.version.security_patch", "");
+        String error = String.format(SECURITY_PATCH_DATE_ERROR,
+                                     SECURITY_PATCH_YEAR,
+                                     SECURITY_PATCH_MONTH,
+                                     buildSecurityPatch);
+
+        int declaredYear = 0;
+        int declaredMonth = 0;
+
+        try {
+            declaredYear = Integer.parseInt(buildSecurityPatch.substring(0,4));
+            declaredMonth = Integer.parseInt(buildSecurityPatch.substring(5,7));
+        } catch (Exception e) {
+            assertTrue(error, false);
+        }
+
+        assertTrue(error, declaredYear >= SECURITY_PATCH_YEAR);
+        assertTrue(error, (declaredYear > SECURITY_PATCH_YEAR) ||
+                          (declaredMonth >= SECURITY_PATCH_MONTH));
+    }
+}
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index 5a80802..6b72b82 100755
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -101,6 +101,7 @@
                     "311230",   // C SPire Wireless + Celluar South
                     "310600",    // Cellcom
                     "31000",     // Republic Wireless US
+                    "310260",    // Republic Wireless US
                     "310026",     // T-Mobile US
                     "330120", // OpenMobile communication
                     // Verizon
@@ -142,6 +143,7 @@
                     "45005",    // SKT Mobility
                     "45002",     // SKT Mobility
                     "45006",    // LGT
+                    "310260",   // Republic Wireless US
                     // Verizon
                     "310004",
                     "310012",
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
index 886193c..b8dbb6e 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
@@ -17,47 +17,45 @@
 
 import android.os.Bundle;
 
-import com.android.compatibility.common.deviceinfo.DeviceInfoActivity;
+import com.android.compatibility.common.deviceinfo.DeviceInfo;
+import com.android.compatibility.common.util.InfoStore;
+
+import java.util.Arrays;
 
 /**
  * Sample device info collector.
  */
-public class SampleDeviceInfo extends DeviceInfoActivity {
+public class SampleDeviceInfo extends DeviceInfo {
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void collectDeviceInfo() {
+    protected void collectDeviceInfo(InfoStore store) throws Exception {
         boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
         double[] doubles = {Double.MAX_VALUE, Double.MIN_VALUE};
         int[] ints = {Integer.MAX_VALUE, Integer.MIN_VALUE};
         long[] longs = {Long.MAX_VALUE, Long.MIN_VALUE};
 
         // Group Foo
-        startGroup("foo");
-        addResult("foo_boolean", Boolean.TRUE);
+        store.startGroup("foo");
+        store.addResult("foo_boolean", Boolean.TRUE);
 
         // Group Bar
-        startGroup("bar");
-        addArray("bar_string", new String[] {
+        store.startGroup("bar");
+        store.addListResult("bar_string", Arrays.asList(new String[] {
                 "bar-string-1",
                 "bar-string-2",
-                "bar-string-3"});
+                "bar-string-3"}));
 
-        addArray("bar_boolean", booleans);
-        addArray("bar_double", doubles);
-        addArray("bar_int", ints);
-        addArray("bar_long", longs);
-        endGroup(); // bar
+        store.addArrayResult("bar_boolean", booleans);
+        store.addArrayResult("bar_double", doubles);
+        store.addArrayResult("bar_int", ints);
+        store.addArrayResult("bar_long", longs);
+        store.endGroup(); // bar
 
-        addResult("foo_double", Double.MAX_VALUE);
-        addResult("foo_int", Integer.MAX_VALUE);
-        addResult("foo_long", Long.MAX_VALUE);
-        addResult("foo_string", "foo-string");
-        endGroup(); // foo
+        store.addResult("foo_double", Double.MAX_VALUE);
+        store.addResult("foo_int", Integer.MAX_VALUE);
+        store.addResult("foo_long", Long.MAX_VALUE);
+        store.addResult("foo_string", "foo-string");
+        store.endGroup(); // foo
     }
 }
 
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
index c336d3c..24cff32 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
@@ -78,4 +78,5 @@
     public static final String TOTAL_MEMORY = "total_memory";
     public static final String REFERENCE_BUILD_FINGERPRINT =
             "reference_build_fingerprint";
+    public static final String AVAILABLE_PROCESSORS = "available_processors";
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 37f8558..b752c91 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -169,6 +169,9 @@
         addResult(LARGE_MEMORY_CLASS, getLargeMemoryClass());
         addResult(TOTAL_MEMORY, getTotalMemory());
 
+        // CPU Info
+        addResult(AVAILABLE_PROCESSORS, Runtime.getRuntime().availableProcessors());
+
         finish(Activity.RESULT_OK, mResults);
     }
 
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
index 6f4d42d..885f267 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/DeqpTestRunner.java
@@ -311,6 +311,8 @@
 
                 final Map<String, String> emptyMap = Collections.emptyMap();
                 mSink.testEnded(testId, emptyMap);
+            } else {
+                CLog.w("Finalization for non-pending case %s", testId);
             }
         }
 
@@ -372,6 +374,8 @@
         public void abortTest(TestIdentifier testId, String errorMessage) {
             final PendingResult result = mPendingResults.get(testId);
 
+            CLog.i("Test %s aborted with message %s", testId, errorMessage);
+
             // Mark as executed
             result.allInstancesPassed = false;
             result.errorMessages.put(mRunConfig, errorMessage);
@@ -390,37 +394,57 @@
         /**
          * Handles beginning of dEQP session.
          */
-        private void handleBeginSession(Map<String, String> values) {
+        private boolean handleBeginSession(Map<String, String> values) {
             // ignore
+            return true;
+        }
+
+        /**
+         * Handle session info
+         */
+        private boolean handleSessionInfo(Map<String, String> values) {
+            // ignore
+            return true;
         }
 
         /**
          * Handles end of dEQP session.
          */
-        private void handleEndSession(Map<String, String> values) {
+        private boolean handleEndSession(Map<String, String> values) {
             // ignore
+            return true;
         }
 
         /**
          * Handles beginning of dEQP testcase.
          */
-        private void handleBeginTestCase(Map<String, String> values) {
-            mCurrentTestId = pathToIdentifier(values.get("dEQP-BeginTestCase-TestCasePath"));
+        private boolean handleBeginTestCase(Map<String, String> values) {
+            String casePath = values.get("dEQP-BeginTestCase-TestCasePath");
+
             mCurrentTestLog = "";
             mGotTestResult = false;
 
+            if (casePath == null) {
+                CLog.w("Got null case path for test case begin event. Current test ID: %s", mCurrentTestId);
+                mCurrentTestId = null;
+                return false;
+            }
+
+            mCurrentTestId = pathToIdentifier(casePath);
+
             // mark instance as started
             if (mPendingResults.get(mCurrentTestId) != null) {
                 mPendingResults.get(mCurrentTestId).remainingConfigs.remove(mRunConfig);
             } else {
                 CLog.w("Got unexpected start of %s", mCurrentTestId);
             }
+            return true;
         }
 
         /**
          * Handles end of dEQP testcase.
          */
-        private void handleEndTestCase(Map<String, String> values) {
+        private boolean handleEndTestCase(Map<String, String> values) {
             final PendingResult result = mPendingResults.get(mCurrentTestId);
 
             if (result != null) {
@@ -442,19 +466,24 @@
                 CLog.w("Got unexpected end of %s", mCurrentTestId);
             }
             mCurrentTestId = null;
+            return true;
         }
 
         /**
          * Handles dEQP testcase result.
          */
-        private void handleTestCaseResult(Map<String, String> values) {
+        private boolean handleTestCaseResult(Map<String, String> values) {
             String code = values.get("dEQP-TestCaseResult-Code");
+            if (code == null) {
+                return false;
+            }
+
             String details = values.get("dEQP-TestCaseResult-Details");
 
             if (mPendingResults.get(mCurrentTestId) == null) {
                 CLog.w("Got unexpected result for %s", mCurrentTestId);
                 mGotTestResult = true;
-                return;
+                return true;
             }
 
             if (code.compareTo("Pass") == 0) {
@@ -480,12 +509,13 @@
                 mGotTestResult = true;
                 CLog.e("Got invalid result code '%s' for test %s", code, mCurrentTestId);
             }
+            return true;
         }
 
         /**
          * Handles terminated dEQP testcase.
          */
-        private void handleTestCaseTerminate(Map<String, String> values) {
+        private boolean handleTestCaseTerminate(Map<String, String> values) {
             final PendingResult result = mPendingResults.get(mCurrentTestId);
 
             if (result != null) {
@@ -504,40 +534,52 @@
 
             mCurrentTestId = null;
             mGotTestResult = true;
+            return true;
         }
 
         /**
          * Handles dEQP testlog data.
          */
-        private void handleTestLogData(Map<String, String> values) {
-            mCurrentTestLog = mCurrentTestLog + values.get("dEQP-TestLogData-Log");
+        private boolean handleTestLogData(Map<String, String> values) {
+            String newLog = values.get("dEQP-TestLogData-Log");
+            if (newLog == null) {
+                return false;
+            }
+            mCurrentTestLog = mCurrentTestLog + newLog;
+            return true;
         }
 
         /**
          * Handles new instrumentation status message.
+         * @return true if handled correctly, false if missing values.
          */
-        public void handleStatus(Map<String, String> values) {
+        public boolean handleStatus(Map<String, String> values) {
             String eventType = values.get("dEQP-EventType");
 
             if (eventType == null) {
-                return;
+                // Not an event, but some other line
+                return true;
             }
 
             if (eventType.compareTo("BeginSession") == 0) {
-                handleBeginSession(values);
+                return handleBeginSession(values);
+            } else if (eventType.compareTo("SessionInfo") == 0) {
+                return handleSessionInfo(values);
             } else if (eventType.compareTo("EndSession") == 0) {
-                handleEndSession(values);
+                return handleEndSession(values);
             } else if (eventType.compareTo("BeginTestCase") == 0) {
-                handleBeginTestCase(values);
+                return handleBeginTestCase(values);
             } else if (eventType.compareTo("EndTestCase") == 0) {
-                handleEndTestCase(values);
+                return handleEndTestCase(values);
             } else if (eventType.compareTo("TestCaseResult") == 0) {
-                handleTestCaseResult(values);
+                return handleTestCaseResult(values);
             } else if (eventType.compareTo("TerminateTestCase") == 0) {
-                handleTestCaseTerminate(values);
+                return handleTestCaseTerminate(values);
             } else if (eventType.compareTo("TestLogData") == 0) {
-                handleTestLogData(values);
+                return handleTestLogData(values);
             }
+            CLog.e("Unknown event type (%s)", eventType);
+            return false;
         }
 
         /**
@@ -548,6 +590,7 @@
             if (mCurrentTestId != null) {
                 // Current instance was removed from remainingConfigs when case
                 // started. Mark current instance as pending.
+                CLog.i("Batch ended with test '%s' current", mCurrentTestId);
                 if (mPendingResults.get(mCurrentTestId) != null) {
                     mPendingResults.get(mCurrentTestId).remainingConfigs.add(mRunConfig);
                 } else {
@@ -569,6 +612,7 @@
         private String mCurrentValue;
         private int mResultCode;
         private boolean mGotExitValue = false;
+        private boolean mParseSuccessful = true;
 
 
         public InstrumentationParser(TestInstanceResultListener listener) {
@@ -591,7 +635,7 @@
                         mCurrentValue = null;
                     }
 
-                    mListener.handleStatus(mValues);
+                    mParseSuccessful &= mListener.handleStatus(mValues);
                     mValues = null;
                 } else if (line.startsWith("INSTRUMENTATION_STATUS: dEQP-")) {
                     if (mCurrentName != null) {
@@ -604,16 +648,25 @@
                     String prefix = "INSTRUMENTATION_STATUS: ";
                     int nameBegin = prefix.length();
                     int nameEnd = line.indexOf('=');
-                    int valueBegin = nameEnd + 1;
-
-                    mCurrentName = line.substring(nameBegin, nameEnd);
-                    mCurrentValue = line.substring(valueBegin);
+                    if (nameEnd < 0) {
+                        CLog.e("Line does not contain value. Logcat interrupted? (%s)", line);
+                        mCurrentValue = null;
+                        mCurrentName = null;
+                        mParseSuccessful = false;
+                        return;
+                    } else {
+                        int valueBegin = nameEnd + 1;
+                        mCurrentName = line.substring(nameBegin, nameEnd);
+                        mCurrentValue = line.substring(valueBegin);
+                    }
                 } else if (line.startsWith("INSTRUMENTATION_CODE: ")) {
                     try {
                         mResultCode = Integer.parseInt(line.substring(22));
                         mGotExitValue = true;
                     } catch (NumberFormatException ex) {
-                        CLog.w("Instrumentation code format unexpected");
+                        CLog.e("Instrumentation code format unexpected");
+                        mParseSuccessful = false;
+                        return;
                     }
                 } else if (mCurrentValue != null) {
                     mCurrentValue = mCurrentValue + line;
@@ -634,7 +687,7 @@
             }
 
             if (mValues != null) {
-                mListener.handleStatus(mValues);
+                mParseSuccessful &= mListener.handleStatus(mValues);
                 mValues = null;
             }
         }
@@ -651,7 +704,7 @@
          * Returns whether target instrumentation exited normally.
          */
         public boolean wasSuccessful() {
-            return mGotExitValue;
+            return mGotExitValue && mParseSuccessful;
         }
 
         /**
@@ -1451,11 +1504,12 @@
         Throwable interruptingError = null;
 
         try {
+            CLog.d("Running command '%s'", command);
             executeShellCommandAndReadOutput(command, parser);
-        } catch (Throwable ex) {
-            interruptingError = ex;
-        } finally {
             parser.flush();
+        } catch (Throwable ex) {
+            CLog.w("Instrumented call threw '%s'", ex.getMessage());
+            interruptingError = ex;
         }
 
         final boolean progressedSinceLastCall = mInstanceListerner.getCurrentTestId() != null ||
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
index 7ec09c9..a22634f 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/DeqpTestRunnerTest.java
@@ -1165,6 +1165,735 @@
         EasyMock.verify(mockDevice);
     }
 
+    public void testRun_sessionInfoValueMissing() throws Exception {
+        final String instrumentationAnswerOk =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test1\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+
+        final String instrumentationAnswerBroken =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n";
+
+        final TestIdentifier[] testIds = {
+                new TestIdentifier("dEQP-GLES3.instances", "test1"),
+        };
+
+        final String[] testPaths = {
+                "dEQP-GLES3.instances.test1",
+        };
+
+        Map<String,String> config = new HashMap<>();
+        config.put("glconfig", "rgba8888d24s8");
+        config.put("rotation", "unspecified");
+        config.put("surfacetype", "window");
+
+        Map<TestIdentifier, List<Map<String, String>>> instances = new HashMap<>();
+
+        instances.put(testIds[0], new ArrayList<Map<String,String>>());
+        instances.get(testIds[0]).add(config);
+
+        Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+        for (TestIdentifier id : testIds) {
+            tests.add(id);
+        }
+
+        ITestInvocationListener mockListener
+                = EasyMock.createStrictMock(ITestInvocationListener.class);
+        IMocksControl orderedControl = EasyMock.createStrictControl();
+        ITestDevice mockDevice = orderedControl.createMock(ITestDevice.class);
+        IDevice mockIDevice = orderedControl.createMock(IDevice.class);
+
+        DeqpTestRunner.IRecovery mockRecovery = EasyMock.createMock(DeqpTestRunner.IRecovery.class);
+
+        DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instances);
+        deqpTest.setAbi(UnitTests.ABI);
+        deqpTest.setDevice(mockDevice);
+        deqpTest.setBuildHelper(new StubCtsBuildHelper());
+        deqpTest.setRecovery(mockRecovery);
+
+        int version = 3 << 16;
+        EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+                .andReturn(Integer.toString(version)).atLeastOnce();
+
+        mockRecovery.onExecutionProgressed();
+        EasyMock.expectLastCall().atLeastOnce();
+
+        mockRecovery.setDevice(mockDevice);
+        EasyMock.expectLastCall().atLeastOnce();
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+            andReturn("").once();
+
+        EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+                EasyMock.eq(true),
+                EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+                .once();
+
+        // query config
+        expectRenderConfigQueryAndReturn(mockDevice,
+                "--deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-gl-major-version=3 "
+                + "--deqp-gl-minor-version=0", "Yes");
+
+        // run config and fail
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerBroken);
+
+        mockRecovery.recoverComLinkKilled();
+        EasyMock.expectLastCall().once();
+
+        // Re-try
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk);
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG)))
+                .andReturn("").once();
+
+        mockListener.testRunStarted(ID, 1);
+        EasyMock.expectLastCall().once();
+
+        // test1
+        mockListener.testStarted(EasyMock.eq(testIds[0]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[0]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        mockListener.testRunEnded(EasyMock.anyLong(), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        orderedControl.replay();
+        EasyMock.replay(mockListener);
+        EasyMock.replay(mockRecovery);
+        deqpTest.run(mockListener);
+
+        EasyMock.verify(mockListener);
+        orderedControl.verify();
+        EasyMock.verify(mockRecovery);
+    }
+
+    public void testRun_resultEventTypeMissing() throws Exception {
+        final String instrumentationAnswerOk =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test1\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+
+        final String instrumentationAnswerBroken =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test1\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n";
+
+        final TestIdentifier[] testIds = {
+                new TestIdentifier("dEQP-GLES3.instances", "test1"),
+        };
+
+        final String[] testPaths = {
+                "dEQP-GLES3.instances.test1",
+        };
+
+        Map<String,String> config = new HashMap<>();
+        config.put("glconfig", "rgba8888d24s8");
+        config.put("rotation", "unspecified");
+        config.put("surfacetype", "window");
+
+        Map<TestIdentifier, List<Map<String, String>>> instances = new HashMap<>();
+
+        instances.put(testIds[0], new ArrayList<Map<String,String>>());
+        instances.get(testIds[0]).add(config);
+
+        Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+        for (TestIdentifier id : testIds) {
+            tests.add(id);
+        }
+
+        ITestInvocationListener mockListener
+                = EasyMock.createStrictMock(ITestInvocationListener.class);
+        IMocksControl orderedControl = EasyMock.createStrictControl();
+        ITestDevice mockDevice = orderedControl.createMock(ITestDevice.class);
+        IDevice mockIDevice = orderedControl.createMock(IDevice.class);
+
+        DeqpTestRunner.IRecovery mockRecovery = EasyMock.createMock(DeqpTestRunner.IRecovery.class);
+
+        DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instances);
+        deqpTest.setAbi(UnitTests.ABI);
+        deqpTest.setDevice(mockDevice);
+        deqpTest.setBuildHelper(new StubCtsBuildHelper());
+        deqpTest.setRecovery(mockRecovery);
+
+        int version = 3 << 16;
+        EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+                .andReturn(Integer.toString(version)).atLeastOnce();
+
+        mockRecovery.onExecutionProgressed();
+        EasyMock.expectLastCall().atLeastOnce();
+
+        mockRecovery.setDevice(mockDevice);
+        EasyMock.expectLastCall().atLeastOnce();
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+            andReturn("").once();
+
+        EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+                EasyMock.eq(true),
+                EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+                .once();
+
+        // query config
+        expectRenderConfigQueryAndReturn(mockDevice,
+                "--deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-gl-major-version=3 "
+                + "--deqp-gl-minor-version=0", "Yes");
+
+        // run config and fail
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerBroken);
+
+        mockRecovery.recoverComLinkKilled();
+        EasyMock.expectLastCall().once();
+
+        // Re-try
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk);
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG)))
+                .andReturn("").once();
+
+        mockListener.testRunStarted(ID, 1);
+        EasyMock.expectLastCall().once();
+
+        // test1
+        mockListener.testStarted(EasyMock.eq(testIds[0]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[0]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        mockListener.testRunEnded(EasyMock.anyLong(), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        orderedControl.replay();
+        EasyMock.replay(mockListener);
+        EasyMock.replay(mockRecovery);
+        deqpTest.run(mockListener);
+
+        EasyMock.verify(mockListener);
+        orderedControl.verify();
+        EasyMock.verify(mockRecovery);
+    }
+
+    /**
+     * Test handling of interrupted line in the instrumentation output
+     * and recovery from the error.
+     */
+    public void testRun_testCasePathInterrupted() throws Exception {
+        final String instrumentationAnswerOk1 =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test1\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+        final String instrumentationAnswerOk2 =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test2\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+        final String instrumentationAnswerBroken =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePat";
+
+        final TestIdentifier[] testIds = {
+                new TestIdentifier("dEQP-GLES3.instances", "test1"),
+                new TestIdentifier("dEQP-GLES3.instances", "test2"),
+        };
+
+        final String[] testPaths = {
+                "dEQP-GLES3.instances.test1",
+                "dEQP-GLES3.instances.test2",
+        };
+
+        Map<String,String> config = new HashMap<>();
+        config.put("glconfig", "rgba8888d24s8");
+        config.put("rotation", "unspecified");
+        config.put("surfacetype", "window");
+
+        Map<TestIdentifier, List<Map<String, String>>> instances = new HashMap<>();
+
+        instances.put(testIds[0], new ArrayList<Map<String,String>>());
+        instances.get(testIds[0]).add(config);
+        instances.put(testIds[1], new ArrayList<Map<String,String>>());
+        instances.get(testIds[1]).add(config);
+
+        Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+        for (TestIdentifier id : testIds) {
+            tests.add(id);
+        }
+
+        ITestInvocationListener mockListener
+                = EasyMock.createStrictMock(ITestInvocationListener.class);
+        IMocksControl orderedControl = EasyMock.createStrictControl();
+        ITestDevice mockDevice = orderedControl.createMock(ITestDevice.class);
+        IDevice mockIDevice = orderedControl.createMock(IDevice.class);
+
+        DeqpTestRunner.IRecovery mockRecovery = EasyMock.createMock(DeqpTestRunner.IRecovery.class);
+
+        DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instances);
+        deqpTest.setAbi(UnitTests.ABI);
+        deqpTest.setDevice(mockDevice);
+        deqpTest.setBuildHelper(new StubCtsBuildHelper());
+        deqpTest.setRecovery(mockRecovery);
+
+        int version = 3 << 16;
+        EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+                .andReturn(Integer.toString(version)).atLeastOnce();
+
+        mockRecovery.onExecutionProgressed();
+        EasyMock.expectLastCall().atLeastOnce();
+
+        mockRecovery.setDevice(mockDevice);
+        EasyMock.expectLastCall().atLeastOnce();
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+            andReturn("").once();
+
+        EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+                EasyMock.eq(true),
+                EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+                .once();
+
+        // query config
+        expectRenderConfigQueryAndReturn(mockDevice,
+                "--deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-gl-major-version=3 "
+                + "--deqp-gl-minor-version=0", "Yes");
+
+        // run config and fail
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1,test2}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerBroken);
+
+        mockRecovery.recoverComLinkKilled();
+        EasyMock.expectLastCall().once();
+
+        // Re-try
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk1);
+
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test2}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk2);
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG)))
+                .andReturn("").once();
+
+        mockListener.testRunStarted(ID, 2);
+        EasyMock.expectLastCall().once();
+
+        // test1
+        mockListener.testStarted(EasyMock.eq(testIds[0]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[0]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        // test2
+        mockListener.testStarted(EasyMock.eq(testIds[1]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[1]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        mockListener.testRunEnded(EasyMock.anyLong(), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        orderedControl.replay();
+        EasyMock.replay(mockListener);
+        EasyMock.replay(mockRecovery);
+        deqpTest.run(mockListener);
+
+        EasyMock.verify(mockListener);
+        orderedControl.verify();
+        EasyMock.verify(mockRecovery);
+    }
+
+    /**
+     * Test handling of interrupted line in the instrumentation output
+     * and recovery from the error.
+     */
+    public void testRun_testCasePathMissing() throws Exception {
+        final String instrumentationAnswerOk1 =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test1\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+        final String instrumentationAnswerOk2 =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-BeginTestCase-TestCasePath=dEQP-GLES3.instances.test2\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Code=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-TestCaseResult-Details=Pass\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=TestCaseResult\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndTestCase\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=EndSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_CODE: 0\r\n";
+        final String instrumentationAnswerBroken =
+                "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=2014.x\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=releaseId\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=0xcafebabe\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Name=targetName\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=SessionInfo\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-SessionInfo-Value=android\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginSession\r\n"
+                + "INSTRUMENTATION_STATUS_CODE: 0\r\n"
+                + "INSTRUMENTATION_STATUS: dEQP-EventType=BeginTestCase\r\n";
+
+
+        final TestIdentifier[] testIds = {
+                new TestIdentifier("dEQP-GLES3.instances", "test1"),
+                new TestIdentifier("dEQP-GLES3.instances", "test2"),
+        };
+
+        final String[] testPaths = {
+                "dEQP-GLES3.instances.test1",
+                "dEQP-GLES3.instances.test2",
+        };
+
+        Map<String,String> config = new HashMap<>();
+        config.put("glconfig", "rgba8888d24s8");
+        config.put("rotation", "unspecified");
+        config.put("surfacetype", "window");
+
+        Map<TestIdentifier, List<Map<String, String>>> instances = new HashMap<>();
+
+        instances.put(testIds[0], new ArrayList<Map<String,String>>());
+        instances.get(testIds[0]).add(config);
+        instances.put(testIds[1], new ArrayList<Map<String,String>>());
+        instances.get(testIds[1]).add(config);
+
+        Collection<TestIdentifier> tests = new ArrayList<TestIdentifier>();
+        for (TestIdentifier id : testIds) {
+            tests.add(id);
+        }
+
+        ITestInvocationListener mockListener
+                = EasyMock.createStrictMock(ITestInvocationListener.class);
+        IMocksControl orderedControl = EasyMock.createStrictControl();
+        ITestDevice mockDevice = orderedControl.createMock(ITestDevice.class);
+        IDevice mockIDevice = orderedControl.createMock(IDevice.class);
+
+        DeqpTestRunner.IRecovery mockRecovery = EasyMock.createMock(DeqpTestRunner.IRecovery.class);
+
+        DeqpTestRunner deqpTest = new DeqpTestRunner(NAME, NAME, tests, instances);
+        deqpTest.setAbi(UnitTests.ABI);
+        deqpTest.setDevice(mockDevice);
+        deqpTest.setBuildHelper(new StubCtsBuildHelper());
+        deqpTest.setRecovery(mockRecovery);
+
+        int version = 3 << 16;
+        EasyMock.expect(mockDevice.getProperty("ro.opengles.version"))
+                .andReturn(Integer.toString(version)).atLeastOnce();
+
+        mockRecovery.onExecutionProgressed();
+        EasyMock.expectLastCall().atLeastOnce();
+
+        mockRecovery.setDevice(mockDevice);
+        EasyMock.expectLastCall().atLeastOnce();
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG))).
+            andReturn("").once();
+
+        EasyMock.expect(mockDevice.installPackage(EasyMock.<File>anyObject(),
+                EasyMock.eq(true),
+                EasyMock.eq(AbiUtils.createAbiFlag(UnitTests.ABI.getName())))).andReturn(null)
+                .once();
+
+        // query config
+        expectRenderConfigQueryAndReturn(mockDevice,
+                "--deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-gl-major-version=3 "
+                + "--deqp-gl-minor-version=0", "Yes");
+
+        // run config and fail
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1,test2}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerBroken);
+
+        mockRecovery.recoverComLinkKilled();
+        EasyMock.expectLastCall().once();
+
+        // Re-try
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test1}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk1);
+
+        runInstrumentationLineAndAnswer(mockDevice, mockIDevice,
+                "{dEQP-GLES3{instances{test2}}}",
+                "--deqp-caselist-file=" + CASE_LIST_FILE_NAME
+                + " --deqp-gl-config-name=rgba8888d24s8 "
+                + "--deqp-screen-rotation=unspecified "
+                + "--deqp-surface-type=window "
+                + "--deqp-log-images=disable "
+                + "--deqp-watchdog=enable", instrumentationAnswerOk2);
+
+        EasyMock.expect(mockDevice.uninstallPackage(EasyMock.eq(DEQP_ONDEVICE_PKG)))
+                .andReturn("").once();
+
+        mockListener.testRunStarted(ID, 2);
+        EasyMock.expectLastCall().once();
+
+        // test1
+        mockListener.testStarted(EasyMock.eq(testIds[0]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[0]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        // test2
+        mockListener.testStarted(EasyMock.eq(testIds[1]));
+        EasyMock.expectLastCall().once();
+
+        mockListener.testEnded(EasyMock.eq(testIds[1]), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        mockListener.testRunEnded(EasyMock.anyLong(), EasyMock.<Map<String, String>>notNull());
+        EasyMock.expectLastCall().once();
+
+        orderedControl.replay();
+        EasyMock.replay(mockListener);
+        EasyMock.replay(mockRecovery);
+        deqpTest.run(mockListener);
+
+        EasyMock.verify(mockListener);
+        orderedControl.verify();
+        EasyMock.verify(mockRecovery);
+    }
+
     /**
      * Test dEQP with multiple instances
      */