Merge "Make vm-tests-tf use ITestDevice.pushDir" into ics-mr0
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 29164bc..81758f6 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -28,6 +28,8 @@
       <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService"/>
       </intent-filter>
+      <meta-data android:name="android.accessibilityservice"
+                android:resource="@xml/accessibilityservice" />
     </service>
 
     <service android:name=".DelegatingAccessibilityService$DelegatingConnectionService"
diff --git a/tests/accessibilityservice/res/xml/accessibilityservice.xml b/tests/accessibilityservice/res/xml/accessibilityservice.xml
new file mode 100644
index 0000000..31a3f71
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/accessibilityservice.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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"
+    android:canRetrieveWindowContent="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
 /**
  * Interface for interacting with the accessibility service mock.
  */
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
 
     /**
      * Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
index 37a9c54..5680b7c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/delegate/DelegatingAccessibilityService.java
@@ -17,7 +17,6 @@
 package android.accessibilityservice.delegate;
 
 import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceDelegate;
 import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
 import android.app.Service;
@@ -64,14 +63,6 @@
 
     @Override
     protected void onServiceConnected() {
-        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
-        info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
-        info.packageNames = new String[] {
-            "com.android.cts.accessibilityservice"
-        };
-        setServiceInfo(info);
-
         // the service is ready to be used only
         // after the system has bound to it
         sServiceDelegate = this;
diff --git a/tests/tests/accessibilityservice/AndroidManifest.xml b/tests/tests/accessibilityservice/AndroidManifest.xml
index 70e1092..c75ffb2 100644
--- a/tests/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/tests/accessibilityservice/AndroidManifest.xml
@@ -26,6 +26,9 @@
       <activity android:label="@string/accessibility_end_to_end_test_activity"
               android:name="android.accessibilityservice.cts.AccessibilityEndToEndTestActivity"/>
 
+      <activity android:label="@string/accessibility_query_window_test_activity"
+              android:name="android.accessibilityservice.cts.AccessibilityWindowQueryActivity"/>
+
   </application>
 
   <instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/tests/tests/accessibilityservice/res/layout/query_window_test.xml b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
new file mode 100644
index 0000000..4b32eb1
--- /dev/null
+++ b/tests/tests/accessibilityservice/res/layout/query_window_test.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    >
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button1"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button1"
+        />
+        <Button
+            android:id="@+id/button2"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button2"
+        />
+        <Button
+            android:id="@+id/button3"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button3"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button4"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button4"
+        />
+        <Button
+            android:id="@+id/button5"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button5"
+        />
+        <Button
+            android:id="@+id/button6"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button6"
+            android:contentDescription="@string/contentDescription"
+        />
+    </LinearLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        >
+        <Button
+            android:id="@+id/button7"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button7"
+        />
+        <Button
+            android:id="@+id/button8"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button8"
+        />
+        <Button
+            android:id="@+id/button9"
+            android:layout_width="160px"
+            android:layout_height="100px"
+            android:text="@string/button9"
+        />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/tests/accessibilityservice/res/values/strings.xml b/tests/tests/accessibilityservice/res/values/strings.xml
index e86d3cc..7b3cd0f 100644
--- a/tests/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/tests/accessibilityservice/res/values/strings.xml
@@ -44,4 +44,38 @@
     <!-- String notification message -->
     <string name="notification_message">Notification message</string>
 
+
+    <!-- String title of the accessibility query window test activity -->
+    <string name="accessibility_query_window_test_activity">Query window test</string>
+
+    <!-- String Button1 text -->
+    <string name="button1">Button1</string>
+
+    <!-- String Button2 text -->
+    <string name="button2">Button2</string>
+
+    <!-- String Button3 text -->
+    <string name="button3">Button3</string>
+
+    <!-- String Button4 text -->
+    <string name="button4">Button4</string>
+
+    <!-- String Button5 text -->
+    <string name="button5">Button5</string>
+
+    <!-- String Button6 text -->
+    <string name="button6">Button6</string>
+
+    <!-- String with content description for Button6 -->
+    <string name="contentDescription">contentDescription</string>
+
+    <!-- String Button7 text -->
+    <string name="button7">Button7</string>
+
+    <!-- String Button8 text -->
+    <string name="button8">Button8</string>
+
+    <!-- String Button9 text -->
+    <string name="button9">Button9</string>
+
 </resources>
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
index b5ebf19..868bd72 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/IAccessibilityServiceDelegate.aidl
@@ -20,7 +20,7 @@
 /**
  * Interface for interacting with the accessibility service mock.
  */
-interface IAccessibilityServiceDelegate {
+oneway interface IAccessibilityServiceDelegate {
 
     /**
      * Delegate an {@link android.view.accessibility.AccessibilityEvent}.
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
new file mode 100644
index 0000000..1e7c625
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityDelegateHelper.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 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 static junit.framework.Assert.fail;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceDelegate;
+import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.List;
+
+/**
+ * This class is a helper for implementing delegation of accessibility events.
+ * This is required because changing accessibility settings can be performed
+ * either via writing to the secure settings, which a CTS test cannot do, or
+ * by manual interaction from the settings application. However, manually
+ * enabling the testing accessibility service does not work because the test
+ * runner restarts the package before running the tests, thus breaking the
+ * bond between the system and the manually enabled testing service. So,
+ * we have a delegating service that is manually enabled which services as a
+ * proxy to deliver accessibility events to the testing service.
+ */
+class AccessibilityDelegateHelper implements ServiceConnection {
+
+   /**
+    * Timeout required for pending Binder calls or event processing to
+    * complete.
+    */
+    public static final long TIMEOUT_ASYNC_PROCESSING = 500;
+
+    /**
+     * The package of the accessibility service mock interface.
+     */
+    private static final String DELEGATING_SERVICE_PACKAGE =
+        "android.accessibilityservice.delegate";
+
+    /**
+     * The package of the delegating accessibility service interface.
+     */
+    private static final String DELEGATING_SERVICE_CLASS_NAME =
+        "android.accessibilityservice.delegate.DelegatingAccessibilityService";
+
+    /**
+     * The package of the delegating accessibility service connection interface.
+     */
+    private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
+        "android.accessibilityservice.delegate."
+            + "DelegatingAccessibilityService$DelegatingConnectionService";
+
+    /**
+     * The client accessibility service to which to delegate.
+     */
+    private final AccessibilityService mAccessibilityService;
+
+    /**
+     * Lock for synchronization.
+     */
+    private final Object mLock = new Object();
+
+    /**
+     * Whether this delegate is initialized.
+     */
+    private boolean mInitialized;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param service The service to which to delegate.
+     */
+    public AccessibilityDelegateHelper(AccessibilityService service) {
+        mAccessibilityService = service;
+    }
+
+    /**
+     * Ensures the required setup for the test performed and that it is bound to the
+     * DelegatingAccessibilityService which runs in another process. The setup is
+     * enabling accessibility and installing and enabling the delegating accessibility
+     * service this test binds to.
+     * </p>
+     * Note: Please look at the class description for information why such an
+     *       approach is taken.
+     */
+    public void bindToDelegatingAccessibilityService(Context context) {
+        // check if accessibility is enabled
+        AccessibilityManager accessibilityManager = (AccessibilityManager) context
+                .getSystemService(Service.ACCESSIBILITY_SERVICE);
+
+        if (!accessibilityManager.isEnabled()) {
+            throw new IllegalStateException("Delegating service not enabled. "
+                    + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+        }
+
+        // check if the delegating service is running
+        List<AccessibilityServiceInfo> enabledServices =
+            accessibilityManager.getEnabledAccessibilityServiceList(
+                    AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        boolean delegatingServiceRunning = false;
+        for (AccessibilityServiceInfo enabledService : enabledServices) {
+            ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
+            if (DELEGATING_SERVICE_PACKAGE.equals(serviceInfo.packageName)
+                    && DELEGATING_SERVICE_CLASS_NAME.equals(serviceInfo.name)) {
+                delegatingServiceRunning = true;
+                break;
+            }
+        }
+
+        if (!delegatingServiceRunning) {
+            // delegating service not running, so check if it is installed at all
+            try {
+                PackageManager packageManager = context.getPackageManager();
+                packageManager.getServiceInfo(new ComponentName(DELEGATING_SERVICE_PACKAGE,
+                        DELEGATING_SERVICE_CLASS_NAME), 0);
+            } catch (NameNotFoundException nnfe) {
+                throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
+                        " not installed.");
+            }
+
+            throw new IllegalStateException("Delegating Accessibility Service not running."
+                     + "(Settings -> Accessibility -> Delegating Accessibility Service)");
+        }
+
+        Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
+                DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
+        context.bindService(intent, this, Context.BIND_AUTO_CREATE);
+
+        final long beginTime = SystemClock.uptimeMillis();
+        synchronized (mLock) {
+            while (true) {
+                if (mInitialized) {
+                    return;
+                }
+                final long elapsedTime = (SystemClock.uptimeMillis() - beginTime);
+                final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+                if (remainingTime <= 0) {
+                    if (!mInitialized) {
+                        throw new IllegalStateException("Cound not connect to the delegating"
+                                + " accessibility service");
+                    }
+                    return;
+                }
+                try {
+                    mLock.wait(remainingTime);
+                } catch (InterruptedException ie) {
+                    /* ignore */
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
+     */
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        IAccessibilityServiceDelegateConnection connection =
+            IAccessibilityServiceDelegateConnection.Stub.asInterface(service);
+        try {
+            connection.setAccessibilityServiceDelegate(new IAccessibilityServiceDelegate.Stub() {
+                @Override
+                public void onAccessibilityEvent(AccessibilityEvent event) {
+                    mAccessibilityService.onAccessibilityEvent(event);
+                }
+                @Override
+                public void onInterrupt() {
+                    mAccessibilityService.onInterrupt();
+                }
+            });
+            mInitialized = true;
+            synchronized (mLock) {
+                mLock.notifyAll();
+            }
+        } catch (RemoteException re) {
+            fail("Could not set delegate to the delegating service.");
+        }
+    }
+
+    /**
+     * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
+     */
+    public void onServiceDisconnected(ComponentName name) {
+        mInitialized = false;
+        /* do nothing */
+    }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index af6d37d..5a565ec 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,40 +16,25 @@
 
 package android.accessibilityservice.cts;
 
-import com.android.cts.accessibilityservice.R;
-
 import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.IAccessibilityServiceDelegate;
-import android.accessibilityservice.IAccessibilityServiceDelegateConnection;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Service;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ListView;
 
+import com.android.cts.accessibilityservice.R;
+
 import junit.framework.TestCase;
 
 import java.util.Iterator;
@@ -62,18 +47,22 @@
  * creating an {@link Activity} and poking around so {@link AccessibilityEvent}s
  * are generated and their correct dispatch verified.
  * <p>
- * Note: The end-to-end test is composed of two APKs, one with a mock accessibility
- * service, another with the instrumented activity and test cases. The motivation for
- * two APKs design is that CTS tests cannot access the secure settings which is
- * required for enabling accessibility and accessibility services. Therefore, manual
- * installation of the <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
  * whose source is located at <strong>cts/tests/accessibility</strong> is required.
- * Once the former package has been installed accessibility must be enabled (Settings ->
- * Accessibility), the mock service must be enabled (Settings -> Accessibility
- * -> Mock Accessibility Service), and then the CTS tests in this package can be
- * successfully run. Further, the mock and tests run in separate processes since
- * the instrumentation restarts the process in which it is running and this
- * breaks the binding between the mock accessibility service and the system.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
  */
 public class AccessibilityEndToEndTest extends
         ActivityInstrumentationTestCase2<AccessibilityEndToEndTestActivity> {
@@ -82,35 +71,10 @@
      * Timeout required for pending Binder calls or event processing to
      * complete.
      */
-    private static final long MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING = 500;
+    private static final long TIMEOUT_ASYNC_PROCESSING = 500;
 
     /**
-     * The count of the polling attempts during {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}
-     */
-    private static final long COUNT_POLLING_ATTEMPTS = 10;
-
-    /**
-     * The package of the accessibility service mock interface.
-     */
-    private static final String DELEGATING_SERVICE_PACKAGE =
-        "android.accessibilityservice.delegate";
-
-    /**
-     * The package of the delegating accessibility service interface.
-     */
-    private static final String DELEGATING_SERVICE_CLASS_NAME =
-        "android.accessibilityservice.delegate.DelegatingAccessibilityService";
-
-    /**
-     * The package of the delegating accessibility service connection interface.
-     */
-    private static final String DELEGATING_SERVICE_CONNECTION_CLASS_NAME =
-        "android.accessibilityservice.delegate."
-            + "DelegatingAccessibilityService$DelegatingConnectionService";
-
-    /**
-     * Creates a new instance for testing
-     * {@link AccessibilityEndToEndTestActivity}.
+     * Creates a new instance for testing {@link AccessibilityEndToEndTestActivity}.
      *
      * @throws Exception If any error occurs.
      */
@@ -124,7 +88,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         // create and populate the expected event
         AccessibilityEvent selectedEvent = AccessibilityEvent.obtain();
@@ -135,7 +99,7 @@
         selectedEvent.setItemCount(2);
         selectedEvent.setCurrentItemIndex(1);
         selectedEvent.setEnabled(true);
-        selectedEvent.setScrollable(true);
+        selectedEvent.setScrollable(false);
         selectedEvent.setFromIndex(0);
         selectedEvent.setToIndex(1);
 
@@ -153,7 +117,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -162,7 +126,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         // create and populate the expected event
         AccessibilityEvent clickedEvent = AccessibilityEvent.obtain();
@@ -186,7 +150,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -195,7 +159,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         // create and populate the expected event
         AccessibilityEvent longClickedEvent = AccessibilityEvent.obtain();
@@ -219,7 +183,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -228,7 +192,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         // create and populate the expected event
         AccessibilityEvent focusedEvent = AccessibilityEvent.obtain();
@@ -254,7 +218,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -270,7 +234,7 @@
         });
 
         // wait for the generated focus event to be dispatched
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
 
         final String beforeText = activity.getString(R.string.text_input_blah);
@@ -302,7 +266,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -311,7 +275,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         String title = activity.getString(R.string.alert_title);
         String message = activity.getString(R.string.alert_message);
@@ -342,7 +306,7 @@
         });
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
     }
 
     @LargeTest
@@ -351,7 +315,7 @@
 
         // Wait for accessibility events to settle i.e. for all events generated
         // while bringing the activity up to be delivered so they do not interfere.
-        SystemClock.sleep(MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING);
+        SystemClock.sleep(TIMEOUT_ASYNC_PROCESSING);
 
         String message = activity.getString(R.string.notification_message);
 
@@ -383,61 +347,18 @@
         notificationManager.notify(notificationId, notification);
 
         // verify if all expected methods have been called
-        assertMockServiceVerifiedWithinTimeout(service);
+        service.verify();
 
         // remove the notification
         notificationManager.cancel(notificationId);
     }
 
-    /**
-     * Asserts the the mock accessibility service has been successfully verified
-     * (which is it has received the expected method calls with expected
-     * arguments) within the {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}. The
-     * verified state is checked by polling upon small intervals.
-     *
-     * @param service The service to verify.
-     * @throws Exception If the verification has failed with exception after the
-     *             {@link #MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING}.
-     */
-    private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
-            throws Throwable {
-        Throwable lastVerifyThrowable = null;
-        long beginTime = SystemClock.uptimeMillis();
-        long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
+    static class MockAccessibilityService extends AccessibilityService {
 
-        // poll until the timeout has elapsed
-        while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
-            // sleep first since immediate call will always fail
-            try {
-                Thread.sleep(pollTmeout);
-            } catch (InterruptedException ie) {
-                /* ignore */
-            }
-
-            try {
-                service.verify();
-                // success - reset so it is not accept more events
-                service.reset();
-                return;
-            } catch (IllegalStateException ise) {
-                // this exception is thrown if the expected event is not
-                // received yet, so we will keep trying within the timeout
-                lastVerifyThrowable = ise;
-                continue;
-            } catch (Throwable t) {
-                // we have just failed
-                lastVerifyThrowable = t;
-                break;
-            }
-        }
-
-        // failure - reset so it is not accept more events
-        service.reset();
-        throw lastVerifyThrowable;
-    }
-
-    static class MockAccessibilityService extends AccessibilityService implements
-            ServiceConnection {
+        /**
+         * Helper for connecting to the delegating accessibility service.
+         */
+        private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
 
         /**
          * The singleton instance.
@@ -451,6 +372,11 @@
             new LinkedList<AccessibilityEvent>();
 
         /**
+         * Reusable temporary builder.
+         */
+        private final StringBuilder mTempBuilder = new StringBuilder();
+
+        /**
          * Interruption call this service expects to receive.
          */
         private boolean mExpectedInterrupt;
@@ -461,14 +387,9 @@
         private boolean mReplaying;
 
         /**
-         * Flag indicating if this mock is initialized.
+         * Lock for synchronization.
          */
-        private boolean mInitialized;
-
-        /**
-         * The {@link Context} whose services to utilize.
-         */
-        private Context mContext;
+        private final Object mLock = new Object();
 
         /**
          * Gets the {@link MockAccessibilityService} singleton.
@@ -491,100 +412,57 @@
          * Creates a new instance.
          */
         private MockAccessibilityService(Context context) {
-            mContext = context;
-            ensureSetupAndBoundToDelegatingAccessibilityService();
-        }
-
-        /**
-         * Ensures the required setup for the test performed and that it is bound to the
-         * DelegatingAccessibilityService which runs in another process. The setup is
-         * enabling accessibility and installing and enabling the delegating accessibility
-         * service this test binds to.
-         * </p>
-         * Note: Please look at the class description for information why such an
-         *       approach is taken.
-         */
-        public void ensureSetupAndBoundToDelegatingAccessibilityService() {
-            // check if accessibility is enabled
-            AccessibilityManager accessibilityManager = (AccessibilityManager) mContext
-                    .getSystemService(Service.ACCESSIBILITY_SERVICE);
-
-            if (!accessibilityManager.isEnabled()) {
-                throw new IllegalStateException("Accessibility not enabled. "
-                        + "(Settings -> Accessibility)");
-            }
-
-            // check if the delegating service is running
-            ComponentName delegatingServiceName = new ComponentName(
-                    DELEGATING_SERVICE_PACKAGE, DELEGATING_SERVICE_CLASS_NAME);
-            ActivityManager activityManager = (ActivityManager) mContext
-                    .getSystemService(Service.ACTIVITY_SERVICE);
-            boolean delegatingServiceRunning = false;
-
-            for (RunningServiceInfo runningServiceInfo : activityManager.getRunningServices(100)) {
-                if (delegatingServiceName.equals(runningServiceInfo.service)) {
-                    delegatingServiceRunning = true;
-                    break;
-                }
-            }
-
-            if (!delegatingServiceRunning) {
-                // delegating service not running, so check if it is installed at all
-                try {
-                    PackageManager packageManager = mContext.getPackageManager();
-                    packageManager.getServiceInfo(delegatingServiceName, 0);
-                } catch (NameNotFoundException nnfe) {
-                    throw new IllegalStateException("CtsDelegatingAccessibilityService.apk" +
-                            " not installed.");
-                }
-
-                throw new IllegalStateException("Delegating Accessibility Service not running."
-                         + "(Settings -> Accessibility -> Delegating Accessibility Service)");
-            }
-
-            Intent intent = new Intent().setClassName(DELEGATING_SERVICE_PACKAGE,
-                    DELEGATING_SERVICE_CONNECTION_CLASS_NAME);
-            mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
-
-            long beginTime = SystemClock.uptimeMillis();
-            long pollTmeout = MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING / COUNT_POLLING_ATTEMPTS;
-
-            // bind to the delegating service which runs in another process by
-            // polling until the binder connection is established
-            while (SystemClock.uptimeMillis() - beginTime < MAX_TIMEOUT_ASYNCHRONOUS_PROCESSING) {
-                if (mInitialized) {
-                    // success
-                    return;
-                }
-                try {
-                    Thread.sleep(pollTmeout);
-                } catch (InterruptedException ie) {
-                    /* ignore */
-                }
-            }
+            mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+            mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(
+                    context);
         }
 
         /**
          * Starts replaying the mock.
          */
-        private void replay() {
+        public void replay() {
             mReplaying = true;
         }
 
         /**
-         * Verifies if all expected service methods have been called.
+         * Verifies the mock service.
+         *
+         * @throws IllegalStateException If the verification has failed.
          */
-        private void verify() {
-            synchronized (this) {
-                if (!mReplaying) {
-                    throw new IllegalStateException("Did you forget to call replay()");
-                }
-                if (mExpectedInterrupt) {
-                    throw new IllegalStateException("Expected call to #interrupt() not received");
-                }
-                if (!mExpectedEvents.isEmpty()) {
-                    throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
-                            + "events \"" + mExpectedEvents + "\" not received");
+        public void verify() throws IllegalStateException {
+            StringBuilder problems = mTempBuilder;
+            final long startTime = SystemClock.uptimeMillis();
+            synchronized (mLock) {
+                while (true) {
+                    if (!mReplaying) {
+                        throw new IllegalStateException("Did you forget to call replay()?");
+                    }
+                    if (!mExpectedInterrupt && mExpectedEvents.isEmpty()) {
+                        reset();
+                        return; // success
+                    }
+                    problems.setLength(0);
+                    if (mExpectedInterrupt) {
+                        problems.append("Expected call to #interrupt() not received.");
+                    }
+                    if (!mExpectedEvents.isEmpty()) {
+                        problems.append("Expected a call to onAccessibilityEvent() for events \""
+                                + mExpectedEvents + "\" not received.");
+                    }
+                    final long elapsedTime = SystemClock.uptimeMillis() - startTime;
+                    final long remainingTime = TIMEOUT_ASYNC_PROCESSING - elapsedTime;
+                    if (remainingTime <= 0) {
+                        reset();
+                        if (problems.length() > 0) {
+                            throw new IllegalStateException(problems.toString());
+                        }
+                        return;
+                    }
+                    try {
+                        mLock.wait(remainingTime);
+                    } catch (InterruptedException ie) {
+                        /* ignore */
+                    }
                 }
             }
         }
@@ -593,10 +471,11 @@
          * Resets this instance so it can be reused.
          */
         private void reset() {
-            synchronized (this) {
+            synchronized (mLock) {
                 mExpectedEvents.clear();
                 mExpectedInterrupt = false;
                 mReplaying = false;
+                mLock.notifyAll();
             }
         }
 
@@ -620,7 +499,7 @@
 
         @Override
         public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
-            synchronized (this) {
+            synchronized (mLock) {
                 if (!mReplaying) {
                     return;
                 }
@@ -629,48 +508,25 @@
                 }
                 AccessibilityEvent expectedEvent = mExpectedEvents.poll();
                 assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+                mLock.notifyAll();
             }
         }
 
         @Override
         public void onInterrupt() {
-            synchronized (this) {
+            synchronized (mLock) {
                 if (!mReplaying) {
                     return;
                 }
-
                 if (!mExpectedInterrupt) {
                     throw new IllegalStateException("Unexpected call to onInterrupt()");
                 }
-
                 mExpectedInterrupt = false;
+                mLock.notifyAll();
             }
         }
 
         /**
-         * {@inheritDoc ServiceConnection#onServiceConnected(ComponentName,IBinder)}
-         */
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            IAccessibilityServiceDelegateConnection connection =
-                IAccessibilityServiceDelegateConnection.Stub
-                    .asInterface(service);
-            try {
-                connection.setAccessibilityServiceDelegate(new AccessibilityServiceDelegate(this));
-                mInitialized = true;
-            } catch (RemoteException re) {
-                fail("Could not set delegate to the delegating service.");
-            }
-        }
-
-        /**
-         * {@inheritDoc ServiceConnection#onServiceDisconnected(ComponentName)}
-         */
-        public void onServiceDisconnected(ComponentName name) {
-            mInitialized = false;
-            /* do nothing */
-        }
-
-        /**
          * Compares all properties of the <code>expectedEvent</code> and the
          * <code>receviedEvent</code> to verify that the received event is the
          * one that is expected.
@@ -764,84 +620,5 @@
                         receivedTextIterator.next().toString());
             }
         }
-
-        /**
-         * This class is the delegate called by the DelegatingAccessibilityService.
-         */
-        private class AccessibilityServiceDelegate extends
-                IAccessibilityServiceDelegate.Stub implements Handler.Callback {
-
-            /**
-             * Tag for logging.
-             */
-            private static final String LOG_TAG = "AccessibilityServiceDelegate";
-
-            /**
-             * Message type for calling {@link #onInterrupt()}
-             */
-            private static final int DO_ON_INTERRUPT = 10;
-
-            /**
-             * Message type for calling {@link #onAccessibilityEvent(AccessibilityEvent)}
-             */
-            private static final int DO_ON_ACCESSIBILITY_EVENT = 20;
-
-            /**
-             * Caller for handling {@link Message}s
-             */
-            private final Handler mHandler;
-
-            /**
-             * The {@link MockAccessibilityService} to which to delegate;
-             */
-            private MockAccessibilityService mMockAccessibilityService;
-
-            /**
-             * Creates a new instance.
-             *
-             * @param mockAccessibilityService The service to whcih to delegate.
-             */
-            public AccessibilityServiceDelegate(MockAccessibilityService mockAccessibilityService) {
-                mMockAccessibilityService = mockAccessibilityService;
-                mHandler = new Handler(this);
-            }
-
-            /**
-             * {@inheritDoc IAccessibilityServiceDelegate#onAccessibilityEvent(AccessibilityEvent)}
-             */
-            public void onAccessibilityEvent(AccessibilityEvent event) {
-                Message message = Message.obtain(mHandler, DO_ON_ACCESSIBILITY_EVENT, event);
-                mHandler.sendMessage(message);
-            }
-
-            /**
-             * {@inheritDoc IAccessibilityServiceDelegate#onInterrupt()}
-             */
-            public void onInterrupt() {
-                Message message = mHandler.obtainMessage(DO_ON_INTERRUPT);
-                mHandler.sendMessage(message);
-            }
-
-            /**
-             * {@inheritDoc Handler.Callback#handleMessage(Message)}
-             */
-            public boolean handleMessage(Message message) {
-                switch (message.what) {
-                    case DO_ON_ACCESSIBILITY_EVENT:
-                        AccessibilityEvent event = (AccessibilityEvent) message.obj;
-                        if (event != null) {
-                            mMockAccessibilityService.onAccessibilityEvent(event);
-                            event.recycle();
-                        }
-                        return true;
-                    case DO_ON_INTERRUPT:
-                        mMockAccessibilityService.onInterrupt();
-                        return true;
-                    default:
-                        Log.w(LOG_TAG, "Unknown message type " + message.what);
-                        return false;
-                }
-            }
-        }
     }
 }
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
new file mode 100644
index 0000000..6f72a75
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivity.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2011 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.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.cts.accessibilityservice.R;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ */
+public class AccessibilityWindowQueryActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.query_window_test);
+
+        findViewById(R.id.button5).setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                /* do nothing */
+            }
+        });
+        findViewById(R.id.button5).setOnLongClickListener(new View.OnLongClickListener() {
+            public boolean onLongClick(View v) {
+                return true;
+            }
+        });
+    }
+}
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
new file mode 100644
index 0000000..d798052
--- /dev/null
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryActivityTest.java
@@ -0,0 +1,497 @@
+/**
+ * Copyright (C) 2011 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 static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Context;
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.cts.accessibilityservice.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Activity for testing the accessibility APIs for querying of
+ * the screen content. These APIs allow exploring the screen and
+ * requesting an action to be performed on a given view from an
+ * AccessiiblityService.
+ * <p>
+ * Note: The accessibility CTS tests are composed of two APKs, one with delegating
+ * accessibility service and another with the instrumented activity and test cases.
+ * The motivation for two APKs design is that CTS tests cannot access the secure
+ * settings which is required for enabling accessibility services, hence there is
+ * no way to manipulate accessibility settings programmaticaly. Further, manually
+ * enabling an accessibility service in the tests APK will not work either because
+ * the instrumentation restarts the process under test which would break the binding
+ * between the accessibility service and the system.
+ * <p>
+ * Therefore, manual installation of the
+ * <strong>CtsAccessibilityServiceTestMockService.apk</strong>
+ * whose source is located at <strong>cts/tests/accessibility</strong> is required.
+ * Once the former package has been installed the service must be enabled
+ * (Settings -> Accessibility -> Delegating Accessibility Service), and then the CTS tests
+ * in this package can be successfully run.
+ * </p>
+ */
+public class AccessibilityWindowQueryActivityTest
+        extends ActivityInstrumentationTestCase2<AccessibilityWindowQueryActivity> {
+
+    private interface AccessibilityEventFilter {
+        public boolean accept(AccessibilityEvent event);
+    }
+
+    public AccessibilityWindowQueryActivityTest() {
+        super(AccessibilityWindowQueryActivity.class);
+    }
+
+    @Override
+    public void setUp() {
+        // start the activity and wait for a handle to its window root.
+        startActivityAndWaitForFirstEvent();
+    }
+
+    @LargeTest
+    public void testFindByText() throws Exception {
+        // find a view by text
+        List<AccessibilityNodeInfo> buttons = findAccessibilityNodeInfosByText(
+                getAwaitedAccessibilityEventSource(), "butto");
+        assertEquals(9, buttons.size());
+    }
+
+    @LargeTest
+    public void testFindByContentDescription() throws Exception {
+        // find a view by text
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.contentDescription);
+        assertNotNull(button);
+    }
+
+    @LargeTest
+    public void testTraverseWindow() throws Exception {
+        // make list of expected nodes
+        List<String> classNameAndTextList = new ArrayList<String>();
+        classNameAndTextList.add("com.android.internal.policy.impl.PhoneWindow$DecorView");
+        classNameAndTextList.add("android.widget.LinearLayout");
+        classNameAndTextList.add("android.widget.FrameLayout");
+        classNameAndTextList.add("android.widget.LinearLayout");
+        classNameAndTextList.add("android.widget.LinearLayout");
+        classNameAndTextList.add("android.widget.LinearLayout");
+        classNameAndTextList.add("android.widget.LinearLayout");
+        classNameAndTextList.add("android.widget.ButtonButton1");
+        classNameAndTextList.add("android.widget.ButtonButton2");
+        classNameAndTextList.add("android.widget.ButtonButton3");
+        classNameAndTextList.add("android.widget.ButtonButton4");
+        classNameAndTextList.add("android.widget.ButtonButton5");
+        classNameAndTextList.add("android.widget.ButtonButton6");
+        classNameAndTextList.add("android.widget.ButtonButton7");
+        classNameAndTextList.add("android.widget.ButtonButton8");
+        classNameAndTextList.add("android.widget.ButtonButton9");
+
+        Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+        fringe.add(getAwaitedAccessibilityEventSource());
+
+        // do a BFS traversal and check nodes
+        while (!fringe.isEmpty()) {
+            AccessibilityNodeInfo current = fringe.poll();
+
+            CharSequence text = current.getText();
+            String receivedClassNameAndText = current.getClassName().toString()
+                + ((text != null) ? text.toString() : "");
+            String expectedClassNameAndText = classNameAndTextList.remove(0);
+
+            assertEquals("Did not get the expected node info",
+                    expectedClassNameAndText, receivedClassNameAndText);
+
+            final int childCount = current.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                AccessibilityNodeInfo child = current.getChild(i);
+                fringe.add(child);
+            }
+        }
+    }
+
+    @LargeTest
+    public void testPerformActionFocus() throws Exception {
+        // find a view and make sure it is not focused
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        assertFalse(button.isFocused());
+
+        // focus the view
+        assertTrue(button.performAction(ACTION_FOCUS));
+
+        // find the view again and make sure it is focused
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+                R.string.button5);
+        assertTrue(button.isFocused());
+    }
+
+    @LargeTest
+    public void testPerformActionClearFocus() throws Exception {
+        // find a view and make sure it is not focused
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        assertFalse(button.isFocused());
+
+        // focus the view
+        assertTrue(button.performAction(ACTION_FOCUS));
+
+        // find the view again and make sure it is focused
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+                R.string.button5);
+        assertTrue(button.isFocused());
+
+        // unfocus the view
+        assertTrue(button.performAction(ACTION_CLEAR_FOCUS));
+
+        // find the view again and make sure it is not focused
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+                R.string.button5);
+        assertFalse(button.isFocused());
+    }
+
+    @LargeTest
+    public void testPerformActionSelect() throws Exception {
+        // find a view and make sure it is not selected
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        assertFalse(button.isSelected());
+
+        // select the view
+        assertTrue(button.performAction(ACTION_SELECT));
+
+        // find the view again and make sure it is selected
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+                R.string.button5);
+        assertTrue(button.isSelected());
+    }
+
+    @LargeTest
+    public void testPerformActionClearSelection() throws Exception {
+        // find a view and make sure it is not selected
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        assertFalse(button.isSelected());
+
+        // select the view
+        assertTrue(button.performAction(ACTION_SELECT));
+
+        // find the view again and make sure it is selected
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),R
+                .string.button5);
+
+        assertTrue(button.isSelected());
+
+        // unselect the view
+        assertTrue(button.performAction(ACTION_CLEAR_SELECTION));
+
+        // find the view again and make sure it is not selected
+        button = findAccessibilityNodeInfoByText(getAwaitedAccessibilityEventSource(),
+                R.string.button5);
+        assertFalse(button.isSelected());
+    }
+
+    @LargeTest
+    public void testGetEventSource() throws Exception {
+        // find a view and make sure it is not focused
+        final AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        assertFalse(button.isSelected());
+
+        // focus and wait for the event
+        AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+                getInstrumentation().getContext());
+        bridge.perfromActionAndWaitForEvent(
+                new Runnable() {
+            @Override
+            public void run() {
+                assertTrue(button.performAction(ACTION_FOCUS));
+            }
+        },
+                new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            }
+        });
+
+        // check that last event source
+        AccessibilityNodeInfo source = getAwaitedAccessibilityEventSource();
+        assertNotNull(source);
+
+        // bounds
+        Rect buttonBounds = new Rect();
+        button.getBoundsInParent(buttonBounds);
+        Rect sourceBounds = new Rect();
+        source.getBoundsInParent(sourceBounds);
+
+        assertEquals(buttonBounds.left, sourceBounds.left);
+        assertEquals(buttonBounds.right, sourceBounds.right);
+        assertEquals(buttonBounds.top, sourceBounds.top);
+        assertEquals(buttonBounds.bottom, sourceBounds.bottom);
+
+        // char sequence attributes
+        assertEquals(button.getPackageName(), source.getPackageName());
+        assertEquals(button.getClassName(), source.getClassName());
+        assertEquals(button.getText(), source.getText());
+        assertSame(button.getContentDescription(), source.getContentDescription());
+
+        // boolean attributes
+        assertSame(button.isFocusable(), source.isFocusable());
+        assertSame(button.isClickable(), source.isClickable());
+        assertSame(button.isEnabled(), source.isEnabled());
+        assertNotSame(button.isFocused(), source.isFocused());
+        assertSame(button.isLongClickable(), source.isLongClickable());
+        assertSame(button.isPassword(), source.isPassword());
+        assertSame(button.isSelected(), source.isSelected());
+        assertSame(button.isCheckable(), source.isCheckable());
+        assertSame(button.isChecked(), source.isChecked());
+    }
+
+    @LargeTest
+    public void testObjectContract() throws Exception {
+        // find a view and make sure it is not focused
+        AccessibilityNodeInfo button = findAccessibilityNodeInfoByText(
+                getAwaitedAccessibilityEventSource(), R.string.button5);
+        AccessibilityNodeInfo parent = button.getParent();
+        final int childCount = parent.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            AccessibilityNodeInfo child = parent.getChild(i);
+            assertNotNull(child);
+            if (child.equals(button)) {
+                assertEquals("Equal objects must have same hasCode.", button.hashCode(),
+                        child.hashCode());
+                return;
+            }
+        }
+        fail("Parent's children do not have the info whose parent is the parent.");
+    }
+
+    @Override
+    protected void scrubClass(Class<?> testCaseClass) {
+        /* intentionally do not scrub */
+    }
+
+    /**
+     * Starts the activity under tests and waits for the first accessibility
+     * event from that activity.
+     */
+    private void startActivityAndWaitForFirstEvent() {
+        AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+                getInstrumentation().getContext());
+        bridge.perfromActionAndWaitForEvent(
+                new Runnable() {
+            @Override
+            public void run() {
+                getActivity();
+            }
+        },
+                new AccessibilityEventFilter() {
+            @Override
+            public boolean accept(AccessibilityEvent event) {
+                final int eventType = event.getEventType();
+                CharSequence packageName = event.getPackageName();
+                Context targetContext = getInstrumentation().getTargetContext();
+                return (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+                        && targetContext.getPackageName().equals(packageName));
+            }
+        });
+    }
+
+    /**
+     * @return The source of the last accessibility event.
+     */
+    private AccessibilityNodeInfo getAwaitedAccessibilityEventSource() {
+        AccessibilityQueryBridge bridge = AccessibilityQueryBridge.getInstance(
+                getInstrumentation().getContext());
+        AccessibilityEvent event = bridge.getAwaitedAccessibilityEvent();
+        if (event != null) {
+            return event.getSource();
+        }
+        return null;
+    }
+
+    private List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(AccessibilityNodeInfo root,
+            String text) {
+        if (root != null) {
+            return root.findAccessibilityNodeInfosByText(text);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Finds the first accessibility info that contains text. The search starts
+     * from the given <code>root</code>
+     *
+     * @param root Node from which to start the search.
+     * @param resId Resource id of the searched text.
+     * @return The node with this text or null.
+     */
+    private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+            int resId) {
+        return findAccessibilityNodeInfoByText(root,
+                getInstrumentation().getContext().getString(resId));
+    }
+
+    /**
+     * Finds the first accessibility info that contains text. The search starts
+     * from the given <code>root</code>
+     *
+     * @param root Node from which to start the search.
+     * @param text The searched text.
+     * @return The node with this text or null.
+     */
+    private AccessibilityNodeInfo findAccessibilityNodeInfoByText(AccessibilityNodeInfo root,
+            String text) {
+        List<AccessibilityNodeInfo> nodes = findAccessibilityNodeInfosByText(root, text);
+        if (nodes != null && !nodes.isEmpty()) {
+            return nodes.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * This class serves as a bridge for querying the screen content.
+     * The bride is connected of a delegating accessibility service.
+     */
+    static class AccessibilityQueryBridge extends AccessibilityService {
+
+        /**
+         * The singleton instance.
+         */
+        private static AccessibilityQueryBridge sInstance;
+
+        /**
+         * Helper for connecting to the delegating accessibility service.
+         */
+        private final AccessibilityDelegateHelper mAccessibilityDelegateHelper;
+
+        /**
+         * The last received accessibility event.
+         */
+        private AccessibilityEvent mAwaitedAccessbiliyEvent;
+
+        /**
+         * Barrier for synchronizing waiting client and this bridge.
+         */
+        private CyclicBarrier mBarrier = new CyclicBarrier(2);
+
+        /**
+         * Filter for the currently waited event.
+         */
+        private AccessibilityEventFilter mWaitedFilter;
+
+        /**
+         * Gets the {@link AccessibilityQueryBridge} singleton.
+         *
+         * @param context A context handle.
+         * @return The mock service.
+         */
+        public static AccessibilityQueryBridge getInstance(Context context) {
+            if (sInstance == null) {
+                // since we do bind once and do not unbind from the delegating
+                // service and JUnit3 does not support @BeforeTest and @AfterTest,
+                // we will leak a service connection after the test but this
+                // does not affect the test results and the test is twice as fast
+                sInstance = new AccessibilityQueryBridge(context);
+            }
+            return sInstance;
+        }
+
+        private AccessibilityQueryBridge(Context context) {
+            mAccessibilityDelegateHelper = new AccessibilityDelegateHelper(this);
+            mAccessibilityDelegateHelper.bindToDelegatingAccessibilityService(context);
+        }
+
+        @Override
+        public void onAccessibilityEvent(AccessibilityEvent event) {
+            if (mWaitedFilter != null && mWaitedFilter.accept(event)) {
+                mAwaitedAccessbiliyEvent = AccessibilityEvent.obtain(event);
+                awaitOnBarrier();
+            }
+        }
+
+        @Override
+        public void onInterrupt() {
+            /* do nothing */
+        }
+
+        /**
+         * @return The event that was waited for.
+         */
+        public AccessibilityEvent getAwaitedAccessibilityEvent() {
+            return mAwaitedAccessbiliyEvent;
+        }
+
+        /**
+         * Performs an action and waits for the resulting event.
+         *
+         * @param action The action to perform.
+         * @param filter Filter for recognizing the waited event.
+         */
+        public void perfromActionAndWaitForEvent(Runnable action,
+                AccessibilityEventFilter filter) {
+            reset();
+            mWaitedFilter = filter;
+            action.run();
+            awaitOnBarrier();
+        }
+
+        /**
+         * Rests the internal state.
+         */
+        private void reset() {
+            if (mAwaitedAccessbiliyEvent != null) {
+                mAwaitedAccessbiliyEvent.recycle();
+                mAwaitedAccessbiliyEvent = null;
+            }
+            mBarrier.reset();
+            mWaitedFilter = null;
+        }
+
+        /**
+         * Calls await of the barrier taking care of the exceptions.
+         */
+        private void awaitOnBarrier() {
+            try {
+                mBarrier.await(AccessibilityDelegateHelper.TIMEOUT_ASYNC_PROCESSING,
+                        TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            } catch (BrokenBarrierException bbe) {
+                /* ignore */
+            } catch (TimeoutException te) {
+                /* ignore */
+            }
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index fb06d14..309bfc7 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -1986,30 +1986,26 @@
             for (int i = 0; i < settings.length; i++) {
                 if (Parameters.SCENE_MODE_AUTO.equals(settings[i].mScene)) continue;
 
-                // If the scene mode overrides the flash mode, it should also override
-                // the supported flash modes.
+                // Both the setting and the supported settings may change. It is
+                // allowed to have more than one supported settings in scene
+                // modes. For example, in night scene mode, supported flash
+                // modes can have on and off.
                 if (autoSceneMode.mSupportedFlash != null) {
-                    if (!autoSceneMode.mFlash.equals(settings[i].mFlash)) {
-                        assertEquals(1, settings[i].mSupportedFlash.size());
-                        assertTrue(settings[i].mSupportedFlash.contains(settings[i].mFlash));
+                    assertTrue(settings[i].mSupportedFlash.contains(settings[i].mFlash));
+                    for (String mode: settings[i].mSupportedFlash) {
+                        assertTrue(autoSceneMode.mSupportedFlash.contains(mode));
                     }
                 }
-
-                // If the scene mode overrides the focus mode, it should also override
-                // the supported focus modes.
                 if (autoSceneMode.mSupportedFocus != null) {
-                    if (!autoSceneMode.mFocus.equals(settings[i].mFocus)) {
-                        assertEquals(1, settings[i].mSupportedFocus.size());
-                        assertTrue(settings[i].mSupportedFocus.contains(settings[i].mFocus));
+                    assertTrue(settings[i].mSupportedFocus.contains(settings[i].mFocus));
+                    for (String mode: settings[i].mSupportedFocus) {
+                        assertTrue(autoSceneMode.mSupportedFocus.contains(mode));
                     }
                 }
-
-                // If the scene mode overrides the white balance, it should also override
-                // the supported white balance.
                 if (autoSceneMode.mSupportedWhiteBalance != null) {
-                    if (!autoSceneMode.mWhiteBalance.equals(settings[i].mWhiteBalance)) {
-                        assertEquals(1, settings[i].mSupportedWhiteBalance.size());
-                        assertTrue(settings[i].mSupportedWhiteBalance.contains(settings[i].mWhiteBalance));
+                    assertTrue(settings[i].mSupportedWhiteBalance.contains(settings[i].mWhiteBalance));
+                    for (String mode: settings[i].mSupportedWhiteBalance) {
+                        assertTrue(autoSceneMode.mSupportedWhiteBalance.contains(mode));
                     }
                 }
             }
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
index a4db981..652c5bd 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerStreamingTest.java
@@ -108,8 +108,10 @@
     // Streaming HLS video from YouTube
     public void testHLS() throws Exception {
         // Play stream for 60 seconds
-        playLiveVideoTest("http://www.youtube.com/api/manifest/hls/ns/test/"
-                + "id/1?user=android-device-test&m3u8=1", 60 * 1000);
+        playLiveVideoTest("http://www.youtube.com/api/manifest/hls/ns/yt-live/id/UeHRu5LFHaU"
+                + "?ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire&signature"
+                + "=313BE90526F2D815EB207156E1460C7E8EEC2503.799EE7B8B7CE3F2957060DB27C216077"
+                + "0303EBD2&key=test_key1&user=android-device-test&m3u8=1", 60 * 1000);
     }
 
     // Streaming audio from local HTTP server
diff --git a/tests/tests/os/src/android/os/cts/FileObserverTest.java b/tests/tests/os/src/android/os/cts/FileObserverTest.java
index d44e22b..b51f804 100644
--- a/tests/tests/os/src/android/os/cts/FileObserverTest.java
+++ b/tests/tests/os/src/android/os/cts/FileObserverTest.java
@@ -16,20 +16,15 @@
 
 package android.os.cts;
 
+import android.os.FileObserver;
+import android.test.AndroidTestCase;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-import android.os.FileObserver;
-import android.test.AndroidTestCase;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
-import dalvik.annotation.ToBeFixed;
-
-@TestTargetClass(FileObserver.class)
 public class FileObserverTest extends AndroidTestCase {
 
     private File mTestFile;
@@ -73,18 +68,6 @@
         }
     }
 
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "FileObserver",
-            args = {java.lang.String.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "FileObserver",
-            args = {java.lang.String.class, int.class}
-        )
-    })
     public void testConstructor() {
         // new the instance
         new MockFileObserver(PATH);
@@ -104,31 +87,6 @@
      * moved from dir observer should get moved-from event,
      * moved to dir observer should get moved-to event.
      */
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "startWatching",
-            args = {}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "onEvent",
-            args = {int.class, java.lang.String.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.PARTIAL,
-            method = "stopWatching",
-            args = {}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "finalize",
-            args = {}
-        )
-    })
-    @ToBeFixed(bug = "1725406", explanation =
-        "android.os.FileObserver#onEvent(int event, String path) still got event "
-        + "after called FileObserver#stopWatching()")
     public void testFileObserver() throws Exception {
         MockFileObserver fileObserver = null;
         int[] expected = null;
@@ -172,7 +130,7 @@
             mTestDir.delete();
 
             expected = new int[] {FileObserver.CREATE,
-                    FileObserver.OPEN, FileObserver.CLOSE_NOWRITE,
+                    FileObserver.OPEN, FileObserver.CLOSE_WRITE,
                     FileObserver.DELETE, FileObserver.DELETE_SELF, UNDEFINED};
             moveEvents = waitForEvent(fileObserver);
             assertEventsEquals(expected, moveEvents);
@@ -217,9 +175,15 @@
     }
 
     private void assertEventsEquals(final int[] expected, final FileEvent[] moveEvents) {
-        assertEquals(expected.length, moveEvents.length);
+        List<Integer> expectedEvents = new ArrayList<Integer>();
         for (int i = 0; i < expected.length; i++) {
-            assertEquals(expected[i], moveEvents[i].event);
+            expectedEvents.add(expected[i]);
+        }
+        List<FileEvent> actualEvents = Arrays.asList(moveEvents);
+        String message = "Expected: " + expectedEvents + " Actual: " + actualEvents;
+        assertEquals(message, expected.length, moveEvents.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(message, expected[i], moveEvents[i].event);
         }
     }
 
@@ -239,6 +203,11 @@
             this.event = event;
             this.path = path;
         }
+
+        @Override
+        public String toString() {
+            return Integer.toString(event);
+        }
     }
 
     /*
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 4dddd5d..3822035 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -80,7 +80,10 @@
             "android.deviceadmin.cts",
 
             // APK for an activity that collects information printed in the CTS report header
-            "android.tests.devicesetup"
+            "android.tests.devicesetup",
+
+            // APK for the Android core tests runner used only during CTS
+            "android.core.tests.runner"
             ));
 
     private boolean isWhitelistedPackage(String packageName) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index f0e7d07..b06109a 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -2267,13 +2267,14 @@
             public void run() {
                 mWebView.setWebViewClient(webViewClient);
                 mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+                mWebView.clearSslPreferences();
                 mWebView.loadUrl(errorUrl);
             }
         });
         waitForUiThreadDone();
 
-        assertEquals(webViewClient.webView(), mWebView);
-        assertEquals(webViewClient.errorUrl(), errorUrl);
+        assertEquals(mWebView, webViewClient.webView());
+        assertEquals(errorUrl, webViewClient.errorUrl());
     }
 
     @TestTargetNew(
@@ -2301,7 +2302,7 @@
         waitForUiThreadDone();
         runTestOnUiThread(new Runnable() {
             public void run() {
-                assertEquals(mWebView.getTitle(), TestHtmlConstants.HELLO_WORLD_TITLE);
+                assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
             }
         });
     }
@@ -2325,6 +2326,7 @@
             public void run() {
                 mWebView.setWebViewClient(new MockWebViewClient());
                 mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+                mWebView.clearSslPreferences();
                 mWebView.loadUrl(url);
             }
         });
@@ -2351,6 +2353,7 @@
             public void run() {
                 mWebView.setWebViewClient(webViewClient);
                 mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+                mWebView.clearSslPreferences();
                 mWebView.loadUrl(firstUrl);
             }
         });
@@ -2370,7 +2373,7 @@
         assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
         runTestOnUiThread(new Runnable() {
             public void run() {
-                assertEquals(mWebView.getTitle(), "Second page");
+                assertEquals("Second page", mWebView.getTitle());
             }
         });
     }
@@ -2390,6 +2393,7 @@
             public void run() {
                 mWebView.setWebViewClient(webViewClient);
                 mWebView.setWebChromeClient(new LoadCompleteWebChromeClient());
+                mWebView.clearSslPreferences();
                 mWebView.loadUrl(firstUrl);
             }
         });
@@ -2412,7 +2416,7 @@
         assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
         runTestOnUiThread(new Runnable() {
             public void run() {
-                assertEquals(mWebView.getTitle(), "Second page");
+                assertEquals("Second page", mWebView.getTitle());
             }
         });
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 3ae1f06..62827d5 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.tradefed.result;
 
+import android.tests.getinfo.DeviceInfoConstants;
+
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.cts.tradefed.build.CtsBuildProvider;
 import com.android.cts.tradefed.device.DeviceInfoCollector;
@@ -27,18 +29,15 @@
 import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.TestSummary;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.StreamUtil;
 
 import org.kxml2.io.KXmlSerializer;
 
-import android.tests.getinfo.DeviceInfoConstants;
-
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -57,7 +56,7 @@
  * <p/>
  * Outputs xml in format governed by the cts_result.xsd
  */
-public class CtsXmlResultReporter extends CollectingTestListener {
+public class CtsXmlResultReporter implements ITestInvocationListener {
 
     private static final String LOG_TAG = "CtsXmlResultReporter";
 
@@ -88,14 +87,18 @@
     @Option(name = CtsTest.PLAN_OPTION, description = "the test plan to run.")
     private String mPlanName = "NA";
 
+    // listen in on the continue-session option provided to CtsTest
+    @Option(name = CtsTest.CONTINUE_OPTION, description = "the test result session to continue.")
+    private Integer mContinueSessionId = null;
+
     @Option(name = "quiet-output", description = "Mute display of test results.")
     private boolean mQuietOutput = false;
 
     protected IBuildInfo mBuildInfo;
-
     private String mStartTime;
-
     private String mDeviceSerial;
+    private TestResults mResults = new TestResults();
+    private TestPackageResult mCurrentPkgResult = null;
 
     public void setReportDir(File reportDir) {
         mReportDir = reportDir;
@@ -106,29 +109,51 @@
      */
     @Override
     public void invocationStarted(IBuildInfo buildInfo) {
-        super.invocationStarted(buildInfo);
-        if (mReportDir == null) {
-            if (!(buildInfo instanceof IFolderBuildInfo)) {
-                throw new IllegalArgumentException("build info is not a IFolderBuildInfo");
-            }
-
-            IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
-            try {
-                CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
-                buildHelper.validateStructure();
-                mReportDir = buildHelper.getResultsDir();
-            } catch (FileNotFoundException e) {
-                throw new IllegalArgumentException("Invalid CTS build", e);
-            }
+        mBuildInfo = buildInfo;
+        if (!(buildInfo instanceof IFolderBuildInfo)) {
+            throw new IllegalArgumentException("build info is not a IFolderBuildInfo");
         }
-        // create a unique directory for saving results, using old cts host convention
-        // TODO: in future, consider using LogFileSaver to create build-specific directories
-        mReportDir = new File(mReportDir, TimeUtil.getResultTimestamp());
-        mReportDir.mkdirs();
-        mStartTime = getTimestamp();
+        IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
         mDeviceSerial = buildInfo.getDeviceSerial() == null ? "unknown_device" :
-                buildInfo.getDeviceSerial();
-        logResult("Created result dir %s", mReportDir.getName());
+            buildInfo.getDeviceSerial();
+        if (mContinueSessionId != null) {
+            CLog.d("Continuing session %d", mContinueSessionId);
+            // reuse existing directory
+            TestResultRepo resultRepo = new TestResultRepo(getBuildHelper(ctsBuild).getResultsDir());
+            mResults = resultRepo.getResult(mContinueSessionId);
+            if (mResults == null) {
+                throw new IllegalArgumentException(String.format("Could not find session %d",
+                        mContinueSessionId));
+            }
+            mPlanName = resultRepo.getSummaries().get(mContinueSessionId).getTestPlan();
+            mStartTime = resultRepo.getSummaries().get(mContinueSessionId).getTimestamp();
+            mReportDir = resultRepo.getReportDir(mContinueSessionId);
+        } else {
+            if (mReportDir == null) {
+                mReportDir = getBuildHelper(ctsBuild).getResultsDir();
+            }
+            // create a unique directory for saving results, using old cts host convention
+            // TODO: in future, consider using LogFileSaver to create build-specific directories
+            mReportDir = new File(mReportDir, TimeUtil.getResultTimestamp());
+            mReportDir.mkdirs();
+            mStartTime = getTimestamp();
+            logResult("Created result dir %s", mReportDir.getName());
+        }
+    }
+
+    /**
+     * Helper method to retrieve the {@link CtsBuildHelper}.
+     * @param ctsBuild
+     * @return
+     */
+    CtsBuildHelper getBuildHelper(IFolderBuildInfo ctsBuild) {
+        CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
+        try {
+            buildHelper.validateStructure();
+        } catch (FileNotFoundException e) {
+            throw new IllegalArgumentException("Invalid CTS build", e);
+        }
+        return buildHelper;
     }
 
     /**
@@ -149,30 +174,51 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void testRunStarted(String name, int numTests) {
+        if (mCurrentPkgResult != null && !name.equals(mCurrentPkgResult.getAppPackageName())) {
+            // display results from previous run
+            logCompleteRun(mCurrentPkgResult);
+        }
         if (name.equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
             logResult("Collecting device info");
-        } else if (!name.equals(getCurrentRunResults().getName())) {
-            // this is a new run
-            if (getCurrentRunResults().isRunComplete()) {
-                // display results from previous run
-                logCompleteRun(getCurrentRunResults());
-            }
+        } else  if (mCurrentPkgResult == null || !name.equals(
+                mCurrentPkgResult.getAppPackageName())) {
             logResult("-----------------------------------------");
             logResult("Test package %s started", name);
             logResult("-----------------------------------------");
         }
-        super.testRunStarted(name, numTests);
+        mCurrentPkgResult = mResults.getOrCreatePackage(name);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        mCurrentPkgResult.insertTest(test);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+        mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
-        super.testEnded(test, testMetrics);
-        TestRunResult results = getCurrentRunResults();
-        TestResult result = results.getTestResults().get(test);
+        mCurrentPkgResult.reportTestEnded(test);
+        Test result = mCurrentPkgResult.findTest(test);
         String stack = result.getStackTrace() == null ? "" : "\n" + result.getStackTrace();
-        logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getStatus(),
+        logResult("%s#%s %s %s", test.getClassName(), test.getTestName(), result.getResult(),
                 stack);
     }
 
@@ -180,12 +226,19 @@
      * {@inheritDoc}
      */
     @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+        mCurrentPkgResult.populateMetrics(runMetrics);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public void invocationEnded(long elapsedTime) {
         // display the results of the last completed run
-        if (getCurrentRunResults().isRunComplete()) {
-            logCompleteRun(getCurrentRunResults());
+        if (mCurrentPkgResult != null) {
+            logCompleteRun(mCurrentPkgResult);
         }
-        super.invocationEnded(elapsedTime);
         createXmlResult(mReportDir, mStartTime, elapsedTime);
         copyFormattingFiles(mReportDir);
         zipResults(mReportDir);
@@ -199,16 +252,15 @@
         }
     }
 
-    private void logCompleteRun(TestRunResult runResults) {
-        if (runResults.getName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+    private void logCompleteRun(TestPackageResult pkgResult) {
+        if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
             logResult("Device info collection complete");
             return;
         }
         logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
-                runResults.getName(), runResults.getNumPassedTests(),
-                runResults.getNumFailedTests() +
-                runResults.getNumErrorTests(),
-                runResults.getNumIncompleteTests());
+                pkgResult.getAppPackageName(), pkgResult.countTests(CtsTestStatus.PASS),
+                pkgResult.countTests(CtsTestStatus.FAIL),
+                pkgResult.countTests(CtsTestStatus.NOT_EXECUTED));
     }
 
     /**
@@ -230,8 +282,10 @@
             serializeResultsDoc(serializer, startTimestamp, endTime);
             serializer.endDocument();
             String msg = String.format("XML test result file generated at %s. Passed %d, " +
-                    "Failed %d, Not Executed %d", mReportDir.getName(), getNumPassedTests(),
-                    getNumFailedTests() + getNumErrorTests(), getNumIncompleteTests());
+                    "Failed %d, Not Executed %d", mReportDir.getName(),
+                    mResults.countTests(CtsTestStatus.PASS),
+                    mResults.countTests(CtsTestStatus.FAIL),
+                    mResults.countTests(CtsTestStatus.NOT_EXECUTED));
             logResult(msg);
             logResult("Time: %s", TimeUtil.formatElapsedTime(elapsedTime));
         } catch (IOException e) {
@@ -260,7 +314,7 @@
         serializeDeviceInfo(serializer);
         serializeHostInfo(serializer);
         serializeTestSummary(serializer);
-        serializeTestResults(serializer);
+        mResults.serialize(serializer);
         // TODO: not sure why, but the serializer doesn't like this statement
         //serializer.endTag(ns, RESULT_TAG);
     }
@@ -273,15 +327,16 @@
     private void serializeDeviceInfo(KXmlSerializer serializer) throws IOException {
         serializer.startTag(ns, "DeviceInfo");
 
-        TestRunResult deviceInfoResult = findRunResult(DeviceInfoCollector.APP_PACKAGE_NAME);
-        if (deviceInfoResult == null) {
-            Log.w(LOG_TAG, String.format("Could not find device info run %s",
-                    DeviceInfoCollector.APP_PACKAGE_NAME));
+        Map<String, String> deviceInfoMetrics = mResults.getDeviceInfoMetrics();
+        if (deviceInfoMetrics == null || deviceInfoMetrics.isEmpty()) {
+            // this might be expected, if device info collection was turned off
+            CLog.d("Could not find device info");
             return;
         }
+
         // Extract metrics that need extra handling, and then dump the remainder into BuildInfo
         Map<String, String> metricsCopy = new HashMap<String, String>(
-                deviceInfoResult.getRunMetrics());
+                deviceInfoMetrics);
         serializer.startTag(ns, "Screen");
         String screenWidth = metricsCopy.remove(DeviceInfoConstants.SCREEN_WIDTH);
         String screenHeight = metricsCopy.remove(DeviceInfoConstants.SCREEN_HEIGHT);
@@ -390,21 +445,6 @@
     }
 
     /**
-     * Finds the {@link TestRunResult} with the given name.
-     *
-     * @param runName
-     * @return the {@link TestRunResult}
-     */
-    private TestRunResult findRunResult(String runName) {
-        for (TestRunResult runResult : getRunResults()) {
-            if (runResult.getName().equals(runName)) {
-                return runResult;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Output the host info XML.
      *
      * @param serializer
@@ -451,69 +491,18 @@
      */
     private void serializeTestSummary(KXmlSerializer serializer) throws IOException {
         serializer.startTag(ns, SUMMARY_TAG);
-        serializer.attribute(ns, FAILED_ATTR, Integer.toString(getNumErrorTests() +
-                getNumFailedTests()));
-        serializer.attribute(ns, NOT_EXECUTED_ATTR,  Integer.toString(getNumIncompleteTests()));
+        serializer.attribute(ns, FAILED_ATTR, Integer.toString(mResults.countTests(
+                CtsTestStatus.FAIL)));
+        serializer.attribute(ns, NOT_EXECUTED_ATTR,  Integer.toString(mResults.countTests(
+                CtsTestStatus.NOT_EXECUTED)));
         // ignore timeouts - these are reported as errors
         serializer.attribute(ns, TIMEOUT_ATTR, "0");
-        serializer.attribute(ns, PASS_ATTR, Integer.toString(getNumPassedTests()));
+        serializer.attribute(ns, PASS_ATTR, Integer.toString(mResults.countTests(
+                CtsTestStatus.PASS)));
         serializer.endTag(ns, SUMMARY_TAG);
     }
 
     /**
-     * Output the detailed test results XML.
-     *
-     * @param serializer
-     * @throws IOException
-     */
-    private void serializeTestResults(KXmlSerializer serializer) throws IOException {
-        for (TestRunResult runResult : getRunResults()) {
-            serializeTestRunResult(serializer, runResult);
-        }
-    }
-
-    /**
-     * Output the XML for one test run aka test package.
-     *
-     * @param serializer
-     * @param runResult the {@link TestRunResult}
-     * @throws IOException
-     */
-    private void serializeTestRunResult(KXmlSerializer serializer, TestRunResult runResult)
-            throws IOException {
-        if (runResult.getName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
-            // ignore run results for the info collecting packages
-            return;
-        }
-        TestPackageResult packageResult = new TestPackageResult();
-        packageResult.setName(getMetric(runResult, CtsTest.PACKAGE_NAME_METRIC));
-        packageResult.setAppPackageName(runResult.getName());
-        packageResult.setDigest(getMetric(runResult, CtsTest.PACKAGE_DIGEST_METRIC));
-        // organize the tests into data structures that mirror the expected xml output.
-        for (Map.Entry<TestIdentifier, TestResult> testEntry : runResult.getTestResults()
-                .entrySet()) {
-            packageResult.insertTest(testEntry.getKey(), testEntry.getValue());
-        }
-        // dump the results
-        packageResult.serialize(serializer);
-    }
-
-    /**
-     * Helper method to retrieve the metric value with given name, or blank if not found
-     *
-     * @param runResult
-     * @param string
-     * @return
-     */
-    private String getMetric(TestRunResult runResult, String keyName) {
-        String value = runResult.getRunMetrics().get(keyName);
-        if (value == null) {
-            return "";
-        }
-        return value;
-    }
-
-    /**
      * Creates the output stream to use for test results. Exposed for mocking.
      * @param mReportPath
      */
@@ -570,4 +559,36 @@
     String getTimestamp() {
         return TimeUtil.getTimestamp();
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunFailed(String errorMessage) {
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStopped(long elapsedTime) {
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationFailed(Throwable cause) {
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSummary getSummary() {
+        return null;
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
index bed2719..d672f41 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/ITestResultRepo.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.tradefed.result;
 
+import java.io.File;
 import java.util.List;
 
 /**
@@ -37,4 +38,11 @@
      */
     public TestResults getResult(int sessionId);
 
+    /**
+     * Get the report directory for given result
+     * @param sessionId
+     * @return
+     */
+    public File getReportDir(int sessionId);
+
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index 184b8ab..917ccbe 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,9 +15,7 @@
  */
 package com.android.cts.tradefed.result;
 
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestResult.TestStatus;
 
 import org.kxml2.io.KXmlSerializer;
 import org.xmlpull.v1.XmlPullParser;
@@ -40,7 +38,7 @@
     private static final String STACK_TAG = "StackTrace";
 
     private String mName;
-    private String mResult;
+    private CtsTestStatus mResult;
     private String mStartTime;
     private String mEndTime;
     private String mMessage;
@@ -56,18 +54,12 @@
      * Create a {@link Test} from a {@link TestResult}.
      *
      * @param name
-     * @param result
      */
-    public Test(String name, TestResult result) {
+    public Test(String name) {
         mName = name;
-        mResult = convertStatus(result.getStatus());
-        mStartTime = TimeUtil.getTimestamp(result.getStartTime());
-        mEndTime = TimeUtil.getTimestamp(result.getEndTime());
-        if (result.getStackTrace() != null) {
-            String sanitizedStack = sanitizeStackTrace(result.getStackTrace());
-            mMessage = getFailureMessageFromStackTrace(sanitizedStack);
-            mStackTrace = sanitizedStack;
-        }
+        mResult = CtsTestStatus.NOT_EXECUTED;
+        mStartTime = TimeUtil.getTimestamp();
+        updateEndTime();
     }
 
     /**
@@ -84,7 +76,7 @@
         return mName;
     }
 
-    public String getResult() {
+    public CtsTestStatus getResult() {
         return mResult;
     }
 
@@ -104,6 +96,20 @@
         return mStackTrace;
     }
 
+    public void setStackTrace(String stackTrace) {
+
+        mStackTrace = sanitizeStackTrace(stackTrace);
+        mMessage = getFailureMessageFromStackTrace(mStackTrace);
+    }
+
+    public void updateEndTime() {
+        mEndTime = TimeUtil.getTimestamp();
+    }
+
+    public void setResultStatus(CtsTestStatus status) {
+        mResult = status;
+    }
+
     /**
      * Serialize this object and all its contents to XML.
      *
@@ -114,7 +120,7 @@
             throws IOException {
         serializer.startTag(CtsXmlResultReporter.ns, TAG);
         serializer.attribute(CtsXmlResultReporter.ns, NAME_ATTR, getName());
-        serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult);
+        serializer.attribute(CtsXmlResultReporter.ns, RESULT_ATTR, mResult.getValue());
         serializer.attribute(CtsXmlResultReporter.ns, STARTTIME_ATTR, mStartTime);
         serializer.attribute(CtsXmlResultReporter.ns, ENDTIME_ATTR, mEndTime);
 
@@ -132,27 +138,6 @@
     }
 
     /**
-     * Convert a {@link TestStatus} to the result text to output in XML
-     *
-     * @param status the {@link TestStatus}
-     * @return
-     */
-    private String convertStatus(TestStatus status) {
-        switch (status) {
-            case ERROR:
-                return CtsTestStatus.FAIL.getValue();
-            case FAILURE:
-                return CtsTestStatus.FAIL.getValue();
-            case PASSED:
-                return CtsTestStatus.PASS.getValue();
-            case INCOMPLETE:
-                return CtsTestStatus.NOT_EXECUTED.getValue();
-        }
-        CLog.w("Unrecognized status %s", status);
-        return CtsTestStatus.FAIL.getValue();
-    }
-
-    /**
      * Strip out any invalid XML characters that might cause the report to be unviewable.
      * http://www.w3.org/TR/REC-xml/#dt-character
      */
@@ -187,7 +172,7 @@
                     "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
         }
         setName(getAttribute(parser, NAME_ATTR));
-        mResult = getAttribute(parser, RESULT_ATTR);
+        mResult = CtsTestStatus.getStatus(getAttribute(parser, RESULT_ATTR));
         mStartTime = getAttribute(parser, STARTTIME_ATTR);
         mEndTime = getAttribute(parser, ENDTIME_ATTR);
 
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
index 138fe7c..fdb8b3b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
@@ -16,7 +16,6 @@
 package com.android.cts.tradefed.result;
 
 import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.result.TestResult;
 import com.android.tradefed.util.ArrayUtil;
 
 import org.kxml2.io.KXmlSerializer;
@@ -67,24 +66,17 @@
     }
 
     /**
-     * Inserts given test result
-     *
      * @param testName
-     * @param testResult
+     * @param insertIfMissing
+     * @return
      */
-    public void insertTest(String testName, TestResult testResult) {
-        Test t = new Test(testName, testResult);
-        insertTest(t);
-    }
-
-    /**
-     * Inserts given test result
-     *
-     * @param testName
-     * @param testResult
-     */
-    private void insertTest(Test test) {
-        mChildTestMap.put(test.getName(), test);
+    public Test findTest(String testName, boolean insertIfMissing) {
+        Test t = mChildTestMap.get(testName);
+        if (t == null && insertIfMissing) {
+            t = new Test(testName);
+            mChildTestMap.put(t.getName(), t);
+        }
+        return t;
     }
 
     /**
@@ -122,7 +114,7 @@
             if (eventType == XmlPullParser.START_TAG && parser.getName().equals(Test.TAG)) {
                 Test test = new Test();
                 test.parse(parser);
-                insertTest(test);
+                mChildTestMap.put(test.getName(), test);
             } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
                 return;
             }
@@ -145,7 +137,7 @@
         }
         String fullClassName = ArrayUtil.join(".", parentSuiteNames);
         for (Test test : mChildTestMap.values()) {
-            if (resultFilter.getValue().equals(test.getResult())) {
+            if (resultFilter.equals(test.getResult())) {
                 tests.add(new TestIdentifier(fullClassName, test.getName()));
             }
         }
@@ -153,4 +145,20 @@
             parentSuiteNames.removeLast();
         }
     }
+
+    /**
+     * Count the number of tests in this {@link TestCase} with given status.
+     *
+     * @param status
+     * @return the test count
+     */
+    public int countTests(CtsTestStatus status) {
+        int total = 0;
+        for (Test test : mChildTestMap.values()) {
+            if (test.getResult().equals(status)) {
+                total++;
+            }
+        }
+        return total;
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index 8480803..7d5db75 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -15,9 +15,9 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.cts.tradefed.testtype.CtsTest;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.TestResult;
 
 import org.kxml2.io.KXmlSerializer;
 import org.xmlpull.v1.XmlPullParser;
@@ -27,8 +27,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Data structure for a CTS test package result.
@@ -48,6 +50,8 @@
     private String mName;
     private String mDigest;
 
+    private Map<String, String> mMetrics = new HashMap<String, String>();
+
     private TestSuite mSuiteRoot = new TestSuite(null);
 
     public void setAppPackageName(String appPackageName) {
@@ -87,17 +91,31 @@
      * @param testId
      * @param testResult
      */
-    public void insertTest(TestIdentifier testId, TestResult testResult) {
+    public Test insertTest(TestIdentifier testId) {
+        return findTest(testId, true);
+    }
+
+    private Test findTest(TestIdentifier testId, boolean insertIfMissing) {
         List<String> classNameSegments = new LinkedList<String>();
         Collections.addAll(classNameSegments, testId.getClassName().split("\\."));
         if (classNameSegments.size() <= 0) {
             CLog.e("Unrecognized package name format for test class '%s'",
                     testId.getClassName());
-        } else {
-            String testCaseName = classNameSegments.remove(classNameSegments.size()-1);
-            mSuiteRoot.insertTest(classNameSegments, testCaseName, testId.getTestName(),
-                    testResult);
+            // should never happen
+            classNameSegments.add("UnknownTestClass");
         }
+            String testCaseName = classNameSegments.remove(classNameSegments.size()-1);
+            return mSuiteRoot.findTest(classNameSegments, testCaseName, testId.getTestName(), insertIfMissing);
+    }
+
+
+    /**
+     * Find the test result for given {@link TestIdentifier}.
+     * @param testId
+     * @return the {@link Test} or <code>null</code>
+     */
+    public Test findTest(TestIdentifier testId) {
+        return findTest(testId, false);
     }
 
     /**
@@ -108,10 +126,10 @@
      */
     public void serialize(KXmlSerializer serializer) throws IOException {
         serializer.startTag(ns, TAG);
-        serializer.attribute(ns, NAME_ATTR, mName);
-        serializer.attribute(ns, APP_PACKAGE_NAME_ATTR, mAppPackageName);
-        serializer.attribute(ns, DIGEST_ATTR, getDigest());
-        if (mName.equals(SIGNATURE_TEST_PKG)) {
+        serializeAttribute(serializer, NAME_ATTR, mName);
+        serializeAttribute(serializer, APP_PACKAGE_NAME_ATTR, mAppPackageName);
+        serializeAttribute(serializer, DIGEST_ATTR, getDigest());
+        if (SIGNATURE_TEST_PKG.equals(mName)) {
             serializer.attribute(ns, "signatureCheck", "true");
         }
         mSuiteRoot.serialize(serializer);
@@ -119,6 +137,21 @@
     }
 
     /**
+     * Helper method to serialize attributes.
+     * Can handle null values. Useful for cases where test package has not been fully populated
+     * such as when unit testing.
+     *
+     * @param attrName
+     * @param attrValue
+     * @throws IOException
+     */
+    private void serializeAttribute(KXmlSerializer serializer, String attrName, String attrValue)
+            throws IOException {
+        attrValue = attrValue == null ? "" : attrValue;
+        serializer.attribute(ns, attrName, attrValue);
+    }
+
+    /**
      * Populates this class with package result data parsed from XML.
      *
      * @param parser the {@link XmlPullParser}. Expected to be pointing at start
@@ -159,4 +192,63 @@
         mSuiteRoot.addTestsWithStatus(tests, suiteNames, resultFilter);
         return tests;
     }
+
+    /**
+     * Populate values in this package result from run metrics
+     * @param runResult
+     */
+    public void populateMetrics(Map<String, String> metrics) {
+        String name = metrics.get(CtsTest.PACKAGE_NAME_METRIC);
+        if (name != null) {
+            setName(name);
+        }
+        String digest = metrics.get(CtsTest.PACKAGE_DIGEST_METRIC);
+        if (digest != null) {
+            setDigest(digest);
+        }
+        mMetrics.putAll(metrics);
+    }
+
+    /**
+     * Report the given test as a failure.
+     *
+     * @param test
+     * @param status
+     * @param trace
+     */
+    public void reportTestFailure(TestIdentifier test, CtsTestStatus status, String trace) {
+        Test result = findTest(test);
+        result.setResultStatus(status);
+        result.setStackTrace(trace);
+    }
+
+    /**
+     * Report that the given test has completed.
+     *
+     * @param test
+     */
+    public void reportTestEnded(TestIdentifier test) {
+        Test result = findTest(test);
+        if (!result.getResult().equals(CtsTestStatus.FAIL)) {
+            result.setResultStatus(CtsTestStatus.PASS);
+        }
+        result.updateEndTime();
+    }
+
+    /**
+     * Return the number of tests with given status
+     *
+     * @param status
+     * @return the total number of tests with given status
+     */
+    public int countTests(CtsTestStatus status) {
+        return mSuiteRoot.countTests(status);
+    }
+
+    /**
+     * @return
+     */
+    public Map<String, String> getMetrics() {
+        return mMetrics;
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
index 9178e91..fd42892 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResultRepo.java
@@ -54,12 +54,17 @@
                 File resultFile = new File(resultList.get(i),
                         CtsXmlResultReporter.TEST_RESULT_FILE_NAME);
                 if (resultFile.exists()) {
-                    mResultDirs.add(i, resultList.get(i));
+                    mResultDirs.add(resultList.get(i));
                 }
             }
         }
     }
 
+    @Override
+    public File getReportDir(int sessionId) {
+        return mResultDirs.get(sessionId);
+    }
+
     private ITestSummary parseSummary(int id, File resultDir) {
         TestSummaryXml result = new TestSummaryXml(id, resultDir.getName());
         try {
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
index 6900e58..a874227 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
@@ -15,12 +15,21 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.cts.tradefed.device.DeviceInfoCollector;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.kxml2.io.KXmlSerializer;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Data structure for the detailed CTS test results.
@@ -29,7 +38,8 @@
  */
 class TestResults extends AbstractXmlPullParser {
 
-    private List<TestPackageResult> mPackages = new ArrayList<TestPackageResult>();
+    private Map<String, TestPackageResult> mPackageMap = new LinkedHashMap<String, TestPackageResult>();
+    private TestPackageResult mDeviceInfoPkg = new TestPackageResult();
 
     /**
      * {@inheritDoc}
@@ -42,16 +52,91 @@
                     TestPackageResult.TAG)) {
                 TestPackageResult pkg = new TestPackageResult();
                 pkg.parse(parser);
-                mPackages.add(pkg);
+                if (pkg.getAppPackageName() != null) {
+                    mPackageMap.put(pkg.getAppPackageName(), pkg);
+                } else {
+                    CLog.w("Found package with no app package name");
+                }
             }
             eventType = parser.next();
         }
     }
 
     /**
-     * @return the list of parsed {@link TestPackageResult}.
+     * @return the list of {@link TestPackageResult}.
      */
-    public List<TestPackageResult> getPackages() {
-        return mPackages;
+    public Collection<TestPackageResult> getPackages() {
+        return mPackageMap.values();
+    }
+
+    /**
+     * Count the number of tests with given status
+     * @param pass
+     * @return
+     */
+    public int countTests(CtsTestStatus status) {
+        int total = 0;
+        for (TestPackageResult result : mPackageMap.values()) {
+            total += result.countTests(status);
+        }
+        return total;
+    }
+
+    /**
+     * @return
+     */
+    public Map<String, String> getDeviceInfoMetrics() {
+        return mDeviceInfoPkg.getMetrics();
+    }
+
+    /**
+     * @param mCurrentPkgResult
+     */
+    public void addPackageResult(TestPackageResult pkgResult) {
+        mPackageMap.put(pkgResult.getName(), pkgResult);
+    }
+
+    /**
+     * @param serializer
+     * @throws IOException
+     */
+    public void serialize(KXmlSerializer serializer) throws IOException {
+        // sort before serializing
+        List<TestPackageResult> pkgs = new ArrayList<TestPackageResult>(mPackageMap.values());
+        Collections.sort(pkgs, new PkgComparator());
+        for (TestPackageResult r : pkgs) {
+            r.serialize(serializer);
+        }
+    }
+
+    private static class PkgComparator implements Comparator<TestPackageResult> {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int compare(TestPackageResult o1, TestPackageResult o2) {
+            return o1.getAppPackageName().compareTo(o2.getAppPackageName());
+        }
+
+    }
+
+    /**
+     * Return existing package with given app package name. If not found, create a new one.
+     * @param name
+     * @return
+     */
+    public TestPackageResult getOrCreatePackage(String appPackageName) {
+        if (appPackageName.equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+            mDeviceInfoPkg.setAppPackageName(DeviceInfoCollector.APP_PACKAGE_NAME);
+            return mDeviceInfoPkg ;
+        }
+        TestPackageResult pkgResult = mPackageMap.get(appPackageName);
+        if (pkgResult == null) {
+            pkgResult = new TestPackageResult();
+            pkgResult.setAppPackageName(appPackageName);
+            mPackageMap.put(appPackageName, pkgResult);
+        }
+        return pkgResult;
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
index 426ff2d..df1dceb 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
@@ -74,16 +74,16 @@
      * @param testName the test method name
      * @param testResult the {@link TestResult}
      */
-    public void insertTest(List<String> suiteNames, String testClassName, String testName,
-            TestResult testResult) {
+    public Test findTest(List<String> suiteNames, String testClassName, String testName,
+            boolean insertIfMissing) {
         if (suiteNames.size() <= 0) {
             // no more package segments
             TestCase testCase = getTestCase(testClassName);
-            testCase.insertTest(testName, testResult);
+            return testCase.findTest(testName, insertIfMissing);
         } else {
             String rootName = suiteNames.remove(0);
             TestSuite suite = getTestSuite(rootName);
-            suite.insertTest(suiteNames, testClassName, testName, testResult);
+            return suite.findTest(suiteNames, testClassName, testName, insertIfMissing);
         }
     }
 
@@ -221,4 +221,21 @@
             parentSuiteNames.removeLast();
         }
     }
+
+    /**
+     * Count the number of tests in this {@link TestSuite} with given status.
+     *
+     * @param status
+     * @return the test count
+     */
+    public int countTests(CtsTestStatus status) {
+        int total = 0;
+        for (TestSuite suite : mChildSuiteMap.values()) {
+            total += suite.countTests(status);
+        }
+        for (TestCase testCase : mChildTestCaseMap.values()) {
+            total += testCase.countTests(status);
+        }
+        return total;
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
index 68bb62e..e8fdad8 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
@@ -130,14 +130,16 @@
      */
     public void reportUnexecutedTests() {
         for (Map.Entry<String, Collection<TestIdentifier>> entry : mRemainingTestsMap.entrySet()) {
-            super.testRunStarted(entry.getKey(), entry.getValue().size());
-            for (TestIdentifier test : entry.getValue()) {
-                // an unexecuted test is currently reported as a 'testStarted' event without a
-                // 'testEnded'. TODO: consider adding an explict API for reporting an unexecuted
-                // test
-                super.testStarted(test);
+            if (!entry.getValue().isEmpty()) {
+                super.testRunStarted(entry.getKey(), entry.getValue().size());
+                for (TestIdentifier test : entry.getValue()) {
+                    // an unexecuted test is currently reported as a 'testStarted' event without a
+                    // 'testEnded'. TODO: consider adding an explict API for reporting an unexecuted
+                    // test
+                    super.testStarted(test);
+                }
+                super.testRunEnded(0, new HashMap<String,String>());
             }
-            super.testRunEnded(0, new HashMap<String,String>());
         }
     }
 }
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
index 4e6b914..7566fce 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -15,13 +15,17 @@
  */
 package com.android.cts.tradefed.result;
 
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.ddmlib.testrunner.ITestRunListener.TestFailure;
-import com.android.tradefed.build.BuildInfo;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.XmlResultReporter;
 import com.android.tradefed.util.FileUtil;
 
+import junit.framework.TestCase;
+
+import org.easymock.EasyMock;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -29,8 +33,6 @@
 import java.util.Collections;
 import java.util.Map;
 
-import junit.framework.TestCase;
-
 /**
  * Unit tests for {@link XmlResultReporter}.
  */
@@ -39,6 +41,7 @@
     private CtsXmlResultReporter mResultReporter;
     private ByteArrayOutputStream mOutputStream;
     private File mReportDir;
+    private IFolderBuildInfo mMockBuild;
 
     /**
      * {@inheritDoc}
@@ -62,6 +65,7 @@
         // TODO: use mock file dir instead
         mReportDir = FileUtil.createTempDir("foo");
         mResultReporter.setReportDir(mReportDir);
+        mMockBuild = EasyMock.createNiceMock(IFolderBuildInfo.class);
     }
 
     @Override
@@ -83,7 +87,7 @@
         final String expectedSummaryOutput =
             "<Summary failed=\"0\" notExecuted=\"0\" timeout=\"0\" pass=\"0\" />";
         final String expectedEndTag = "</TestResult>";
-        mResultReporter.invocationStarted(new BuildInfo("1", "test", "test"));
+        mResultReporter.invocationStarted(mMockBuild);
         mResultReporter.invocationEnded(1);
         String actualOutput = getOutput();
         assertTrue(actualOutput.startsWith(expectedHeaderOutput));
@@ -101,7 +105,7 @@
     public void testSinglePass() {
         Map<String, String> emptyMap = Collections.emptyMap();
         final TestIdentifier testId = new TestIdentifier("com.foo.FooTest", "testFoo");
-        mResultReporter.invocationStarted(new BuildInfo());
+        mResultReporter.invocationStarted(mMockBuild);
         mResultReporter.testRunStarted("run", 1);
         mResultReporter.testStarted(testId);
         mResultReporter.testEnded(testId, emptyMap);
@@ -128,7 +132,7 @@
         Map<String, String> emptyMap = Collections.emptyMap();
         final TestIdentifier testId = new TestIdentifier("FooTest", "testFoo");
         final String trace = "this is a trace\nmore trace";
-        mResultReporter.invocationStarted(new BuildInfo());
+        mResultReporter.invocationStarted(mMockBuild);
         mResultReporter.testRunStarted("run", 1);
         mResultReporter.testStarted(testId);
         mResultReporter.testFailed(TestFailure.FAILURE, testId, trace);
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
index b3c903b..df80dbb 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestPackageResultTest.java
@@ -16,8 +16,6 @@
 package com.android.cts.tradefed.result;
 
 import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestResult.TestStatus;
 
 import junit.framework.TestCase;
 
@@ -34,14 +32,11 @@
     public void testGetTestsWithStatus() {
         TestPackageResult pkgResult = new TestPackageResult();
         TestIdentifier excludedTest = new TestIdentifier("com.example.ExampleTest", "testPass");
-        TestResult passed = new TestResult();
-        passed.setStatus(TestStatus.PASSED);
-        pkgResult.insertTest(excludedTest, passed);
+        pkgResult.insertTest(excludedTest);
+        pkgResult.reportTestEnded(excludedTest);
         TestIdentifier includedTest = new TestIdentifier("com.example.ExampleTest",
                 "testNotExecuted");
-        TestResult notExecuted = new TestResult();
-        notExecuted.setStatus(TestStatus.INCOMPLETE);
-        pkgResult.insertTest(includedTest, notExecuted);
+        pkgResult.insertTest(includedTest);
         Collection<TestIdentifier> tests =  pkgResult.getTestsWithStatus(
                 CtsTestStatus.NOT_EXECUTED);
         assertEquals(1, tests.size());
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
index a0360cd..75f545e 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
@@ -64,7 +64,7 @@
         TestResults parser = new TestResults();
         parser.parse(new StringReader(TEST_PACKAGE_FULL));
         assertEquals(1, parser.getPackages().size());
-        TestPackageResult pkg = parser.getPackages().get(0);
+        TestPackageResult pkg = parser.getPackages().iterator().next();
         assertEquals("pkgName", pkg.getName());
         assertEquals("appPkgName", pkg.getAppPackageName());
         assertEquals("digValue", pkg.getDigest());
@@ -91,7 +91,7 @@
         TestResults parser = new TestResults();
         parser.parse(new StringReader(TEST_FULL));
         assertEquals(1, parser.getPackages().size());
-        TestPackageResult pkg = parser.getPackages().get(0);
+        TestPackageResult pkg = parser.getPackages().iterator().next();
         TestSuite comSuite = pkg.getTestSuites().iterator().next();
         assertEquals("com", comSuite.getName());
         TestSuite exampleSuite = comSuite.getTestSuites().iterator().next();