Merge "LOCAL_COMPATIBILITY_SUPPORT_FILES relative to $(TOP)." into nyc-dev
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
index 3d64d47..0c4e74c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ContactsTest.java
@@ -259,9 +259,7 @@
         assertManagedLocalContact(contactInfo);
         contactInfo.assertPhotoUrisReadable();
         assertFalse(contactInfo.hasPhotoId());
-
-        // Quirk: the _id column from the SIP lookup is actually of the data id, not the contact id.
-        // assertTrue(isEnterpriseContactId(contactInfo.contactId));
+        assertTrue(isEnterpriseContactId(contactInfo.contactId));
     }
 
     public void testPrimaryProfileEnterpriseEmailLookup_canAccessEnterpriseContact()
@@ -1093,12 +1091,32 @@
         contactInfo.assertPhotoUri(R.raw.managed_photo);
     }
 
+    private void assertContactInfoEquals(ContactInfo lhs, ContactInfo rhs) {
+        if (lhs == null) {
+            assertNull(rhs);
+        } else {
+            assertNotNull(rhs);
+            assertEquals(lhs.contactId, rhs.contactId);
+            assertEquals(lhs.displayName, rhs.displayName);
+            assertEquals(lhs.photoId, rhs.photoId);
+            assertEquals(lhs.photoThumbnailUri, rhs.photoThumbnailUri);
+            assertEquals(lhs.photoUri, rhs.photoUri);
+        }
+    }
+
     private ContactInfo getContactInfoFromPhoneLookupUri(boolean isEnterprise, String phoneNumber) {
         Uri baseUri = (isEnterprise) ? PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI
                 : PhoneLookup.CONTENT_FILTER_URI;
         Uri uri = baseUri.buildUpon().appendPath(phoneNumber).build();
-        return getContactInfoFromUri(uri, PhoneLookup._ID, PhoneLookup.DISPLAY_NAME,
+        ContactInfo contactInfo = getContactInfoFromUri(uri, PhoneLookup._ID,
+                PhoneLookup.DISPLAY_NAME,
                 PhoneLookup.PHOTO_URI, PhoneLookup.PHOTO_THUMBNAIL_URI, PhoneLookup.PHOTO_ID);
+
+        ContactInfo contactInfo2 = getContactInfoFromUri(uri, PhoneLookup.CONTACT_ID,
+                PhoneLookup.DISPLAY_NAME,
+                PhoneLookup.PHOTO_URI, PhoneLookup.PHOTO_THUMBNAIL_URI, PhoneLookup.PHOTO_ID);
+        assertContactInfoEquals(contactInfo, contactInfo2);
+        return contactInfo;
     }
 
     private ContactInfo getContactInfoFromEnterprisePhoneLookupUriWithSipAddress(
@@ -1107,7 +1125,7 @@
                 : PhoneLookup.CONTENT_FILTER_URI;
         Uri uri = baseUri.buildUpon().appendPath(sipAddress)
                 .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1").build();
-        return getContactInfoFromUri(uri, PhoneLookup._ID, PhoneLookup.DISPLAY_NAME,
+        return getContactInfoFromUri(uri, PhoneLookup.CONTACT_ID, PhoneLookup.DISPLAY_NAME,
                 PhoneLookup.PHOTO_URI, PhoneLookup.PHOTO_THUMBNAIL_URI, PhoneLookup.PHOTO_ID);
     }
 
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 41e13ee..43a7baf 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -44,23 +44,38 @@
       <activity android:label="Full screen activity for gesture dispatch testing"
               android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"/>
 
-        <service
-                android:name="android.accessibilityservice.cts.StubGestureAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
-            <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
+      <service
+              android:name="android.accessibilityservice.cts.StubGestureAccessibilityService"
+              android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
+          <intent-filter>
+              <action android:name="android.accessibilityservice.AccessibilityService" />
 
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
-            </intent-filter>
+              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+          </intent-filter>
 
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_gesture_a11y_service" />
-        </service>
+          <meta-data
+                  android:name="android.accessibilityservice"
+                  android:resource="@xml/stub_gesture_a11y_service" />
+      </service>
+
+      <activity android:label="@string/accessibility_soft_keyboard_modes_activity"
+              android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity"
+              android:windowSoftInputMode="stateAlwaysVisible" />
+
+      <service android:name=".StubSoftKeyboardModesAccessibilityService"
+               android:label="@string/title_soft_keyboard_modes_accessibility_service"
+               android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+          <intent-filter>
+              <action android:name="android.accessibilityservice.AccessibilityService"/>
+              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+          </intent-filter>
+          <meta-data android:name="android.accessibilityservice"
+                    android:resource="@xml/stub_soft_keyboard_modes_accessibility_service" />
+      </service>
 
     </application>
 
-  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
                    android:targetPackage="android.accessibilityservice.cts"
                    android:label="Tests for the accessibility APIs.">
         <meta-data android:name="listener"
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index db2707d..1287524 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -21,4 +21,4 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.accessibilityservice.cts" />
     </test>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml b/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml
new file mode 100644
index 0000000..577a204
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_soft_keyboard_modes_test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/edit_text"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" />
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 89ce1df..1c34c2c 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -145,5 +145,16 @@
         unveiling\n\n</string>
 
     <string name="full_screen_text_view">Full Screen Text View</string>
+    <!-- Soft Keyboard Modes tests -->
+
+    <!-- String title for the accessibility service -->
+    <string name="accessibility_soft_keyboard_modes_activity">
+        Soft Keyboard Modes Activity</string>
+
+    <string name="title_soft_keyboard_modes_accessibility_service">
+        Stub Soft Keyboard Modes Accessibility Service</string>
+
+    <!-- Description of the accessibility service -->
+    <string name="soft_keyboard_modes_accessibility_service_description">This Accessibility Service was installed for testing purposes. It can be uninstalled by going to Settings > Apps > android.view.accessibilityservice.services and selecting \"Uninstall\".</string>
 
 </resources>
diff --git a/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml b/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml
new file mode 100644
index 0000000..b5f9ede
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_soft_keyboard_modes_accessibility_service.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
+    android:canRetrieveWindowContent="true"
+    android:description="@string/soft_keyboard_modes_accessibility_service_description" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
new file mode 100644
index 0000000..649df16
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -0,0 +1,362 @@
+/**
+ * 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 android.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
+import android.app.UiAutomation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.Selection;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import android.accessibilityservice.cts.R;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test cases for testing the accessibility APIs for interacting with the soft keyboard show mode.
+ */
+public class AccessibilitySoftKeyboardModesTest extends ActivityInstrumentationTestCase2
+        <AccessibilitySoftKeyboardModesTest.SoftKeyboardModesActivity> {
+
+    /**
+     * Timeout in which we are waiting for the system to start the mock
+     * accessibility services.
+     */
+    private static final long TIMEOUT_SERVICE_TOGGLE_MS = 10000;
+
+    private static final long TIMEOUT_PROPAGATE_SETTING = 5000;
+
+    private static final int SHOW_MODE_AUTO = 0;
+    private static final int SHOW_MODE_HIDDEN = 1;
+
+    private int mCallbackCount;
+    private int mLastCallbackValue;
+
+    private Context mContext;
+    private StubSoftKeyboardModesAccessibilityService mService;
+    private SoftKeyboardController mKeyboardController;
+
+    private Object mLock = new Object();
+
+    public AccessibilitySoftKeyboardModesTest() {
+        super(SoftKeyboardModesActivity.class);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // If we don't call getActivity(), we get an empty list when requesting the number of
+        // windows on screen.
+        getActivity();
+
+        mContext = getInstrumentation().getContext();
+        UiAutomation uiAutomation;
+        try {
+            uiAutomation = getUiAutomation();
+        } catch (RuntimeException e) {
+            // Clean up UI Automation after other tests as we cannot request UI Automation with
+            // different flags if one already exists.
+            uiAutomation = getInstrumentation().getUiAutomation();
+            uiAutomation.destroy();
+
+            // Try to get UI Automation again.
+            uiAutomation = getUiAutomation();
+        }
+        String command = "pm grant " + mContext.getPackageName()
+                + "android.permission.WRITE_SECURE_SETTINGS";
+        executeShellCommand(uiAutomation, command);
+        uiAutomation.destroy();
+
+        disableAllServices();
+        enableTestService();
+
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        disableAllServices();
+    }
+
+    public void testApiReturnValues_shouldChangeValueOnRequestAndSendCallback() throws Exception {
+        mLastCallbackValue = -1;
+
+        final SoftKeyboardController.OnShowModeChangedListener listener =
+                new SoftKeyboardController.OnShowModeChangedListener() {
+                    @Override
+                    public void onShowModeChanged(SoftKeyboardController controller, int showMode) {
+                        synchronized (mLock) {
+                            mLastCallbackValue = showMode;
+                            mLock.notifyAll();
+                        }
+                    }
+                };
+        mKeyboardController.addOnShowModeChangedListener(listener);
+
+        // The soft keyboard should be in its' default mode.
+        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+        // Set the show mode to SHOW_MODE_HIDDEN.
+        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+
+        // Make sure the mode was changed.
+        assertEquals(SHOW_MODE_HIDDEN, mKeyboardController.getShowMode());
+
+        // Make sure we're getting the callback with the proper value.
+        waitForCallbackValueWithLock(SHOW_MODE_HIDDEN);
+
+        // Make sure we can set the value back to the default.
+        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_AUTO));
+        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+        waitForCallbackValueWithLock(SHOW_MODE_AUTO);
+
+        // Make sure we can remove our listener.
+        assertTrue(mKeyboardController.removeOnShowModeChangedListener(listener));
+    }
+
+    public void testHideSoftKeyboard_shouldHideAndShowKeyboardOnRequest() throws Exception {
+        // The soft keyboard should be in its' default mode.
+        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+        // Note: This Activity always has a visible keyboard (due to windowSoftInputMode being set
+        // to stateAlwaysVisible).
+        int numWindowsWithIme = mService.getTestWindowsListSize();
+
+        // Request the keyboard be hidden.
+        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+        waitForWindowStateChanged();
+
+        // Make sure the keyboard is hidden.
+        assertEquals(numWindowsWithIme - 1, mService.getTestWindowsListSize());
+
+        // Request the default keyboard mode.
+        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_AUTO));
+        waitForWindowStateChanged();
+
+        // Make sure the keyboard is visible.
+        assertEquals(numWindowsWithIme, mService.getTestWindowsListSize());
+    }
+
+
+    public void testHideSoftKeyboard_shouldHideKeyboardUntilAllServicesDisabled() throws Exception {
+        // The soft keyboard should be in its' default mode.
+        assertEquals(SHOW_MODE_AUTO, mKeyboardController.getShowMode());
+
+        // Note: This Activity always has a visible keyboard (due to windowSoftInputMode being set
+        // to stateAlwaysVisible).
+        int numWindowsWithIme = mService.getTestWindowsListSize();
+
+        // Set the show mode to SHOW_MODE_HIDDEN.
+        assertTrue(mKeyboardController.setShowMode(SHOW_MODE_HIDDEN));
+        waitForWindowStateChanged();
+
+        // Make sure the keyboard is hidden.
+        assertEquals(numWindowsWithIme - 1, mService.getTestWindowsListSize());
+
+        // Make sure we can see the soft keyboard once all Accessibility Services are disabled.
+        disableAllServices();
+        waitForWindowStateChanged();
+
+        // Enable our test service,.
+        enableTestService();
+
+        // See how many windows are present.
+        assertEquals(numWindowsWithIme, mService.getTestWindowsListSize());
+    }
+
+    private synchronized UiAutomation getUiAutomation() {
+        return getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+    }
+
+    private void executeShellCommand(UiAutomation uiAutomation, String command) throws Exception {
+        ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command);
+        BufferedReader reader = null;
+        try (InputStream inputStream = new FileInputStream(fd.getFileDescriptor())) {
+            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+            while (reader.readLine() != null) {
+                // Keep reading.
+            }
+        } finally {
+            if (reader != null) {
+                reader.close();
+            }
+            fd.close();
+        }
+    }
+
+    private void waitForCallbackValueWithLock(int expectedValue) throws Exception {
+        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_PROPAGATE_SETTING;
+
+        while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+            synchronized(mLock) {
+                if (mLastCallbackValue == expectedValue) {
+                    return;
+                }
+                try {
+                    mLock.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
+                } catch (InterruptedException e) {
+                    // Wait until timeout.
+                }
+            }
+        }
+
+        throw new IllegalStateException("last callback value <" + mLastCallbackValue
+                + "> does not match expected value < " + expectedValue + ">");
+    }
+
+    private void waitForWindowStateChanged() throws Exception {
+        UiAutomation uiAutomation = getUiAutomation();
+        try {
+            uiAutomation.executeAndWaitForEvent(new Runnable() {
+                @Override
+                public void run() {
+                    // Do nothing.
+                }
+            },
+            new UiAutomation.AccessibilityEventFilter() {
+                @Override
+                public boolean accept (AccessibilityEvent event) {
+                    return event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED;
+                }
+            },
+            TIMEOUT_PROPAGATE_SETTING);
+        } catch (TimeoutException ignored) {
+            // Ignore since the event could have occured before this method was called. There should            // be a check after this method returns to catch incorrect values.
+        } finally {
+            uiAutomation.destroy();
+        }
+    }
+
+    private void disableAllServices() throws Exception {
+        final Object waitLockForA11yOff = new Object();
+        AccessibilityManager manager =
+                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        manager.addAccessibilityStateChangeListener(
+                new AccessibilityManager.AccessibilityStateChangeListener() {
+                    @Override
+                    public void onAccessibilityStateChanged(boolean b) {
+                        synchronized (waitLockForA11yOff) {
+                            waitLockForA11yOff.notifyAll();
+                        }
+                    }
+                });
+        ContentResolver cr = mContext.getContentResolver();
+        UiAutomation uiAutomation = getUiAutomation();
+        executeShellCommand(uiAutomation, "settings put secure "
+                + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " null");
+        uiAutomation.destroy();
+        StubSoftKeyboardModesAccessibilityService.sInstance = null;
+        long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_TOGGLE_MS;
+        while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+            synchronized (waitLockForA11yOff) {
+                if (!manager.isEnabled()) {
+                    return;
+                }
+                try {
+                    waitLockForA11yOff.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
+                } catch (InterruptedException e) {
+                    // Ignored; loop again
+                }
+            }
+        }
+        throw new RuntimeException("Unable to turn accessibility off");
+    }
+
+    private void enableTestService() throws Exception {
+        Context context = getInstrumentation().getContext();
+        AccessibilityManager manager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        List<AccessibilityServiceInfo> serviceInfos =
+                manager.getInstalledAccessibilityServiceList();
+        for (int i = 0; i < serviceInfos.size(); i++) {
+            AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
+            if (context.getString(R.string.soft_keyboard_modes_accessibility_service_description)
+                    .equals(serviceInfo.getDescription())) {
+                ContentResolver cr = context.getContentResolver();
+                UiAutomation uiAutomation = getUiAutomation();
+                String command = "settings put secure "
+                        + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + " "
+                        + serviceInfo.getId();
+                executeShellCommand(uiAutomation, command);
+                executeShellCommand(uiAutomation, "settings put secure "
+                        + Settings.Secure.ACCESSIBILITY_ENABLED + " 1");
+                uiAutomation.destroy();
+
+                // We have enabled the services of interest and need to wait until they
+                // are instantiated and started (if needed) and the system binds to them.
+                long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_SERVICE_TOGGLE_MS;
+                while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
+                    synchronized(
+                            StubSoftKeyboardModesAccessibilityService.sWaitObjectForConnecting) {
+                        if (StubSoftKeyboardModesAccessibilityService.sInstance != null) {
+                            mService = StubSoftKeyboardModesAccessibilityService.sInstance;
+                            mKeyboardController = mService.getTestSoftKeyboardController();
+                            return;
+                        }
+                        try {
+                            StubSoftKeyboardModesAccessibilityService.sWaitObjectForConnecting.wait(
+                                    timeoutTimeMillis - SystemClock.uptimeMillis());
+                        } catch (InterruptedException e) {
+                            // Ignored; loop again
+                        }
+                    }
+                }
+                throw new IllegalStateException("Stub accessibility service not started");
+            }
+        }
+        throw new IllegalStateException("Stub accessiblity service not found");
+    }
+
+    /**
+     * Activity for testing the AccessibilityService API for hiding and showring the soft keyboard.
+     */
+    public static class SoftKeyboardModesActivity extends AccessibilityTestActivity {
+        public SoftKeyboardModesActivity() {
+            super();
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.accessibility_soft_keyboard_modes_test);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java
new file mode 100644
index 0000000..27d51d9
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubSoftKeyboardModesAccessibilityService.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.accessibilityservice.cts;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Intent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+/**
+ * Stub accessibility service for testing APIs to show/hide Soft Keyboard.
+ */
+public class StubSoftKeyboardModesAccessibilityService extends AccessibilityService {
+
+    public static Object sWaitObjectForConnecting = new Object();
+
+    public static StubSoftKeyboardModesAccessibilityService sInstance = null;
+
+    @Override
+    protected void onServiceConnected() {
+        synchronized (sWaitObjectForConnecting) {
+            sInstance = this;
+            sWaitObjectForConnecting.notifyAll();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        sInstance = null;
+        super.onDestroy();
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        /* do nothing */
+    }
+
+    @Override
+    public void onInterrupt() {
+        /* do nothing */
+    }
+
+    public SoftKeyboardController getTestSoftKeyboardController() {
+        return sInstance.getSoftKeyboardController();
+    }
+
+    public int getTestWindowsListSize() {
+        return sInstance.getWindows().size();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/RegionTest.java b/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
index 803f3e9..10e277b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RegionTest.java
@@ -1497,21 +1497,42 @@
         Rect oriRect = new Rect(0, 0, 10, 10);
         mRegion = new Region();
 
+        // test reading/writing an empty parcel
         Parcel p = Parcel.obtain();
         mRegion.writeToParcel(p, flags);
-        assertEquals(8, p.dataSize());
-
-        p = Parcel.obtain();
-        mRegion.set(oriRect);
-        mRegion.writeToParcel(p, flags);
-        assertEquals(24, p.dataSize());
 
         p.setDataPosition(0);
         Region dst = Region.CREATOR.createFromParcel(p);
+        assertTrue(dst.isEmpty());
+
+        // test reading/writing a single rect parcel
+        p = Parcel.obtain();
+        mRegion.set(oriRect);
+        mRegion.writeToParcel(p, flags);
+
+        p.setDataPosition(0);
+        dst = Region.CREATOR.createFromParcel(p);
         assertEquals(oriRect.top, dst.getBounds().top);
         assertEquals(oriRect.left, dst.getBounds().left);
         assertEquals(oriRect.bottom, dst.getBounds().bottom);
         assertEquals(oriRect.right, dst.getBounds().right);
+
+        // test reading/writing a multiple rect parcel
+        p = Parcel.obtain();
+        mRegion.op(5, 5, 15, 15, Region.Op.UNION);
+        mRegion.writeToParcel(p, flags);
+
+        p.setDataPosition(0);
+        dst = Region.CREATOR.createFromParcel(p);
+        assertTrue(dst.contains(2,2));
+        assertTrue(dst.contains(7,7));
+        assertTrue(dst.contains(12,12));
+        assertFalse(dst.contains(2,12));
+        assertFalse(dst.contains(12,2));
+        assertEquals(0, dst.getBounds().top);
+        assertEquals(0, dst.getBounds().left);
+        assertEquals(15, dst.getBounds().bottom);
+        assertEquals(15, dst.getBounds().right);
     }
 
     public void testDescribeContents() {
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java
index 434ac20..556fb2d 100644
--- a/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java
@@ -23,6 +23,7 @@
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.PhoneLookup;
@@ -60,7 +61,17 @@
         mBuilder.cleanup();
     }
 
-    private long[] setupTestData() throws Exception {
+    static class Id {
+        public long contactId;
+        public long dataId;
+
+        public Id (long contactId, long dataId) {
+            this.contactId = contactId;
+            this.dataId = dataId;
+        }
+    }
+
+    private Id[] setupTestData() throws Exception {
         TestRawContact rawContact = mBuilder.newRawContact()
                 .with(RawContacts.ACCOUNT_TYPE, "test_account")
                 .with(RawContacts.ACCOUNT_NAME, "test_name")
@@ -68,7 +79,7 @@
         rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
                 .with(StructuredName.DISPLAY_NAME, "Hot Tamale")
                 .insert();
-        rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
+        long dataId = rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
                 .with(Phone.DATA, "1111222333444")
                 .with(Email.TYPE, Phone.TYPE_HOME)
                 .insert().load().getId();
@@ -82,24 +93,41 @@
         rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
                 .with(StructuredName.DISPLAY_NAME, "Cold Tamago")
                 .insert();
-        rawContact2.newDataRow(Phone.CONTENT_ITEM_TYPE)
+       long dataId2 =  rawContact2.newDataRow(Phone.CONTENT_ITEM_TYPE)
                 .with(Phone.DATA, "2111222333444")
                 .with(Phone.TYPE, Phone.TYPE_OTHER)
-                .insert().load();
-
+                .insert().load().getId();
         rawContact2.load();
         TestContact contact2 = rawContact2.getContact().load();
 
-        return new long[] {
-                contact.getId(), contact2.getId()
+        // Contact with SIP address
+        TestRawContact rawContact3 = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_account")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+        rawContact3.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "Warm Tempura")
+                .insert();
+        long dataId3 = rawContact2.newDataRow(SipAddress.CONTENT_ITEM_TYPE)
+                .with(SipAddress.SIP_ADDRESS, "777@sip.org")
+                .with(SipAddress.TYPE, SipAddress.TYPE_WORK)
+                .insert().load().getId();
+        rawContact3.load();
+        TestContact contact3 = rawContact2.getContact().load();
+
+        return new Id[] {
+                new Id(contact.getId(), dataId),
+                new Id(contact2.getId(), dataId2),
+                new Id(contact3.getId(), dataId3)
         };
+
     }
 
     /**
      * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
      */
     public void testPhoneLookup_nomatch() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
                 .appendPath("no-such-phone-number").build();
 
@@ -110,12 +138,14 @@
      * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
      */
     public void testPhoneLookup_found1() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
                 .appendPath("1111222333444").build();
 
         final ContentValues expected = new ContentValues();
-        expected.put(PhoneLookup._ID, ids[0]);
+        expected.put(PhoneLookup._ID, ids[0].contactId);
+        expected.put(PhoneLookup.CONTACT_ID, ids[0].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[0].dataId);
         expected.put(PhoneLookup.NUMBER, "1111222333444");
 
         assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
@@ -125,22 +155,39 @@
      * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
      */
     public void testPhoneLookup_found2() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
                 .appendPath("2111222333444").build();
 
         final ContentValues expected = new ContentValues();
-        expected.put(PhoneLookup._ID, ids[1]);
+        expected.put(PhoneLookup._ID, ids[1].contactId);
+        expected.put(PhoneLookup.CONTACT_ID, ids[1].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[1].dataId);
         expected.put(PhoneLookup.NUMBER, "2111222333444");
 
         assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
     }
 
+    public void testPhoneLookup_sip_found() throws Exception {
+        Id[] ids = setupTestData();
+        final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
+                .appendPath("777@sip.org")
+                .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1")
+                .build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup.CONTACT_ID, ids[2].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[2].dataId);
+        expected.put(SipAddress.SIP_ADDRESS, "777@sip.org");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
     /**
      * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
      */
     public void testPhoneLookupEnterprise_nomatch() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
                 .appendPath("no-such-phone-number").build();
 
@@ -151,12 +198,14 @@
      * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
      */
     public void testPhoneLookupEnterprise_found1() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
                 .appendPath("1111222333444").build();
 
         final ContentValues expected = new ContentValues();
-        expected.put(PhoneLookup._ID, ids[0]);
+        expected.put(PhoneLookup._ID, ids[0].contactId);
+        expected.put(PhoneLookup.CONTACT_ID, ids[0].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[0].dataId);
         expected.put(PhoneLookup.NUMBER, "1111222333444");
 
         assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
@@ -166,27 +215,45 @@
      * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
      */
     public void testPhoneLookupEnterprise_found2() throws Exception {
-        long[] ids = setupTestData();
+        Id[] ids = setupTestData();
         final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
                 .appendPath("2111222333444").build();
 
         final ContentValues expected = new ContentValues();
-        expected.put(PhoneLookup._ID, ids[1]);
+        expected.put(PhoneLookup._ID, ids[1].contactId);
+        expected.put(PhoneLookup.CONTACT_ID, ids[1].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[1].dataId);
         expected.put(PhoneLookup.NUMBER, "2111222333444");
 
         assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
     }
 
-    private void assertCursorStoredValuesWithContactsFilter(Uri uri, long[] contactsId,
+    public void testPhoneLookupEnterprise_sip_found() throws Exception {
+        Id[] ids = setupTestData();
+        final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath("777@sip.org")
+                .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1")
+                .build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup._ID, ids[2].dataId);
+        expected.put(PhoneLookup.CONTACT_ID, ids[2].contactId);
+        expected.put(PhoneLookup.DATA_ID, ids[2].dataId);
+        expected.put(SipAddress.SIP_ADDRESS, "777@sip.org");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
+    private void assertCursorStoredValuesWithContactsFilter(Uri uri, Id[] ids,
             ContentValues... expected) {
         // We need this helper function to add a filter for specific contacts because
         // otherwise tests will fail if performed on a device with existing contacts data
         StringBuilder sb = new StringBuilder();
         sb.append(Contacts._ID + " in ");
         sb.append("(");
-        for (int i = 0; i < contactsId.length; i++) {
+        for (int i = 0; i < ids.length; i++) {
             if (i != 0) sb.append(",");
-            sb.append(contactsId[i]);
+            sb.append(ids[i].contactId);
         }
         sb.append(")");
         DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null,
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 916cf6d..596f5fa 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -845,6 +845,9 @@
     }
 
     public void testBroadcastGenreEncodeDecode() {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
         String[] broadcastGenre = new String[] {"Animation", "Classic, opera"};
         insertProgramWithBroadcastGenre(broadcastGenre);
         try (Cursor c = mContentResolver.query(TvContract.Programs.CONTENT_URI,
@@ -857,6 +860,9 @@
     }
 
     public void testBroadcastGenreQueryChannel() {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
         // "Animation" is mapped to Genres.MOVIES
         // "Classic, opera" is mapped to Genres.MUSIC
         insertProgramWithBroadcastGenre(new String[]{"Animation"});
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
new file mode 100644
index 0000000..860c394
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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 android.view.cts;
+
+import android.view.cts.R;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.cts.util.PollingCheck;
+import android.os.Looper;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.FrameMetrics;
+import android.view.View;
+import android.view.Window;
+import android.widget.ScrollView;
+
+import java.lang.Thread;
+import java.lang.Exception;
+import java.lang.System;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class FrameMetricsListenerTest extends ActivityInstrumentationTestCase2<MockActivity> {
+
+    private Instrumentation mInstrumentation;
+    private Window.FrameMetricsListener mFrameMetricsListener;
+    private Activity mActivity;
+
+    public FrameMetricsListenerTest() {
+        super(MockActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        mInstrumentation = getInstrumentation();
+    }
+
+    private void layout(final int layoutId) {
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.setContentView(layoutId);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    public void testReceiveData() throws Throwable {
+        layout(R.layout.scrollview_layout);
+        final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view);
+        final ArrayList<FrameMetrics> data = new ArrayList<>();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final Window myWindow = mActivity.getWindow();
+        final Window.FrameMetricsListener listener =
+            new Window.FrameMetricsListener() {
+               @Override
+               public void onMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+                   assertEquals(myWindow, window);
+                   assertEquals(0, dropCount);
+                   data.add(new FrameMetrics(frameMetrics));
+               }
+            };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().addFrameMetricsListener(listener, handler);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scrollView.fling(-100);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return data.size() != 0;
+            }
+        }.run();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().removeFrameMetricsListener(listener);
+                data.clear();
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scrollView.fling(100);
+                assertEquals(0, data.size());
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+    }
+
+    public void testMultipleListeners() throws Throwable {
+        layout(R.layout.scrollview_layout);
+        final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view);
+        final ArrayList<FrameMetrics> data1 = new ArrayList<>();
+        final Handler handler = new Handler(Looper.getMainLooper());
+        final Window myWindow = mActivity.getWindow();
+
+        final Window.FrameMetricsListener frameMetricsListener1 =
+            new Window.FrameMetricsListener() {
+               @Override
+               public void onMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+                   assertEquals(myWindow, window);
+                   assertEquals(0, dropCount);
+                   data1.add(new FrameMetrics(frameMetrics));
+               }
+            };
+        final ArrayList<FrameMetrics> data2 = new ArrayList<>();
+        final Window.FrameMetricsListener frameMetricsListener2 =
+            new Window.FrameMetricsListener() {
+               @Override
+               public void onMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+                   assertEquals(myWindow, window);
+                   assertEquals(0, dropCount);
+                   data2.add(new FrameMetrics(frameMetrics));
+               }
+            };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().addFrameMetricsListener(frameMetricsListener1, handler);
+                mActivity.getWindow().addFrameMetricsListener(frameMetricsListener2, handler);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scrollView.fling(-100);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return data1.size() != 0 && data1.size() == data2.size();
+            }
+        }.run();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().removeFrameMetricsListener(frameMetricsListener1);
+                mActivity.getWindow().removeFrameMetricsListener(frameMetricsListener2);
+            }
+        });
+    }
+
+    public void testDropCount() throws Throwable {
+        layout(R.layout.scrollview_layout);
+        final ScrollView scrollView = (ScrollView) mActivity.findViewById(R.id.scroll_view);
+
+        final Window window = mActivity.getWindow();
+        final AtomicInteger framesDropped = new AtomicInteger();
+        final AtomicInteger frameCount = new AtomicInteger();
+
+        final HandlerThread thread = new HandlerThread("Listener");
+        thread.start();
+        final Handler handler = new Handler(thread.getLooper());
+        final Window.FrameMetricsListener frameMetricsListener =
+            new Window.FrameMetricsListener() {
+               @Override
+               public void onMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCount) {
+                    try {
+                        Thread.sleep(100);
+                        framesDropped.addAndGet(dropCount);
+                    } catch (Exception e) { }
+               }
+            };
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().addFrameMetricsListener(frameMetricsListener, handler);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scrollView.fling(-100);
+            }
+        });
+
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return framesDropped.get() > 0;
+            }
+        }.run();
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.getWindow().removeFrameMetricsListener(frameMetricsListener);
+            }
+        });
+    }
+}
+
+
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 66fcd44..7bde54e 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;
         }
 
         /**
@@ -1452,11 +1505,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 2c8a816..2ba6b42 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
@@ -1162,6 +1162,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
      */