Merge "Fix compile error." into honeycomb
diff --git a/tests/res/drawable/baseline_jpeg.jpg b/tests/res/drawable/baseline_jpeg.jpg
new file mode 100644
index 0000000..ed5251c
--- /dev/null
+++ b/tests/res/drawable/baseline_jpeg.jpg
Binary files differ
diff --git a/tests/res/drawable/bmp_test.bmp b/tests/res/drawable/bmp_test.bmp
new file mode 100644
index 0000000..5ec6dd4
--- /dev/null
+++ b/tests/res/drawable/bmp_test.bmp
Binary files differ
diff --git a/tests/res/drawable/gif_test.gif b/tests/res/drawable/gif_test.gif
new file mode 100644
index 0000000..d1c2815
--- /dev/null
+++ b/tests/res/drawable/gif_test.gif
Binary files differ
diff --git a/tests/res/drawable/png_test.png b/tests/res/drawable/png_test.png
new file mode 100644
index 0000000..5230051
--- /dev/null
+++ b/tests/res/drawable/png_test.png
Binary files differ
diff --git a/tests/res/drawable/progressive_jpeg.jpg b/tests/res/drawable/progressive_jpeg.jpg
new file mode 100644
index 0000000..6b58be4
--- /dev/null
+++ b/tests/res/drawable/progressive_jpeg.jpg
Binary files differ
diff --git a/tests/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/tests/app/src/android/app/cts/ActivityManagerTest.java
index 5b1c2b3..217a17c 100644
--- a/tests/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -305,4 +305,21 @@
         ConfigurationInfo conInf = mActivityManager.getDeviceConfigurationInfo();
         assertNotNull(conInf);
     }
+
+    /**
+     * Simple test for {@link ActivityManager.isUserAMonkey()} - verifies its false.
+     *
+     * TODO: test positive case
+     */
+    public void testIsUserAMonkey() {
+        assertFalse(ActivityManager.isUserAMonkey());
+    }
+
+    /**
+     * Verify that {@link ActivityManager.isRunningInTestHarness()} is false.
+     */
+    public void testIsRunningInTestHarness() {
+        assertFalse("isRunningInTestHarness must be false in production builds",
+                ActivityManager.isRunningInTestHarness());
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
new file mode 100644
index 0000000..32cc0cc
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2010 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.graphics.cts;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.cts.stub.R;
+
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@TestTargetClass(BitmapRegionDecoder.class)
+public class BitmapRegionDecoderTest extends InstrumentationTestCase {
+    private static final String TAG = "BitmapRegionDecoder";
+    private ArrayList<File> mFilesCreated = new ArrayList<File>(
+            NAMES_TEMP_FILES.length);
+
+    private Resources mRes;
+
+    // The test images, including a baseline JPEG, progressive JPEG, a PNG,
+    // a GIF and a BMP.
+    private static int[] RES_IDS = new int[] {
+            R.drawable.baseline_jpeg, R.drawable.progressive_jpeg,
+            R.drawable.png_test, R.drawable.gif_test, R.drawable.bmp_test
+    };
+    private static String[] NAMES_TEMP_FILES = new String[] {
+        "baseline_temp.jpg", "progressive_temp.jpg", "png_temp.png",
+        "gif_temp.gif", "bmp_temp.bmp"
+    };
+
+    // The width and height of the above image.
+    // -1 denotes that the image format is not supported by BitmapRegionDecoder
+    private static int WIDTHS[] = new int[] {1280, 1280, 640, -1, -1};
+    private static int HEIGHTS[] = new int[] {960, 960, 480, -1, -1};
+
+    // The number of test images, format of which is supported by BitmapRegionDecoder
+    private static int NUM_TEST_IMAGES = 3;
+
+    private static int TILE_SIZE = 256;
+
+    // Configurations for BitmapFactory.Options
+    private static Config[] COLOR_CONFIGS = new Config[] {Config.ARGB_8888,
+            Config.RGB_565};
+    private static int[] SAMPLESIZES = new int[] {1, 4};
+
+    private int[] mExpectedColors = new int [TILE_SIZE * TILE_SIZE];
+    private int[] mActualColors = new int [TILE_SIZE * TILE_SIZE];
+
+    // We allow a certain degree of discrepancy between the tile-based decoding
+    // result and the regular decoding result, because the two decoders may have
+    // different implementations. The allowable discrepancy is set to a mean
+    // square error of 3 * (1 * 1) among the RGB values.
+    private int mMseMargin = 3 * (1 * 1);
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRes = getInstrumentation().getTargetContext().getResources();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (File file : mFilesCreated) {
+            file.delete();
+        }
+        super.tearDown();
+    }
+
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        method = "newInstance",
+        args = {java.lang.String.class, boolean.class}
+    )
+    public void testNewInstanceInputStream() throws IOException {
+        for (int i = 0; i < RES_IDS.length; ++i) {
+            InputStream is = obtainInputStream(RES_IDS[i]);
+            try {
+                BitmapRegionDecoder decoder =
+                        BitmapRegionDecoder.newInstance(is, false);
+                assertEquals(WIDTHS[i], decoder.getWidth());
+                assertEquals(HEIGHTS[i], decoder.getHeight());
+            } catch (IOException e) {
+                assertEquals(WIDTHS[i], -1);
+                assertEquals(HEIGHTS[i], -1);
+            } finally {
+                if (is != null) {
+                    is.close();
+                }
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "newInstance",
+            args = {byte[].class, int.class, int.class, boolean.class}
+    )
+    public void testNewInstanceByteArray() throws IOException {
+        for (int i = 0; i < RES_IDS.length; ++i) {
+            byte[] imageData = obtainByteArray(RES_IDS[i]);
+            try {
+                BitmapRegionDecoder decoder = BitmapRegionDecoder
+                        .newInstance(imageData, 0, imageData.length, false);
+                assertEquals(WIDTHS[i], decoder.getWidth());
+                assertEquals(HEIGHTS[i], decoder.getHeight());
+            } catch (IOException e) {
+                assertEquals(WIDTHS[i], -1);
+                assertEquals(HEIGHTS[i], -1);
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "newInstance",
+            args = {java.io.FileDescriptor.class, boolean.class}
+    )
+    public void testNewInstanceStringAndFileDescriptor() throws IOException {
+        for (int i = 0; i < RES_IDS.length; ++i) {
+            String filepath = obtainPath(i);
+            FileDescriptor fd = obtainDescriptor(filepath);
+            try {
+                BitmapRegionDecoder decoder1 =
+                        BitmapRegionDecoder.newInstance(filepath, false);
+                assertEquals(WIDTHS[i], decoder1.getWidth());
+                assertEquals(HEIGHTS[i], decoder1.getHeight());
+
+                BitmapRegionDecoder decoder2 =
+                        BitmapRegionDecoder.newInstance(fd, false);
+                assertEquals(WIDTHS[i], decoder2.getWidth());
+                assertEquals(HEIGHTS[i], decoder2.getHeight());
+            } catch (IOException e) {
+                assertEquals(WIDTHS[i], -1);
+                assertEquals(HEIGHTS[i], -1);
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.PARTIAL_COMPLETE,
+            method = "decodeRegion",
+            args = {android.graphics.Rect.class, android.graphics.BitmapFactory.Options.class}
+    )
+    public void testDecodeRegionInputStream() throws IOException {
+        Options opts = new BitmapFactory.Options();
+        for (int i = 0; i < NUM_TEST_IMAGES; ++i) {
+            for (int j = 0; j < SAMPLESIZES.length; ++j) {
+                for (int k = 0; k < COLOR_CONFIGS.length; ++k) {
+                    opts.inSampleSize = SAMPLESIZES[j];
+                    opts.inPreferredConfig = COLOR_CONFIGS[k];
+
+                    InputStream is1 = obtainInputStream(RES_IDS[i]);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    InputStream is2 = obtainInputStream(RES_IDS[i]);
+                    Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts);
+
+                    compareRegionByRegion(decoder, opts, wholeImage);
+                    wholeImage.recycle();
+                }
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.PARTIAL_COMPLETE,
+            method = "decodeRegion",
+            args = {android.graphics.Rect.class, android.graphics.BitmapFactory.Options.class}
+    )
+    public void testDecodeRegionByteArray() throws IOException {
+        Options opts = new BitmapFactory.Options();
+        for (int i = 0; i < NUM_TEST_IMAGES; ++i) {
+            for (int j = 0; j < SAMPLESIZES.length; ++j) {
+                for (int k = 0; k < COLOR_CONFIGS.length; ++k) {
+                    opts.inSampleSize = SAMPLESIZES[j];
+                    opts.inPreferredConfig = COLOR_CONFIGS[k];
+
+                    byte[] imageData = obtainByteArray(RES_IDS[i]);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder
+                            .newInstance(imageData, 0, imageData.length, false);
+                    Bitmap wholeImage = BitmapFactory.decodeByteArray(imageData,
+                            0, imageData.length, opts);
+
+                    compareRegionByRegion(decoder, opts, wholeImage);
+                    wholeImage.recycle();
+                }
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.PARTIAL_COMPLETE,
+            method = "decodeRegion",
+            args = {android.graphics.Rect.class, android.graphics.BitmapFactory.Options.class}
+    )
+    public void testDecodeRegionStringAndFileDescriptor() throws IOException {
+        Options opts = new BitmapFactory.Options();
+        for (int i = 0; i < NUM_TEST_IMAGES; ++i) {
+            String filepath = obtainPath(i);
+            for (int j = 0; j < SAMPLESIZES.length; ++j) {
+                for (int k = 0; k < COLOR_CONFIGS.length; ++k) {
+                    opts.inSampleSize = SAMPLESIZES[j];
+                    opts.inPreferredConfig = COLOR_CONFIGS[k];
+
+                    BitmapRegionDecoder decoder =
+                        BitmapRegionDecoder.newInstance(filepath, false);
+                    Bitmap wholeImage = BitmapFactory.decodeFile(filepath, opts);
+                    compareRegionByRegion(decoder, opts, wholeImage);
+
+                    FileDescriptor fd1 = obtainDescriptor(filepath);
+                    decoder = BitmapRegionDecoder.newInstance(fd1, false);
+                    FileDescriptor fd2 = obtainDescriptor(filepath);
+                    compareRegionByRegion(decoder, opts, wholeImage);
+                    wholeImage.recycle();
+                }
+            }
+        }
+    }
+
+    @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "recycle",
+            args = {}
+    )
+    public void testRecycle() throws IOException {
+        InputStream is = obtainInputStream(RES_IDS[0]);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        decoder.recycle();
+        try {
+            decoder.getWidth();
+            fail("Should throw an exception!");
+        } catch (Exception e) {
+        }
+
+        try {
+            decoder.getHeight();
+            fail("Should throw an exception!");
+        } catch (Exception e) {
+        }
+
+        Rect rect = new Rect(0, 0, WIDTHS[0], HEIGHTS[0]);
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        try {
+            decoder.decodeRegion(rect, opts);
+            fail("Should throw an exception!");
+        } catch (Exception e) {
+        }
+    }
+
+    private void compareRegionByRegion(BitmapRegionDecoder decoder,
+            Options opts, Bitmap wholeImage) {
+        int width = decoder.getWidth();
+        int height = decoder.getHeight();
+        Rect rect = new Rect(0, 0, width, height);
+        int numCols = (width + TILE_SIZE - 1) / TILE_SIZE;
+        int numRows = (height + TILE_SIZE - 1) / TILE_SIZE;
+        Bitmap actual;
+        Bitmap expected;
+
+        for (int i = 0; i < numCols; ++i) {
+            for (int j = 0; j < numRows; ++j) {
+                Rect rect1 = new Rect(i * TILE_SIZE, j * TILE_SIZE,
+                        (i + 1) * TILE_SIZE, (j + 1) * TILE_SIZE);
+                rect1.intersect(rect);
+                actual = decoder.decodeRegion(rect1, opts);
+                int left = rect1.left / opts.inSampleSize;
+                int top = rect1.top / opts.inSampleSize;
+                Rect rect2 = new Rect(left, top, left + actual.getWidth(),
+                        top + actual.getHeight());
+                expected = Bitmap.createBitmap(wholeImage, rect2.left,
+                        rect2.top, rect2.width(), rect2.height(), null, false);
+                compareBitmaps(expected, actual, mMseMargin, true);
+                actual.recycle();
+                expected.recycle();
+            }
+        }
+    }
+
+    private InputStream obtainInputStream(int resId) {
+        return mRes.openRawResource(resId);
+    }
+
+    private byte[] obtainByteArray(int resId) throws IOException {
+        InputStream is = obtainInputStream(resId);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int readLength;
+        while ((readLength = is.read(buffer)) != -1) {
+            os.write(buffer, 0, readLength);
+        }
+        byte[] data = os.toByteArray();
+        is.close();
+        os.close();
+        return data;
+    }
+
+    private String obtainPath(int idx) throws IOException {
+        File dir = getInstrumentation().getTargetContext().getFilesDir();
+        dir.mkdirs();
+        File file = new File(dir, NAMES_TEMP_FILES[idx]);
+        InputStream is = obtainInputStream(RES_IDS[idx]);
+        FileOutputStream fOutput = new FileOutputStream(file);
+        mFilesCreated.add(file);
+        byte[] dataBuffer = new byte[1024];
+        int readLength = 0;
+        while ((readLength = is.read(dataBuffer)) != -1) {
+            fOutput.write(dataBuffer, 0, readLength);
+        }
+        is.close();
+        fOutput.close();
+        return (file.getPath());
+    }
+
+    private FileDescriptor obtainDescriptor(String path) throws IOException {
+        File file = new File(path);
+        return(ParcelFileDescriptor.open(file,
+                ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor());
+    }
+
+    // Compare expected to actual to see if their diff is less then mseMargin.
+    // lessThanMargin is to indicate whether we expect the diff to be
+    // "less than" or "no less than".
+    private void compareBitmaps(Bitmap expected, Bitmap actual,
+            int mseMargin, boolean lessThanMargin) {
+        assertEquals("mismatching widths", expected.getWidth(),
+                actual.getWidth());
+        assertEquals("mismatching heights", expected.getHeight(),
+                actual.getHeight());
+
+        double mse = 0;
+        int width = expected.getWidth();
+        int height = expected.getHeight();
+        int[] expectedColors;
+        int[] actualColors;
+        if (width == TILE_SIZE && height == TILE_SIZE) {
+            expectedColors = mExpectedColors;
+            actualColors = mActualColors;
+        } else {
+            expectedColors = new int [width * height];
+            actualColors = new int [width * height];
+        }
+
+        expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
+        actual.getPixels(actualColors, 0, width, 0, 0, width, height);
+
+        for (int row = 0; row < height; ++row) {
+            for (int col = 0; col < width; ++col) {
+                int idx = row * width + col;
+                mse += distance(expectedColors[idx], actualColors[idx]);
+            }
+        }
+        mse /= width * height;
+
+        if (lessThanMargin) {
+            assertTrue("MSE too large for normal case: " + mse,
+                    mse <= mseMargin);
+        } else {
+            assertFalse("MSE too small for abnormal case: " + mse,
+                    mse <= mseMargin);
+        }
+    }
+
+    private double distance(int exp, int actual) {
+        int r = Color.red(actual) - Color.red(exp);
+        int g = Color.green(actual) - Color.green(exp);
+        int b = Color.blue(actual) - Color.blue(exp);
+        return r * r + g * g + b * b;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildProvider.java
index 90fb8ba..131c605 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildProvider.java
@@ -50,4 +50,11 @@
     public void buildNotTested(IBuildInfo info) {
         // ignore
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void cleanUp(IBuildInfo info) {
+        // ignore
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
index 18ea7cf..96546d0 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
@@ -16,9 +16,6 @@
 
 package com.android.cts.tradefed.testtype;
 
-import com.android.tradefed.testtype.IRemoteTest;
-
-import java.util.Collection;
 
 /**
  * Interface for accessing tests from the CTS repository.
@@ -26,11 +23,11 @@
 interface ITestCaseRepo {
 
     /**
-     * Gets a list of tests identified by given list of uris
+     * Get a {@link TestPackageDef} given a uri
      *
-     * @param testUris the string uris
-     * @return a {@link Collection} of {@link IRemoteTest}
+     * @param testUri the string uris
+     * @return a {@link TestPackageDef} or <code>null</code> if the uri cannot be found in repo
      */
-    public Collection<IRemoteTest> getTests(Collection<String> testUris);
+    public ITestPackageDef getTestPackage(String testUri);
 
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
new file mode 100644
index 0000000..f6febdb
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tradefed.testtype;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.File;
+
+/**
+ * Container for CTS test info.
+ * <p/>
+ * Knows how to translate this info into a runnable {@link IRemoteTest}.
+ */
+interface ITestPackageDef {
+
+    /**
+     * Get the unique URI of the test package.
+     * @return the {@link String} uri
+     */
+    public String getUri();
+
+    /**
+     * Creates a runnable {@link IRemoteTest} from info stored in this definition.
+     *
+     * @param testCaseDir {@link File} representing directory of test case data
+     * @return a {@link IRemoteTest} with all necessary data populated to run the test or
+     *         <code>null</code> if test could not be created
+     */
+    public IRemoteTest createTest(File testCaseDir);
+
+    /**
+     * Determine if given test is defined in this package.
+     *
+     * @param testDef the {@link TestIdentifier}
+     * @return <code>true</code> if test is defined
+     */
+    public boolean isKnownTest(TestIdentifier testDef);
+
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
index 212eb66..8bcf94c 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
@@ -128,13 +128,14 @@
             parser.parse(createXmlStream(ctsPlanFile));
             Collection<String> testUris = parser.getTestUris();
             ITestCaseRepo testRepo = createTestCaseRepo();
-            Collection<IRemoteTest> tests = testRepo.getTests(testUris);
             collectDeviceInfo(getDevice(), mTestCaseDir, listeners);
-            for (IRemoteTest test : tests) {
-                if (test instanceof IDeviceTest) {
-                    ((IDeviceTest)test).setDevice(getDevice());
+            for (String testUri : testUris) {
+                ITestPackageDef testPackage = testRepo.getTestPackage(testUri);
+                if (testPackage != null) {
+                    runTest(listeners, testPackage);
+                } else {
+                    Log.w(LOG_TAG, String.format("Could not find test uri %s", testUri));
                 }
-                test.run(listeners);
             }
         } catch (FileNotFoundException e) {
             throw new IllegalArgumentException("failed to find CTS plan file", e);
@@ -144,13 +145,34 @@
     }
 
     /**
+     * Runs the test.
+     *
+     * @param listeners
+     * @param testPackage
+     * @throws DeviceNotAvailableException
+     */
+    private void runTest(List<ITestInvocationListener> listeners, ITestPackageDef testPackage)
+            throws DeviceNotAvailableException {
+        IRemoteTest test = testPackage.createTest(mTestCaseDir);
+        if (test != null) {
+            if (test instanceof IDeviceTest) {
+                ((IDeviceTest)test).setDevice(getDevice());
+            }
+            ResultFilter filter = new ResultFilter(listeners, testPackage);
+            test.run(filter);
+        }
+    }
+
+    /**
      * Runs the device info collector instrumentation on device, and forwards it to test listeners
      * as run metrics.
+     * <p/>
+     * Exposed so unit tests can mock.
      *
      * @param listeners
      * @throws DeviceNotAvailableException
      */
-    private void collectDeviceInfo(ITestDevice device, File testApkDir,
+    void collectDeviceInfo(ITestDevice device, File testApkDir,
             List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
         DeviceInfoCollector.collectDeviceInfo(device, testApkDir, listeners);
     }
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
new file mode 100644
index 0000000..d11ab03
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ResultFilter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.testtype;
+
+import com.android.ddmlib.Log;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestSummary;
+import com.android.tradefed.targetsetup.IBuildInfo;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link ITestInvocationListener} that filters test results based on the set of expected tests
+ * in CTS test package xml files.
+ */
+class ResultFilter implements ITestInvocationListener {
+
+    private final List<ITestInvocationListener> mListeners;
+    private final ITestPackageDef mTestPackage;
+
+    /**
+     * Create a {@link ResultFilter}.
+     *
+     * @param listeners the real {@link ITestInvocationListener} to forward results to
+     * @param testPackage the {@link ITestPackageDef} that defines the expected tests
+     */
+    ResultFilter(List<ITestInvocationListener> listeners, ITestPackageDef testPackage) {
+        mListeners = listeners;
+        mTestPackage = testPackage;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationStarted(IBuildInfo buildInfo) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.invocationStarted(buildInfo);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationFailed(Throwable cause) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.invocationFailed(cause);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.invocationEnded(elapsedTime);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSummary getSummary() {
+        // should never be called
+        return null;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String dataName, LogDataType dataType, InputStream dataStream) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.testLog(dataName, dataType, dataStream);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStarted(String runName, int testCount) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.testRunStarted(runName, testCount);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunFailed(String errorMessage) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.testRunFailed(errorMessage);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunStopped(long elapsedTime) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.testRunStopped(elapsedTime);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+        for (ITestInvocationListener listener : mListeners) {
+            listener.testRunEnded(elapsedTime, runMetrics);
+        }
+        // TODO: consider reporting all remaining tests in mTestPackage as failed tests with
+        // notExecuted result
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testStarted(TestIdentifier test) {
+        if (isKnownTest(test)) {
+            for (ITestInvocationListener listener : mListeners) {
+                listener.testStarted(test);
+            }
+        } else {
+            Log.d("ResultFilter", String.format("Skipping reporting unknown test %s", test));
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+        if (isKnownTest(test)) {
+            for (ITestInvocationListener listener : mListeners) {
+                listener.testFailed(status, test, trace);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+        if (isKnownTest(test)) {
+            for (ITestInvocationListener listener : mListeners) {
+                listener.testEnded(test, testMetrics);
+            }
+        }
+    }
+
+    /**
+     * @param test
+     * @return
+     */
+    private boolean isKnownTest(TestIdentifier test) {
+        return mTestPackage.isKnownTest(test);
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
index 8392762..fc20314 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
@@ -16,7 +16,6 @@
 package com.android.cts.tradefed.testtype;
 
 import com.android.ddmlib.Log;
-import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
 import java.io.BufferedInputStream;
@@ -25,8 +24,6 @@
 import java.io.FileNotFoundException;
 import java.io.FilenameFilter;
 import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Hashtable;
 import java.util.Map;
 
@@ -115,21 +112,8 @@
     /**
      * {@inheritDoc}
      */
-    public Collection<IRemoteTest> getTests(Collection<String> testUris) {
-        Collection<IRemoteTest> tests = new ArrayList<IRemoteTest>(testUris.size());
-        for (String uri : testUris) {
-            TestPackageDef def = mTestMap.get(uri);
-            if (def != null) {
-                IRemoteTest test = def.createTest(mTestCaseDir);
-                if (test != null) {
-                    tests.add(test);
-                } else {
-                    Log.w(LOG_TAG, String.format("Failed to create test from package uri %s", uri));
-                }
-            } else {
-                Log.w(LOG_TAG, String.format("Could not find test with uri %s", uri));
-            }
-        }
-        return tests;
+    @Override
+    public ITestPackageDef getTestPackage(String testUri) {
+        return mTestMap.get(testUri);
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index a75295e..cf67365 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -29,7 +29,7 @@
  * <p/>
  * Knows how to translate this info into a runnable {@link IRemoteTest}.
  */
-public class TestPackageDef {
+class TestPackageDef implements ITestPackageDef {
 
     private static final String LOG_TAG = "TestPackageDef";
 
@@ -44,13 +44,15 @@
 
     private Collection<TestIdentifier> mTests = new ArrayList<TestIdentifier>();
 
+    /** the cached {@link IRemoteTest} */
+    private IRemoteTest mRemoteTest;
+
     void setUri(String uri) {
         mUri = uri;
     }
 
     /**
-     * Get the unique URI of the test package.
-     * @return the {@link String} uri
+     * {@inheritDoc}
      */
     public String getUri() {
         return mUri;
@@ -114,13 +116,20 @@
     }
 
     /**
-     * Creates a runnable {@link IRemoteTest} from info stored in this definition.
-     *
-     * @param testCaseDir {@link File} representing directory of test case data
-     * @return a {@link IRemoteTest} with all necessary data populated to run the test or
-     *         <code>null</code> if test could not be created
+     * {@inheritDoc}
      */
     public IRemoteTest createTest(File testCaseDir) {
+        if (mRemoteTest == null) {
+            mRemoteTest = doCreateTest(testCaseDir);
+        }
+        return mRemoteTest;
+    }
+
+    /**
+     * @param testCaseDir
+     * @return
+     */
+    private IRemoteTest doCreateTest(File testCaseDir) {
         if (mIsHostSideTest) {
             Log.d(LOG_TAG, String.format("Creating host test for %s", mName));
             JarHostTest hostTest = new JarHostTest();
@@ -157,6 +166,13 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    public boolean isKnownTest(TestIdentifier testDef) {
+        return mTests.contains(testDef);
+    }
+
+    /**
      * Add a {@link TestDef} to the list of tests in this package.
      *
      * @param testdef