am cbd66b79: am 4f024787: Merge "Add Basic PreferenceActivityTest" into froyo

* commit 'cbd66b7946414c313ec38d6f56dde942293e451e':
  Add Basic PreferenceActivityTest
diff --git a/.gitignore b/.gitignore
index 542ab17..ca65e7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 *.pyc
 *.~*
+bin
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index dab331a..bbf7eaf 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,7 +25,7 @@
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni libaudioquality
 
 LOCAL_SDK_VERSION := current
 
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 5577f38..60ebf63 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -21,6 +21,9 @@
       android:versionName="1.0">
       
     <uses-sdk android:minSdkVersion="5"></uses-sdk>
+    
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
 
     <application android:label="@string/app_name">
 
@@ -71,5 +74,25 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors" />
         </activity>
-    </application>
+
+      <activity android:name=".audioquality.AudioQualityVerifierActivity"
+                android:label="@string/aq_verifier"
+                android:screenOrientation="portrait">
+          <intent-filter>
+              <action android:name="android.intent.action.MAIN" />
+              <category android:name="android.cts.intent.category.MANUAL_TEST" />
+          </intent-filter>
+          <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+      </activity>
+      
+      <activity android:name=".audioquality.CalibrateVolumeActivity"
+                android:label="@string/aq_calibrate_volume_name" />
+      
+      <activity android:name=".audioquality.ViewResultsActivity"
+                android:label="@string/aq_view_results_name" />
+
+      <service android:name=".audioquality.ExperimentService" />
+      
+   </application>
+
 </manifest> 
diff --git a/apps/CtsVerifier/assets/audioquality/pink_10000_3s b/apps/CtsVerifier/assets/audioquality/pink_10000_3s
new file mode 100644
index 0000000..e248a50
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/pink_10000_3s
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/pink_5000_1s b/apps/CtsVerifier/assets/audioquality/pink_5000_1s
new file mode 100644
index 0000000..6d88505
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/pink_5000_1s
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/pink_5000_2s b/apps/CtsVerifier/assets/audioquality/pink_5000_2s
new file mode 100644
index 0000000..4acb0ca
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/pink_5000_2s
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_1 b/apps/CtsVerifier/assets/audioquality/stim_dt_1
new file mode 100644
index 0000000..65830bd
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_1
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_2 b/apps/CtsVerifier/assets/audioquality/stim_dt_2
new file mode 100644
index 0000000..c61e7c8
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_2
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_3 b/apps/CtsVerifier/assets/audioquality/stim_dt_3
new file mode 100644
index 0000000..be5f755
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_3
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_31 b/apps/CtsVerifier/assets/audioquality/stim_dt_31
new file mode 100644
index 0000000..8ff5b20
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_31
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_4 b/apps/CtsVerifier/assets/audioquality/stim_dt_4
new file mode 100644
index 0000000..a9c0497
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_4
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_5 b/apps/CtsVerifier/assets/audioquality/stim_dt_5
new file mode 100644
index 0000000..73c2777
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_5
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_6 b/apps/CtsVerifier/assets/audioquality/stim_dt_6
new file mode 100644
index 0000000..381c79a
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_6
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_66 b/apps/CtsVerifier/assets/audioquality/stim_dt_66
new file mode 100644
index 0000000..84964ec
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_66
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_7 b/apps/CtsVerifier/assets/audioquality/stim_dt_7
new file mode 100644
index 0000000..c304764
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_7
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_8 b/apps/CtsVerifier/assets/audioquality/stim_dt_8
new file mode 100644
index 0000000..7479f34
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_8
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/stim_dt_9 b/apps/CtsVerifier/assets/audioquality/stim_dt_9
new file mode 100644
index 0000000..02f5276
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/stim_dt_9
Binary files differ
diff --git a/apps/CtsVerifier/assets/audioquality/tone_250 b/apps/CtsVerifier/assets/audioquality/tone_250
new file mode 100644
index 0000000..ed411f5
--- /dev/null
+++ b/apps/CtsVerifier/assets/audioquality/tone_250
Binary files differ
diff --git a/apps/CtsVerifier/jni/Android.mk b/apps/CtsVerifier/jni/Android.mk
index 98a4678..4343259 100644
--- a/apps/CtsVerifier/jni/Android.mk
+++ b/apps/CtsVerifier/jni/Android.mk
@@ -14,19 +14,4 @@
 # limitations under the License.
 #
 
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libctsverifier_jni
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PRELINK_MODULE := false
-
-LOCAL_SRC_FILES := \
-		CtsVerifierJniOnLoad.cpp \
-		com_android_cts_verifier_os_FileUtils.cpp	
-
-LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-
-include $(BUILD_SHARED_LIBRARY)
+include $(call all-subdir-makefiles)
diff --git a/apps/CtsVerifier/jni/audioquality/Android.mk b/apps/CtsVerifier/jni/audioquality/Android.mk
new file mode 100644
index 0000000..565cec2
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_CPP_EXTENSION := .cpp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_MODULE    := libaudioquality
+LOCAL_SRC_FILES := Fft.cpp Window.cpp GlitchTest.cpp MeasureRms.cpp \
+   OverflowCheck.cpp LinearityTest.cpp CompareSpectra.cpp \
+   GenerateSinusoid.cpp Wrapper.cpp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/CtsVerifier/jni/audioquality/CompareSpectra.cpp b/apps/CtsVerifier/jni/audioquality/CompareSpectra.cpp
new file mode 100644
index 0000000..262a400
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/CompareSpectra.cpp
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ */
+
+// An amplitude-normalized spectrum comparison method.
+
+#include "Fft.h"
+#include "Window.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+/* Find the endpoints of the signal stored in data such that the rms of
+   the found segment exceeds signalOnRms.  data is of length n.  The
+   RMS calculations used to find the endpoints use a window of length
+   step and advance step samples.  The approximate, conservative
+   endpoint sample indices are returned in start and end. */
+static void findEndpoints(short* data, int n, int step, float signalOnRms,
+                          int* start, int* end) {
+    int size = step;
+    *start = *end = 0;
+    int last_frame = n - size;
+    for (int frame = 0; frame < last_frame; frame += step) {
+        double sum = 0.0;
+        for (int i=0; i < size; ++i) {
+            float val = data[i + frame];
+            sum += (val * val);
+        }
+        float rms = sqrt(sum / size);
+        if (! *start) {
+            if (rms >= signalOnRms) {
+                *start = frame + size;
+            }
+            continue;
+        } else {
+            if (rms < signalOnRms) {
+                *end = frame - size;
+                // fprintf(stderr, "n:%d onset:%d offset:%d\n", n, *start, *end);
+                return;
+            }
+        }
+    }
+    // Handle the case where the signal does not drop below threshold
+    // after onset.
+    if ((*start > 0) && (! *end)) {
+        *end = n - size - 1;
+    }
+}
+
+// Sum the magnitude squared spectra.
+static void accumulateMagnitude(float* im, float* re, int size, double* mag) {
+    for (int i = 0; i < size; ++i) {
+        mag[i] += ((re[i] * re[i]) + (im[i] * im[i]));
+    }
+}
+
+/* Return a pointer to 1+(fftSize/2) spectrum magnitude values
+   averaged over all of the numSamples in pcm.  It is the
+   responsibility of the caller to free this magnitude array.  Return
+   NULL on failure.  Use 50% overlap on the spectra. An FFT of
+   fftSize points is used to compute the spectra, The overall signal
+   rms over all frequencies between lowestBin and highestBin is
+   returned as a scalar in rms. */
+double* getAverageSpectrum(short* pcm, int numSamples, int fftSize,
+                           int lowestBin, int highestBin, float* rms) {
+    if (numSamples < fftSize) return NULL;
+    int numFrames = 1 + ((2 * (numSamples - fftSize)) / fftSize);
+    int numMag = 1 + (fftSize / 2);
+    float* re = new float[fftSize];
+    float* im = new float[fftSize];
+    double* mag = new double[numMag];
+    for (int i = 0; i < numMag; ++i) {
+        mag[i] = 0.0;
+    }
+    Window wind(fftSize);
+    Fft ft;
+    int pow2 = ft.fftPow2FromWindowSize(fftSize);
+    ft.fftInit(pow2);
+    int input_p = 0;
+    for (int i = 0; i < numFrames; ++i) {
+        wind.window(pcm + input_p, re, 0.0);
+        for (int j = 0; j < fftSize; ++j) {
+            im[j] = 0.0;
+        }
+        ft.fft(re,im);
+        accumulateMagnitude(im, re, numMag, mag);
+        input_p += fftSize / 2;
+    }
+    double averageEnergy = 0.0; // per frame average energy
+    for (int i = 0; i < numMag; ++i) {
+        double e = mag[i]/numFrames;
+        if ((i >= lowestBin) && (i <= highestBin))
+            averageEnergy += e;
+        mag[i] = sqrt(e);
+    }
+    *rms = sqrt(averageEnergy / (highestBin - lowestBin + 1));
+    delete [] re;
+    delete [] im;
+    return mag;
+}
+
+/* Compare the average magnitude spectra of the signals in pcm and
+   refPcm, which are of length numSamples and numRefSamples,
+   respectively; both sampled at sample_rate.  The maximum deviation
+   between average spectra, expressed in dB, is returned in
+   maxDeviation, and the rms of all dB variations is returned in
+   rmsDeviation.  Note that a lower limit is set on the frequencies that
+   are compared so as to ignore irrelevant DC and rumble components.  If
+   the measurement fails for some reason, return 0; else return 1, for
+   success.  Causes for failure include the amplitude of one or both of
+   the signals being too low, or the duration of the signals being too
+   short.
+
+   Note that the expected signal collection scenario is that the phone
+   would be stimulated with a broadband signal as in a recognition
+   attempt, so that there will be some "silence" regions at the start and
+   end of the pcm signals.  The preferred stimulus would be pink noise,
+   but any broadband signal should work. */
+int compareSpectra(short* pcm, int numSamples, short* refPcm,
+                   int numRefSamples, float sample_rate,
+                   float* maxDeviation, float* rmsDeviation) {
+    int fftSize = 512;           // must be a power of 2
+    float lowestFreq = 100.0;    // don't count DC, room rumble, etc.
+    float highestFreq = 3500.0;  // ignore most effects of sloppy anti alias filters.
+    int lowestBin = int(0.5 + (lowestFreq * fftSize / sample_rate));
+    int highestBin = int(0.5 + (highestFreq * fftSize / sample_rate));
+    float signalOnRms = 1000.0; // endpointer RMS on/off threshold
+    int endpointStepSize = int(0.5 + (sample_rate * 0.02)); // 20ms setp
+    float rms1 = 0.0;
+    float rms2 = 0.0;
+    int onset = 0;
+    int offset = 0;
+    findEndpoints(refPcm, numRefSamples, endpointStepSize, signalOnRms,
+                   &onset, &offset);
+    double* spect1 = getAverageSpectrum(refPcm + onset, offset - onset,
+                                        fftSize, lowestBin, highestBin, &rms1);
+    findEndpoints(pcm, numSamples, endpointStepSize, signalOnRms,
+                  &onset, &offset);
+    double* spect2 = getAverageSpectrum(pcm + onset, offset - onset,
+                                        fftSize, lowestBin, highestBin, &rms2);
+    int magSize = 1 + (fftSize/2);
+    if ((rms1 <= 0.0) || (rms2 <= 0.0))
+        return 0; // failure because one or both signals are too short or
+                  // too low in amplitude.
+    float rmsNorm = rms2 / rms1; // compensate for overall gain differences
+    // fprintf(stderr, "Level normalization: %f dB\n", 20.0 * log10(rmsNorm));
+    *maxDeviation = 0.0;
+    float sumDevSquared = 0.0;
+    for (int i = lowestBin; i <= highestBin; ++i) {
+        double val = 1.0;
+        if ((spect1[i] > 0.0) && (spect2[i] > 0.0)) {
+            val = 20.0 * log10(rmsNorm * spect1[i] / spect2[i]);
+        }
+        sumDevSquared += val * val;
+        if (fabs(val) > fabs(*maxDeviation)) {
+            *maxDeviation = val;
+        }
+        // fprintf(stderr, "%d %f\n", i, val);
+    }
+    *rmsDeviation = sqrt(sumDevSquared / (highestBin - lowestBin + 1));
+    delete [] spect1;
+    delete [] spect2;
+    return 1; // success
+}
+
+
diff --git a/apps/CtsVerifier/jni/audioquality/CompareSpectra.h b/apps/CtsVerifier/jni/audioquality/CompareSpectra.h
new file mode 100644
index 0000000..8b473e1
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/CompareSpectra.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef COMPARE_SPECTRA_H
+#define COMPARE_SPECTRA_H
+
+/* Compare the average magnitude spectra of the signals in pcm and
+   refPcm, which are of length numSamples and nRefSamples,
+   respectively; both sampled at sample_rate.  The maximum deviation
+   between average spectra, expressed in dB, is returned in
+   maxDeviation, and the rms of all dB variations is returned in
+   rmsDeviation.  Note that a lower limit is set on the frequencies that
+   are compared so as to ignore irrelevant DC and rumble components.  If
+   the measurement fails for some reason, return 0; else return 1, for
+   success.  Causes for failure include the amplitude of one or both of
+   the signals being too low, or the duration of the signals being too
+   short.
+
+   Note that the expected signal collection scenario is that the phone
+   would be stimulated with a broadband signal as in a recognition
+   attempt, so that there will be some "silence" regions at the start and
+   end of the pcm signals.  The preferred stimulus would be pink noise,
+   but any broadband signal should work. */
+
+int compareSpectra(short* pcm, int numSamples, short* refPcm,
+                   int nRefSamples, float sampleRate,
+                   float* maxDeviation, float* rmsDeviation);
+
+#endif // COMPARE_SPECTRA_H
diff --git a/apps/CtsVerifier/jni/audioquality/Fft.cpp b/apps/CtsVerifier/jni/audioquality/Fft.cpp
new file mode 100644
index 0000000..867bc86
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Fft.cpp
@@ -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.
+ */
+
+#include "Fft.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+Fft::Fft(void) : mCosine(0), mSine(0), mFftSize(0), mFftTableSize(0) { }
+
+Fft::~Fft(void) {
+    fftCleanup();
+}
+
+/* Construct a FFT table suitable to perform a DFT of size 2^power. */
+void Fft::fftInit(int power) {
+    fftCleanup();
+    fftMakeTable(power);
+}
+
+void Fft::fftCleanup() {
+        delete [] mSine;
+        delete [] mCosine;
+        mSine = NULL;
+        mCosine = NULL;
+        mFftTableSize = 0;
+        mFftSize = 0;
+}
+
+/* z <- (10 * log10(x^2 + y^2))    for n elements */
+int Fft::fftLogMag(float *x, float *y, float *z, int n) {
+    float *xp, *yp, *zp, t1, t2, ssq;
+
+    if(x && y && z && n) {
+        for(xp=x+n, yp=y+n, zp=z+n; zp > z;) {
+            t1 = *--xp;
+            t2 = *--yp;
+            ssq = (t1*t1)+(t2*t2);
+            *--zp = (ssq > 0.0)? 10.0 * log10((double)ssq) : -200.0;
+        }
+        return 1;    //true
+    } else {
+        return 0;    // false/fail
+    }
+}
+
+int Fft::fftMakeTable(int pow2) {
+    int lmx, lm;
+    float *c, *s;
+    double scl, arg;
+
+    mFftSize = 1 << pow2;
+    mFftTableSize = lmx = mFftSize/2;
+    mSine = new float[lmx];
+    mCosine = new float[lmx];
+    scl = (M_PI*2.0)/mFftSize;
+    for (s=mSine, c=mCosine, lm=0; lm<lmx; lm++ ) {
+        arg = scl * lm;
+        *s++ = sin(arg);
+        *c++ = cos(arg);
+    }
+    mBase = (mFftTableSize * 2)/mFftSize;
+    mPower2 = pow2;
+    return(mFftTableSize);
+}
+
+
+/* Compute the discrete Fourier transform of the 2**l complex sequence
+ * in x (real) and y (imaginary).  The DFT is computed in place and the
+ * Fourier coefficients are returned in x and y.
+ */
+void Fft::fft( float *x, float *y ) {
+    float c, s, t1, t2;
+    int j1, j2, li, lix, i;
+    int lmx, lo, lixnp, lm, j, nv2, k=mBase, im, jm, l = mPower2;
+
+    for (lmx=mFftSize, lo=0; lo < l; lo++, k *= 2) {
+        lix = lmx;
+        lmx /= 2;
+        lixnp = mFftSize - lix;
+        for (i=0, lm=0; lm<lmx; lm++, i += k ) {
+            c = mCosine[i];
+            s = mSine[i];
+            for ( li = lixnp+lm, j1 = lm, j2 = lm+lmx; j1<=li;
+                  j1+=lix, j2+=lix ) {
+                t1 = x[j1] - x[j2];
+                t2 = y[j1] - y[j2];
+                x[j1] += x[j2];
+                y[j1] += y[j2];
+                x[j2] = (c * t1) + (s * t2);
+                y[j2] = (c * t2) - (s * t1);
+            }
+        }
+    }
+
+    /* Now perform the bit reversal. */
+    j = 1;
+    nv2 = mFftSize/2;
+    for ( i=1; i < mFftSize; i++ ) {
+        if ( j < i ) {
+            jm = j-1;
+            im = i-1;
+            t1 = x[jm];
+            t2 = y[jm];
+            x[jm] = x[im];
+            y[jm] = y[im];
+            x[im] = t1;
+            y[im] = t2;
+        }
+        k = nv2;
+        while ( j > k ) {
+            j -= k;
+            k /= 2;
+        }
+        j += k;
+    }
+}
+
+/* Compute the discrete inverse Fourier transform of the 2**l complex
+ * sequence in x (real) and y (imaginary).  The DFT is computed in
+ * place and the Fourier coefficients are returned in x and y.  Note
+ * that this DOES NOT scale the result by the inverse FFT size.
+ */
+void Fft::ifft(float *x, float *y ) {
+    float c, s, t1, t2;
+    int j1, j2, li, lix, i;
+    int lmx, lo, lixnp, lm, j, nv2, k=mBase, im, jm, l = mPower2;
+
+    for (lmx=mFftSize, lo=0; lo < l; lo++, k *= 2) {
+        lix = lmx;
+        lmx /= 2;
+        lixnp = mFftSize - lix;
+        for (i=0, lm=0; lm<lmx; lm++, i += k ) {
+            c = mCosine[i];
+            s = - mSine[i];
+            for ( li = lixnp+lm, j1 = lm, j2 = lm+lmx; j1<=li;
+                        j1+=lix, j2+=lix ) {
+                t1 = x[j1] - x[j2];
+                t2 = y[j1] - y[j2];
+                x[j1] += x[j2];
+                y[j1] += y[j2];
+                x[j2] = (c * t1) + (s * t2);
+                y[j2] = (c * t2) - (s * t1);
+            }
+        }
+    }
+
+    /* Now perform the bit reversal. */
+    j = 1;
+    nv2 = mFftSize/2;
+    for ( i=1; i < mFftSize; i++ ) {
+        if ( j < i ) {
+            jm = j-1;
+            im = i-1;
+            t1 = x[jm];
+            t2 = y[jm];
+            x[jm] = x[im];
+            y[jm] = y[im];
+            x[im] = t1;
+            y[im] = t2;
+        }
+        k = nv2;
+        while ( j > k ) {
+            j -= k;
+            k /= 2;
+        }
+        j += k;
+    }
+}
+
+int Fft::fftGetSize(void) { return mFftSize; }
+
+int Fft::fftGetPower2(void) { return mPower2; }
diff --git a/apps/CtsVerifier/jni/audioquality/Fft.h b/apps/CtsVerifier/jni/audioquality/Fft.h
new file mode 100644
index 0000000..6a21d9a
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Fft.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#ifndef LEGACY_TALKIN_FFT_H
+#define LEGACY_TALKIN_FFT_H
+
+class Fft {
+public:
+    Fft(void);
+
+    virtual ~Fft(void);
+
+    // Prepare for radix-2 FFT's of size (1<<pow2) 
+    void fftInit(int pow2);
+
+    // Forward fft.  Real time-domain components in x, imaginary in y 
+    void fft(float *x, float *y);
+
+    // Inverse fft.  Real frequency-domain components in x, imaginary in y 
+    void ifft(float *x, float *y);
+
+    // Compute the dB-scaled log-magnitude spectrum from the real spectal
+    // amplitude values in 'x', and imaginary values in 'y'.  Return the
+    // magnitude spectrum in z.  Compute 'n' components. 
+    int fftLogMag(float *x, float *y, float *z, int n);
+
+    int fftGetSize();
+
+    int fftGetPower2();
+
+    // Return the power of 2 required to contain at least size samples.
+    static int fftPow2FromWindowSize(int size) {
+        int pow2 = 1;
+        while ((1 << pow2) < size)
+            pow2++;
+        return pow2;
+    }
+
+private:
+    // Free up memory and reset the static globals. 
+    void fftCleanup();
+
+    // Create the sine/cosine basis tables and return the size of the FFT
+    // corresponding to pow2. 
+    int fftMakeTable(int pow2);
+
+    float* mSine;
+    float* mCosine;
+    int mFftTableSize;
+    int mFftSize;
+    int mPower2;
+    int mBase;
+};
+
+#endif // LEGACY_TALKIN_FFT_H 
diff --git a/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.cpp b/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.cpp
new file mode 100644
index 0000000..1823d58
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.cpp
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+// Generate a sinusoidal signal with optional onset and offset ramps.
+
+#include <stdlib.h>
+#include <math.h>
+
+static inline short clipAndRound(float val) {
+    if (val > 32767.0)
+        return 32767;
+    if (val < -32768.0)
+        return -32768;
+    if (val >= 0.0)
+        return static_cast<short>(0.5 + val);
+    return static_cast<short>(val - 0.5);
+}
+
+/* Return a sinusoid of frequency freq Hz and amplitude ampl in output
+   of length numOutput.  If ramp > 0.0, taper the ends of the signal
+   with a half-raised-cosine function.  It is up to the caller to
+   delete[] output.  If this call fails due to unreasonable arguments,
+   numOutput will be zero, and output will be NULL.  Note that the
+   duration of the up/down ramps will be within the specified
+   duration.  Note that if amplitude is specified outside of the
+   numerical range of int16, the signal will be clipped at +- 32767. */
+void generateSinusoid(float freq, float duration, float sampleRate,
+                      float amplitude, float ramp,
+                      int* numOutput, short** output) {
+    // Sanity check
+    if ((duration < (2.0 * ramp)) || ((freq * 2.0) > sampleRate) || (ramp < 0.0)) {
+        *output = NULL;
+        *numOutput = 0;
+        return;
+    }
+    int numSamples = int(0.5 + (sampleRate * duration));
+    double arg = M_PI * 2.0 * freq / sampleRate;
+    short* wave = new short[numSamples];
+    int numRamp = int(0.5 + (sampleRate * ramp));
+    for (int i = 0; i < numSamples; ++i) {
+        float val = amplitude * sin(arg * i);
+        if (numRamp > 0) {
+            if (i < numRamp) {
+                float gain = (0.5 - (0.5 * cos((0.5 + i) * M_PI / numRamp)));
+                val *= gain;
+            } else {
+                if (i > (numSamples - numRamp - 1)) {
+                    float gain = (0.5 - (0.5 * cos((0.5 + (numSamples - i - 1))
+                            * M_PI / numRamp)));
+                    val *= gain;
+                }
+            }
+        }
+        wave[i] = clipAndRound(val);
+    }
+    *numOutput = numSamples;
+    *output = wave;
+}
diff --git a/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.h b/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.h
new file mode 100644
index 0000000..eeedca6
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/GenerateSinusoid.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef GENERATE_SINUSOID_H
+#define GENERATE_SINUSOID_H
+
+/* Return a sinusoid of frequency freq Hz and amplitude ampl in output
+   of length numOutput.  If ramp > 0.0, taper the ends of the signal
+   with a half-raised-cosine function.  It is up to the caller to
+   delete[] output.  If this call fails due to unreasonable arguments,
+   numOutput will be zero, and output will be NULL.  Note that the
+   duration of the up/down ramps will be within the specified
+   duration.  Note that if amplitude is specified outside of the
+   numerical range of int16, the signal will be clipped at +- 32767. */
+void generateSinusoid(float freq, float duration, float sampleRate,
+                      float amplitude, float ramp,
+                      int* numOutput, short** output);
+
+#endif // GENERATE_SINUSOID_H
+
diff --git a/apps/CtsVerifier/jni/audioquality/GlitchTest.cpp b/apps/CtsVerifier/jni/audioquality/GlitchTest.cpp
new file mode 100644
index 0000000..2605f77
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/GlitchTest.cpp
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+#include "GlitchTest.h"
+#include "Window.h"
+#include "Fft.h"
+
+#include <math.h>
+
+GlitchTest::GlitchTest(void) : mRe(0), mIm(0), mFt(0), mWind(0) {}
+
+void GlitchTest::init(float sampleRate, float stimFreq, float onsetThresh,
+                      float dbSnrThresh) {
+    cleanup(); // in case Init gets called multiple times.
+    // Analysis parameters
+    float windowDur = 0.025;    // Duration of the analysis window in seconds. 
+    float frameInterval = 0.01; // Interval in seconds between frame onsets 
+    float lowestFreq = 200.0;   // Lowest frequency to include in
+                                // background noise power integration.
+    mSampleRate = sampleRate;
+    mOnsetThresh = onsetThresh;
+    mDbSnrThresh = dbSnrThresh;
+    mFrameStep = (int)(0.5 + (mSampleRate * frameInterval));
+    mWindowSize = (int)(0.5 + (mSampleRate * windowDur));
+    mWind = new Window(mWindowSize);
+    mFt = new Fft;
+    mFt->fftInit(mFt->fftPow2FromWindowSize(mWindowSize));
+    mFftSize = mFt->fftGetSize();
+    mRe = new float[mFftSize];
+    mIm = new float[mFftSize];
+    float freqInterval = mSampleRate / mFftSize;
+    // We can exclude low frequencies from consideration, since the
+    // phone may have DC offset in the A/D, and there may be rumble in
+    // the testing room.    
+    mLowestSpectrumBin = (int)(0.5 + (lowestFreq / freqInterval));
+    // These are the bin indices within which most of the energy due to
+    // the (windowed) tone should be found. 
+    mLowToneBin = (int)(0.5 + (stimFreq / freqInterval)) - 2;
+    mHighToneBin = (int)(0.5 + (stimFreq / freqInterval)) + 2;
+    if (mLowestSpectrumBin >= mLowToneBin) {
+        mLowestSpectrumBin = mHighToneBin + 1;
+    }
+}
+
+int GlitchTest::checkToneSnr(short* pcm, int numSamples, float* duration,
+                             int* numBadFrames) {
+    *numBadFrames = 0;
+    *duration = 0.0;
+    if (!(mRe && mIm)) {
+        return -1; // not initialized.
+    }
+    int n_frames = 1 + ((numSamples - mWindowSize) / mFrameStep);
+    if (n_frames < 4) { // pathologically short input signal 
+        return -2;
+    }
+    *numBadFrames = 0;
+    int onset = -1;
+    int offset = -1;
+    for (int frame = 0; frame < n_frames; ++frame) {
+        int numSpectra = 0;
+        mWind->window(pcm + frame*mFrameStep, mRe, 0.0);
+        realMagSqSpectrum(mRe, mWindowSize, mRe, &numSpectra);
+        int maxLoc = 0;
+        float maxValue = 0.0;
+        findPeak(mRe, mLowestSpectrumBin, numSpectra, &maxLoc, &maxValue);
+        // possible states: (1) before tone onset; (2) within tone
+        // region; (3) after tone offset.
+        if ((onset < 0) && (offset < 0)) { // (1) 
+            if ((maxLoc >= mLowToneBin) && (maxLoc <= mHighToneBin)
+                    && (maxValue > mOnsetThresh)) {
+                onset = frame;
+            }
+            continue;
+        }
+        if ((onset >= 0) && (offset < 0)) { // (2) 
+            if (frame > (onset + 2)) { // let the framer get completely
+                                       // into the tonal signal
+                double sumNoise = 1.0; // init. to small non-zero vals to
+                                       // avoid log or divz problems
+                double sumSignal = 1.0;
+                float snr = 0.0;
+                if (maxValue < mOnsetThresh) {
+                    offset = frame;
+                    *duration = mFrameStep * (offset - onset) / mSampleRate;
+                    if (*numBadFrames >= 1) {
+                        (*numBadFrames) -= 1; // account for expected glitch at
+                                            // signal offset
+                    }
+                    continue;
+                }
+                for (int i = mLowestSpectrumBin; i < mLowToneBin; ++i) {
+                    sumNoise += mRe[i]; // note that mRe contains the magnitude
+                                        // squared spectrum.
+                }
+                for (int i = mLowToneBin; i <= mHighToneBin; ++i)
+                    sumSignal += mRe[i];
+                for (int i = mHighToneBin + 1; i < numSpectra; ++i) {
+                    sumNoise += mRe[i]; // Note: mRe has the mag squared spectrum.
+                }
+                snr = 10.0 * log10(sumSignal / sumNoise);
+                if (snr < mDbSnrThresh)
+                    (*numBadFrames) += 1;
+            }
+            continue;
+        }
+        if ((onset >= 0) && (offset > 0)) { // (3)
+            if ((maxLoc >= mLowToneBin) && (maxLoc <= mHighToneBin) &&
+                    (maxValue > mOnsetThresh)) { // tone should not pop up again!
+                (*numBadFrames) += 1;
+            }
+            continue;
+        }
+    }
+    if ((onset >= 0) && (offset > 0))
+        return 1;  // Success.
+    if (onset < 0) {
+        return -3; // Signal onset not found.
+    }
+    return -4;     // Signal offset not found.
+}
+
+void GlitchTest::cleanup(void) {
+    delete [] mRe;
+    delete [] mIm;
+    delete mFt;
+    delete mWind;
+    mRe = 0;
+    mIm = 0;
+    mWind = 0;
+    mFt = 0;
+}
+
+int GlitchTest::realMagSqSpectrum(float* data, int numInput,
+                                  float* output, int* numOutput) {
+    *numOutput = 0;
+    if ((numInput <= 0) || (numInput > mFftSize))
+        return 0;
+    int i = 0;
+    for (i = 0; i < numInput; ++i) {
+        mRe[i] = data[i];
+        mIm[i] = 0.0;
+    }
+    for ( ; i < mFftSize; ++i) {
+        mRe[i] = 0.0;
+        mIm[i] = 0.0;
+    }
+    mFt->fft(mRe, mIm);
+    *numOutput = 1 + (mFftSize / 2);
+    for (i = 0; i < *numOutput; ++i) {
+        output[i] = (mRe[i] * mRe[i]) + (mIm[i] * mIm[i]);
+    }
+    return 1;
+}
+
+void GlitchTest::findPeak(float* data, int startSearch, int endSearch,
+                          int* maxLoc, float* maxValue) {
+    float amax = data[startSearch];
+    int loc = startSearch;
+    for (int i = startSearch + 1; i < endSearch; ++i) {
+        if (data[i] > amax) {
+            amax = data[i];
+            loc = i;
+        }
+    }
+    *maxLoc = loc;
+    *maxValue = 10.0 * log10(amax / mWindowSize);
+}
+
diff --git a/apps/CtsVerifier/jni/audioquality/GlitchTest.h b/apps/CtsVerifier/jni/audioquality/GlitchTest.h
new file mode 100644
index 0000000..de96e6e
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/GlitchTest.h
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+#ifndef GLITCH_TEST_H
+#define GLITCH_TEST_H
+
+class Fft;
+class Window;
+
+class GlitchTest {
+public:
+    GlitchTest(void);
+
+    virtual ~GlitchTest(void) {
+        cleanup();
+    }
+
+    /* Set up the instance to operate on input test signals sampled at
+       sample_rate that contain a stimulis tone of stim_freq frequency.
+       The signal will be considered on during the interval it exceeds
+       onset_thresh (dB re 1.0).  Any frames containing a tone that have
+       a signal energy to out-of-band energy ratio less than
+       db_snr_thresh are counted as bad frames.  Init must be called
+       before CheckToneSnr. */
+    void init(float sample_rate, float stim_freq, float onset_thresh,
+              float db_snr_thresh);
+
+    /* Analyze the n_samples of the lin16 signal in pcm.  This signal is
+       assumed sampled at sample_rate, and to contain a sinusoid with
+       center frequency stim_freq, embedded somewhere in time, with
+       "silence" intervals before and after.  The contiguous duration of
+       the tone that exceeds onset_thresh (in dB re 1.0) is returned as
+       seconds in duration. The number of frames for which the ratio of
+       the energy at the tone frequency to the energy in the rest of the
+       spectrum is less than db_snr_thresh is returned in n_bad_frames.
+       If the test succeed, the method returns 1, else it returns a
+       negative number that reflects the cause of failure as follows:
+         -1     The instance is not initialized.
+         -2     There are not enough samples to do a reasonable check.
+         -3     The tone signal onset was not found.
+         -4     The tone signal end was not found. */
+    int checkToneSnr(short* pcm, int n_samples, float* duration,
+                     int* n_bad_frames);
+
+private:
+    // Free memory, etc.
+    void cleanup(void);
+
+    /* Do a real FFT on the n_input samples in data, and return n_output
+       power spectral density points in output.  The output points include
+       DC through the Nyquist frequency (i.e. 1 + fft_size/2).  output
+       must be large enough to accommodate this size. If n_input==0 or
+       n_input > fft_size, return 0; else return 1. */
+    int realMagSqSpectrum(float* data, int n_input,
+                             float* output, int* n_output);
+
+    /* Find the largest value in data starting at start_search and ending
+       at end_search-1.  The values in data are assumed to be magnitude
+       squared values from a spectrum computation based on window_size
+       sample points.  Return the index where the largest value was found,
+       and return the dB (re 1.0) equivalent of the highest magnitude. */
+    void findPeak(float* data, int start_search, int end_search,
+                  int* max_loc, float* max_value);
+
+    // Real and Imaginary analysis arrays.
+    float* mRe;
+    float* mIm;
+    // Fourier transform and window.
+    Fft* mFt;
+    Window* mWind;
+    // Derived parameters and other variables.
+    float mSampleRate;
+    int mFrameStep;
+    int mWindowSize;
+    int mFftSize;
+    float mOnsetThresh;
+    float mDbSnrThresh;
+    int mLowestSpectrumBin;
+    int mLowToneBin;
+    int mHighToneBin;
+};
+
+#endif // GLITCH_TEST_H
diff --git a/apps/CtsVerifier/jni/audioquality/LinearityTest.cpp b/apps/CtsVerifier/jni/audioquality/LinearityTest.cpp
new file mode 100644
index 0000000..f8cf9ac
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/LinearityTest.cpp
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+/* This test accepts a collection of N speech waveforms collected as
+   part of N recognition attempts.  The waveforms are ordered by
+   increasing presentation level.  The test determines the extent to
+   which the peak amplitudes in the waveforms track the change in
+   presentation level.  Failure to track the presentation level within
+   some reasonable margin is an indication of clipping or of automatic
+   gain control in the signal path.
+
+   The speech stimuli that are used for this test should simply be
+   replications of exactly the same speech signal presented at
+   different levels.  It is expected that all recognition attempts on
+   this signal result in correct recognition.  A warning, but not a
+   hard failure, should be issued if any of the attempts fail.  The
+   hard failure criterion for this test should be only based on the
+   amplitude linearity tracking. */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+/* Keep a record of the top N absolute values found using a slow
+   bubble-sort.  This is OK, since the use of this program is not time
+   critical, and N is usually small.  Note that the argument n = N-1. */
+static void bubbleUp(int* store, int n, short val) {
+    if (val < store[n])
+        return;
+    for (int i = 0; i <= n; ++i) {
+        if (val >= store[i]) {
+            for (int j = n; j > i ; j--)
+                store[j] = store[j-1];
+            store[i] = val;
+            return;
+        }
+    }
+}
+
+/* Make two measurements on the signal of length numSamples sampled at
+   sampleRate in pcm: the RMS of the highest amplitude 30ms segment
+   (returned in peakRms), and the RMS of the top 50 peak absolute
+   values found (returned in peakAverage).  If the signal is too
+   short to make reasonable measurements, the function returns 0, else
+   it returns 1. */
+static int peakLevels(short* pcm, int numSamples, float sampleRate,
+                      float* peakAverage, float* peakRms) {
+    float rmsFrameSize = 0.03;
+    float rmsFrameStep = 0.01;
+    int frameStep = int(0.5 + (sampleRate * rmsFrameStep));
+    int frameSize = int(0.5 + (sampleRate * rmsFrameSize));
+    int numFrames = 1 + ((numSamples - frameSize) / frameStep);
+
+    if (numFrames < 10) {
+        return 0; // failure for too short signal
+    }
+
+    // Peak RMS calculation
+    double maxEnergy = 0.0;
+    for (int frame = 0; frame < numFrames; ++frame) {
+        double energy = 0.0;
+        int limit = (frame * frameStep) + frameSize;
+        for (int i = frame * frameStep; i < limit; ++i) {
+            double s = pcm[i];
+            energy += s * s;
+        }
+        if (energy > maxEnergy) {
+            maxEnergy = energy;
+        }
+    }
+    *peakRms = sqrt(maxEnergy / frameSize);
+
+    // Find the absolute highest topN peaks in the signal and compute
+    // the RMS of their values.
+    int topN = 50; // The number of highest peaks over which to average.
+    int topM = topN - 1;
+    int* maxVal = new int[topN];
+    for (int i = 0; i < topN; ++i) {
+        maxVal[i] = 0;
+    }
+    for (int i = 0; i < numSamples; ++i) {
+        if (pcm[i] >= 0) {
+            bubbleUp(maxVal, topM, pcm[i]);
+        } else {
+            bubbleUp(maxVal, topM, -pcm[i]);
+        }
+    }
+    float sum = 0.0;
+    // The RMS is taken bacause we want the values of the highest peaks
+    // to dominate.
+    for (int i = 0; i < topN; ++i) {
+        float fval = maxVal[i];
+        sum += (fval * fval);
+    }
+    delete [] maxVal;
+    *peakAverage = sqrt(sum/topN);
+    return 1; // success
+}
+
+/* There are numSignals int16 signals in pcms.  sampleCounts is an
+   integer array of length numSignals containing their respective
+   lengths in samples.  They are all sampled at sampleRate.  The pcms
+   are ordered by increasing stimulus level.  The level steps between
+   successive stimuli were of size dbStepSize dB.  The signal with
+   index referenceStim (0 <= referenceStim < numSignals) should be in
+   an amplitude range that is reasonably certain to be linear (e.g. at
+   normal speaking levels).  The maximum deviation in linearity found
+   (in dB) is returned in maxDeviation.  The function returns 1 if
+   the measurements could be made, or a negative number that
+   indicates the error, as follows:
+      -1 The input signals or sample counts are missing.
+      -2 The number of input signals is < 2.
+      -3 The specified sample rate is <= 4000.0
+      -4 The dB step size for the increase in stimulus level is <= 0.0
+      -5 The specified reverence stimulus number is out of range.
+      -6 One or more of the stimuli is too short in duration. */
+int linearityTest(short** pcms, int* sampleCounts, int numSignals,
+                  float sampleRate, float dbStepSize, int referenceStim,
+                  float* maxDeviation) {
+    if (!(pcms && sampleCounts)) {
+        return -1; // Input signals or sample counts are missing
+    }
+    if (numSignals < 2) {
+        return -2; // the number of input signals must be >= 2;
+    }
+    if (sampleRate <= 4000.0) {
+        return -3; // The sample rate must be > 4000 Hz.
+    }
+    if (dbStepSize <= 0.0) {
+        return -4; // The dB step size must be > 0.0
+    }
+    if (!((referenceStim >= 0) && (referenceStim < numSignals))) {
+        return -5; // (0 <= referenceStim < numSignals) must be true
+    }
+    float* peakAverage = new float[numSignals];
+    float* peakRms = new float[numSignals];
+    for (int sig = 0; sig < numSignals; ++sig) {
+        if (!peakLevels(pcms[sig], sampleCounts[sig],
+             sampleRate, peakAverage + sig, peakRms + sig)) {
+            return -6; // failure because a signal is too short.
+        }
+    }
+    float peakAverageRef = peakAverage[referenceStim];
+    float peakRmsRef = peakRms[referenceStim];
+    float maxDev = 0.0;
+    for (int i = 0; i < numSignals; ++i) {
+        float dbAverage = 20.0 * log10(peakAverage[i]/peakAverageRef);
+        float dbRms = 20.0 * log10(peakRms[i]/peakRmsRef);
+        float reference = dbStepSize * (i - referenceStim);
+        float average_level = 0.5 * (dbAverage + dbRms);
+        float dev = fabs(average_level - reference);
+        // fprintf(stderr,"dbAverage:%f dbRms:%f reference:%f dev:%f\n",
+        //         dbAverage, dbRms, reference, dev);
+        if (dev > maxDev)
+            maxDev = dev;
+    }
+    *maxDeviation = maxDev;
+    return 1;
+}
diff --git a/apps/CtsVerifier/jni/audioquality/LinearityTest.h b/apps/CtsVerifier/jni/audioquality/LinearityTest.h
new file mode 100644
index 0000000..d92566a
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/LinearityTest.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef LINEARITY_TEST_H
+#define LINEARITY_TEST_H
+
+/* There are numSignals int16 signals in pcms.  sampleCounts is an
+   integer array of length numSignals containing their respective
+   lengths in samples.  They are all sampled at sampleRate.  The pcms
+   are ordered by increasing stimulus level.  The level steps between
+   successive stimuli were of size dbStepSize dB.  The signal with
+   index referenceStim (0 <= referenceStim < numSignals) should be in
+   an amplitude range that is reasonably certain to be linear (e.g. at
+   normal speaking levels).  The maximum deviation in linearity found
+   (in dB) is returned in maxDeviation.  The function returns 1 if
+   the measurements could be made, or a negative number that
+   indicates the error, as follows:
+      -1 The input signals or sample counts are missing.
+      -2 The number of input signals is < 2.
+      -3 The specified sample rate is <= 4000.0
+      -4 The dB step size for the increase in stimulus level is <= 0.0/
+      -5 The specified reverence stimulus number is out of range.
+      -6 One or more of the stimuli is too short in duration. */
+int linearityTest(short** pcms, int* sampleCounts, int numSignals,
+                  float sampleRate, float dbStepSize,
+                  int referenceStim, float* maxDeviation);
+
+
+#endif /* LINEARITY_TEST_H */
diff --git a/apps/CtsVerifier/jni/audioquality/MeasureRms.cpp b/apps/CtsVerifier/jni/audioquality/MeasureRms.cpp
new file mode 100644
index 0000000..640b191
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/MeasureRms.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+#include <math.h>
+
+/* Return the median of the n values in "values".
+   Uses a stupid bubble sort, but is only called once on small array. */
+float getMedian(float* values, int n) {
+    if (n <= 0)
+        return 0.0;
+    if (n == 1)
+        return values[0];
+    if (n == 2)
+        return 0.5 * (values[0] + values[1]);
+    for (int i = 1; i < n; ++i)
+        for (int j = i; j < n; ++j) {
+            if (values[j] < values[i-1]) {
+                float tmp = values[i-1];
+                values[i-1] = values[j];
+                values[j] = tmp;
+            }
+        }
+    int ind = int(0.5 + (0.5 * n)) - 1;
+    return values[ind];
+}
+
+float computeAndRemoveMean(short* pcm, int numSamples) {
+    float sum = 0.0;
+
+    for (int i = 0; i < numSamples; ++i)
+        sum += pcm[i];
+    short mean;
+    if (sum >= 0.0)
+        mean = (short)(0.5 + (sum / numSamples));
+    else
+        mean = (short)((sum / numSamples) - 0.5);
+    for (int i = 0; i < numSamples; ++i)
+        pcm[i] -= mean;
+    return sum / numSamples;
+}
+
+void measureRms(short* pcm, int numSamples, float sampleRate, float onsetThresh,
+                float* rms, float* stdRms, float* mean, float* duration) {
+    *rms = 0.0;
+    *stdRms = 0.0;
+    *duration = 0.0;
+    float frameDur = 0.025;    // Both the duration and interval of the
+                                // analysis frames.
+    float calInterval = 0.250; // initial part of signal used to
+                                // establish background level (seconds).
+    double sumFrameRms = 1.0;
+    float sumSampleSquares = 0.0;
+    double sumFrameSquares = 1.0; // init. to small number to avoid
+                                    // log and divz problems.
+    int frameSize = (int)(0.5 + (sampleRate * frameDur));
+    int numFrames = numSamples / frameSize;
+    int numCalFrames = int(0.5 + (calInterval / frameDur));
+    if (numCalFrames < 1)
+        numCalFrames = 1;
+    int frame = 0;
+
+    *mean = computeAndRemoveMean(pcm, numSamples);
+
+    if (onsetThresh < 0.0) { // Handle the case where we want to
+                              // simply measure the RMS of the entire
+                              // input sequence.
+        for (frame = 0; frame < numFrames; ++frame) {
+            short* p_data = pcm + (frame * frameSize);
+            int i;
+            for (i = 0, sumSampleSquares = 0.0; i < frameSize; ++i) {
+                float samp = p_data[i];
+                sumSampleSquares += samp * samp;
+            }
+            sumSampleSquares /= frameSize;
+            sumFrameSquares += sumSampleSquares;
+            double localRms = sqrt(sumSampleSquares);
+            sumFrameRms += localRms;
+        }
+        *rms = sumFrameRms / numFrames;
+        *stdRms = sqrt((sumFrameSquares / numFrames) - (*rms * *rms));
+        *duration = frameSize * numFrames / sampleRate;
+        return;
+    }
+
+    /* This handles the case where we look for a target signal against a
+       background, and expect the signal to start some time after the
+       beginning, and to finish some time before the end of the input
+       samples. */
+    if (numFrames < (3 * numCalFrames)) {
+        return;
+    }
+    float* calValues = new float[numCalFrames];
+    float calMedian = 0.0;
+    int onset = -1;
+    int offset = -1;
+
+    for (frame = 0; frame < numFrames; ++frame) {
+        short* p_data = pcm + (frame * frameSize);
+        int i;
+        for (i = 0, sumSampleSquares = 1.0; i < frameSize; ++i) {
+            float samp = p_data[i];
+            sumSampleSquares += samp * samp;
+        }
+        sumSampleSquares /= frameSize;
+        /* We handle three states: (1) before the onset of the signal; (2)
+           within the signal; (3) following the signal.  The signal is
+           assumed to be at least onsetThresh dB above the background
+           noise, and that at least one frame of silence/background
+           precedes the onset of the signal. */
+        if (onset < 0) { // (1)
+            sumFrameSquares += sumSampleSquares;
+            if (frame < numCalFrames ) {
+                calValues[frame] = sumSampleSquares;
+                continue;
+            }
+            if (frame == numCalFrames) {
+                calMedian = getMedian(calValues, numCalFrames);
+                if (calMedian < 10.0)
+                    calMedian = 10.0; // avoid divz, etc.
+            }
+            float ratio = 10.0 * log10(sumSampleSquares / calMedian);
+            if (ratio > onsetThresh) {
+                onset = frame;
+                sumFrameSquares = 1.0;
+                sumFrameRms = 1.0;
+            }
+            continue;
+        }
+        if ((onset > 0) && (offset < 0)) { // (2)
+            int sig_frame = frame - onset;
+            if (sig_frame < numCalFrames) {
+                calValues[sig_frame] = sumSampleSquares;
+            } else {
+                if (sig_frame == numCalFrames) {
+                    calMedian = getMedian(calValues, numCalFrames);
+                    if (calMedian < 10.0)
+                        calMedian = 10.0; // avoid divz, etc.
+                }
+                float ratio = 10.0 * log10(sumSampleSquares / calMedian);
+                int denFrames = frame - onset - 1;
+                if (ratio < (-onsetThresh)) { // found signal end
+                    *rms = sumFrameRms / denFrames;
+                    *stdRms = sqrt((sumFrameSquares / denFrames) - (*rms * *rms));
+                    *duration = frameSize * (frame - onset) / sampleRate;
+                    offset = frame;
+                    continue;
+                }
+            }
+            sumFrameSquares += sumSampleSquares;
+            double localRms = sqrt(sumSampleSquares);
+            sumFrameRms += localRms;
+            continue;
+        }
+        if (offset > 0) { // (3)
+            /* If we have found the real signal end, the level should stay
+               low till data end.  If not, flag this anomaly by increasing the
+               reported duration. */
+            float localRms = 1.0 + sqrt(sumSampleSquares);
+            float localSnr = 20.0 * log10(*rms / localRms);
+            if (localSnr < onsetThresh)
+                *duration = frameSize * (frame - onset) / sampleRate;
+            continue;
+        }
+    }
+    delete [] calValues;
+}
+
diff --git a/apps/CtsVerifier/jni/audioquality/MeasureRms.h b/apps/CtsVerifier/jni/audioquality/MeasureRms.h
new file mode 100644
index 0000000..78b43ae
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/MeasureRms.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef MEASURE_RMS_H
+#define MEASURE_RMS_H
+
+/* Measure the rms of the non-silence segment of the signal in pcm, which
+   is of numSamples length, and sampled at sampleRate.  pcm is assumed to
+   consist of silence - signal - silence, as might be logged during a
+   speech recognition attempt.  The stimulus signal in this case should
+   be approximately a 3-second burst of pink noise presented at a level
+   comparable to normal speaking level.  The RMS is measured using 25ms
+   duration non-overlapping windows.  These are averaged over the whole
+   non-silence part of pcm, and the result is returned in rms.  The
+   standard deviation of this measurement over all frames is returned in
+   stdRms, and the estimated duration of the non-silence region, in
+   seconds, is returned in duration.  The target signal is taken to be
+   that segment that is onsetThresh dB above the background, and is
+   expected to be continuous, once the onset has been found.  If
+   onsetThresh < 0.0, simply make the measurememt over the entire pcm
+   signal.  In both cases, the mean of the entire signal is returned in
+   mean. */
+void measureRms(short* pcm, int numSamples, float sampleRate,
+                float onsetThresh, float* rms, float* stdRms,
+                float* mean, float* duration);
+
+
+#endif /* MEASURE_RMS_H */
diff --git a/apps/CtsVerifier/jni/audioquality/OverflowCheck.cpp b/apps/CtsVerifier/jni/audioquality/OverflowCheck.cpp
new file mode 100644
index 0000000..20e9285
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/OverflowCheck.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+/* This assumes an input signal that has at least a few hundred
+   milliseconds of high-amplitude sinusoidal signal.  The signal is
+   expected to be a relatively low-frequency sinusoid (200-400 Hz).
+   If the signal is linearly reproduced or clipped, There will be no
+   large step changes in value from one sample to the next.  On the
+   other hand, if the signal contains numerical overflow
+   (wrap-around), very large excursions will be produced.
+
+   This program first searches for the high-amplitude recorded
+   segment, then examines just that part of the signal for "large
+   excursions", and returns the results of the search as four
+   integers:
+
+   n_wraps signal_size sine_start sine_end
+
+   where n_wraps is the number of anomolous value jumps found,
+   signal_size is the number of lin16 samples found in the file,
+   sine_start and sine_end are the limits of the region searched for
+   anomolous jumps. */
+
+#include <stdlib.h>
+#include <math.h>
+
+// MAX_ALLOWED_STEP is the largest sample-to-sample change that will
+// be considered "normal" for 250 Hz signals sampled at 8kHz.  This is
+// calibrated by the largest sample-to-sample change that is naturally
+// present in a 250 Hz sine wave with amplitude of 40000 (which is
+// actually 7804).
+#define MAX_ALLOWED_STEP 16000
+
+// This is the RMS value that is expected to be exceded by a sinusoid
+// with a peak amplitude of 32767 (actually 23169).
+#define SIGNAL_ON_RMS 12000.0
+
+static void findEndpoints(short* data, int n, int step, int* start, int* end) {
+    int size = step;
+    *start = *end = 0;
+    int last_frame = n - size;
+    for (int frame = 0; frame < last_frame; frame += step) {
+        double sum = 0.0;
+        for (int i=0; i < size; ++i) {
+            float val = data[i + frame];
+            sum += (val * val);
+        }
+        float rms = sqrt(sum / size);
+        if (! *start) {
+            if (rms >= SIGNAL_ON_RMS) {
+                *start = frame + size;
+            }
+            continue;
+        } else {
+            if (rms < SIGNAL_ON_RMS) {
+                *end = frame - size;
+                return;
+            }
+        }
+    }
+    if ((*start > 0) && (! *end)) {
+        *end = n - size - 1;
+    }
+}
+
+static void checkExcursions(short* data, int start, int end, int* numJumps,
+                            int* maxPeak, int* minPeak) {
+    *numJumps = 0;
+    int endm = end - 1;
+    if ((endm - start) < 3) {
+        *numJumps = -1;
+        return;
+    }
+    *maxPeak = *minPeak = data[start];
+    for (int i = start; i < endm; ++i) {
+        int v1 = data[i];
+        int v2 = data[i+1];
+        if (v1 > *maxPeak)
+            *maxPeak = v1;
+        if (v1 < *minPeak)
+            *minPeak = v1;
+        int diff = v2 - v1;
+        if (diff < 0)
+            diff = -diff;
+        if (diff > MAX_ALLOWED_STEP)
+            (*numJumps) += 1;
+    }
+    return;
+}
+
+int overflowCheck(short* pcm, int numSamples, float sampleRate,
+                  float* duration, int* numDeltas, int* onset, int* offset,
+                  int* maxPeak, int* minPeak) {
+    float windSize = 0.020;
+    int minBuff = int(2.0 * sampleRate); // must have 2 sec of data at least.
+
+    if(pcm && (numSamples >= minBuff)) {
+        int step = int(0.5 + (windSize * sampleRate));
+        *onset = 0;
+        *offset = 0;
+
+        findEndpoints(pcm, numSamples, step, onset, offset);
+        *numDeltas = -1;
+        checkExcursions(pcm, *onset, *offset, numDeltas, maxPeak, minPeak);
+        *duration = (*offset - *onset) / sampleRate;
+        return 1; // true/success
+    }
+    return 0; // failure
+}
diff --git a/apps/CtsVerifier/jni/audioquality/OverflowCheck.h b/apps/CtsVerifier/jni/audioquality/OverflowCheck.h
new file mode 100644
index 0000000..ea6d780
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/OverflowCheck.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#ifndef OVERFLOW_CHECK_H
+#define OVERFLOW_CHECK_H
+
+/* This is a clipping/overflow check.  This is designed to look for a
+   ~3-second duration ~250 Hz pure tone at a level sufficient to cause
+   about 20% clipping.  It examines the recorded waveform for possible
+   overflow/wraparound.  The input signal of numSamples total sampled at
+   sampleRate is in pcm. The expected signal contains silence - tone -
+   silence.  The approximate duration in seconds of the tone found is
+   returned in duration.  The number of unexpectedly-large jumps within
+   the tone is returned in numDeltas.  The approximate sample numbers of
+   the tone endpoints are returned in onset and offset.  The maximum
+   and minimum found in the signal located between onset and offset are
+   returned in maxPeak and minPeak, respectively.  The function
+   return is 1 if the operation was sucessful, 0 on failure. */
+int overflowCheck(short* pcm, int numSamples, float sampleRate,
+                  float* duration, int* numDeltas, int* onset, int* offset,
+                  int* maxPeak, int* minPeak);
+
+#endif // OVERFLOW_CHECK_H
diff --git a/apps/CtsVerifier/jni/audioquality/Window.cpp b/apps/CtsVerifier/jni/audioquality/Window.cpp
new file mode 100644
index 0000000..8e89ed4
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Window.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#include "Window.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+void Window::windowCreate(int size) {
+    if (mWindowWeights && (mWindowSize < size)) {
+        delete [] mWindowWeights;
+        mWindowWeights = NULL;
+        mWindowSize = 0;
+    }
+    if (!mWindowWeights) {
+        mWindowWeights = new float[size];
+    }
+    if (mWindowSize != size) {
+        double arg = M_PI * 2.0 / size;
+        mWindowSize = size;
+        for (int i = 0; i < size; ++i) {
+            mWindowWeights[i] = 0.5 - (0.5 * cos((i + 0.5) * arg));
+        }
+    }
+}
+
+void Window::windowCleanup() {
+    mWindowSize = 0;
+    delete [] mWindowWeights;
+    mWindowWeights = NULL;
+}
+
+/* Multiply the signal in data by the window weights.  Place the
+   resulting mWindowSize floating-point values in output.  If preemp
+   is != 0.0, apply a 1st-order preemphasis filter, and assume that
+   there are mWindowSize+1 samples available in data. */
+void Window::window(short* data, float* output, float preemp) {
+    if (preemp == 0.0) {
+        for (int i = 0; i < mWindowSize; ++i)
+            output[i] = data[i] * mWindowWeights[i];
+    } else {
+        for (int i = 0; i < mWindowSize; ++i)
+            output[i] = (data[i+1] - (preemp * data[i])) * mWindowWeights[i];
+    }
+}
+
diff --git a/apps/CtsVerifier/jni/audioquality/Window.h b/apps/CtsVerifier/jni/audioquality/Window.h
new file mode 100644
index 0000000..08298354
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Window.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef WINDOW_INTERFACE_H
+#define WINDOW_INTERFACE_H
+
+class Window {
+public:
+    Window(int size) : mWindowWeights(0), mWindowSize(0) {
+        windowCreate(size);
+    }
+
+    Window() : mWindowWeights(0), mWindowSize(0) {
+    }
+
+    virtual ~Window(void) {
+        windowCleanup();
+    }
+
+    /* Create a Hann window of length size.  This allocates memory that
+       should be freed using window_cleanup(). */
+    void windowCreate(int size);
+
+    /* Free up memory and reset size to 0. */
+    void windowCleanup(void);
+
+
+    /* Multiply the signal in data by the window weights.  Place the
+       resulting window_size floating-point values in output.  If preemp
+       is != 0.0, apply a 1st-order preemphasis filter, and assume that
+       there are window_size+1 samples available in data. */
+    void window(short* data, float* output, float preemp);
+
+    int getWindowSize() { return mWindowSize; }
+
+    float* getWindowWeights() { return mWindowWeights; }
+
+private:
+    float* mWindowWeights;
+    int mWindowSize;
+
+};
+
+#endif /* WINDOW_INTERFACE_H */
diff --git a/apps/CtsVerifier/jni/audioquality/Wrapper.cpp b/apps/CtsVerifier/jni/audioquality/Wrapper.cpp
new file mode 100644
index 0000000..bee15c6
--- /dev/null
+++ b/apps/CtsVerifier/jni/audioquality/Wrapper.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+// Wrapper to the native phone test signal processing library, which
+// exposes an interface suitable for calling via JNI.
+
+#include <stdlib.h>
+#include <jni.h>
+
+#include "GenerateSinusoid.h"
+#include "MeasureRms.h"
+#include "GlitchTest.h"
+#include "OverflowCheck.h"
+#include "CompareSpectra.h"
+#include "LinearityTest.h"
+
+typedef short *shortPtr;
+
+extern "C" {
+    JNIEXPORT jshortArray JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_generateSinusoid(
+            JNIEnv *env, jobject obj,
+            jfloat freq, jfloat duration,
+            jfloat sampleRate, jfloat amplitude, jfloat ramp);
+    JNIEXPORT jfloatArray JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_measureRms(
+            JNIEnv *env, jobject obj,
+            jshortArray jpcm, jfloat sampleRate, jfloat onsetThresh);
+    JNIEXPORT jfloatArray JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_glitchTest(
+            JNIEnv *env, jobject obj,
+            jfloat sampleRate, jfloat stimFreq, jfloat onsetThresh,
+            jfloat dbSnrThresh, jshortArray jpcm);
+    JNIEXPORT jfloatArray JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_overflowCheck(
+            JNIEnv *env, jobject obj,
+            jshortArray jpcm, jfloat sampleRate);
+    JNIEXPORT jfloatArray JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_compareSpectra(
+            JNIEnv *env, jobject obj,
+            jshortArray jpcm, jshortArray jrefPcm, jfloat sampleRate);
+    JNIEXPORT jfloat JNICALL
+        Java_com_android_cts_verifier_audioquality_Native_linearityTest(
+            JNIEnv *env, jobject obj,
+            jobjectArray jpcms,
+            jfloat sampleRate, jfloat dbStepSize, jint referenceStim);
+};
+
+/* Returns an array of sinusoidal samples.
+   If the arguments are invalid, returns an empty array. */
+JNIEXPORT jshortArray JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_generateSinusoid(
+        JNIEnv *env, jobject obj,
+        jfloat freq, jfloat duration,
+        jfloat sampleRate, jfloat amplitude, jfloat ramp) {
+    short *wave = NULL;
+    int numSamples = 0;
+
+    generateSinusoid(freq, duration, sampleRate, amplitude, ramp,
+            &numSamples, &wave);
+
+    jshortArray ja;
+    if (!numSamples) {
+        ja = env->NewShortArray(0);
+    } else {
+        ja = env->NewShortArray(numSamples);
+        env->SetShortArrayRegion(ja, 0, numSamples, wave);
+        delete[] wave;
+    }
+    return ja;
+}
+
+/* Returns an array of four floats.
+   ret[0] = RMS
+   ret[1] = standard deviation of the RMS
+   ret[2] = non-silent region duration
+   ret[3] = mean value
+*/
+JNIEXPORT jfloatArray JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_measureRms(
+        JNIEnv *env, jobject obj,
+        jshortArray jpcm, jfloat sampleRate, jfloat onsetThresh) {
+    float ret[4];
+    ret[0] = ret[1] = ret[2] = ret[3] = -1.0;
+    int numSamples = env->GetArrayLength(jpcm);
+    short *pcm = new short[numSamples];
+    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);
+
+    measureRms(pcm, numSamples, sampleRate, onsetThresh, ret, ret + 1,
+            ret + 3, ret + 2);
+
+    jfloatArray ja = env->NewFloatArray(4);
+    env->SetFloatArrayRegion(ja, 0, 4, ret);
+    return ja;
+}
+
+/* Returns an array of three floats.
+   ret[0] = #bad frames
+   ret[1] = error code
+   ret[2] = duration
+   Error code = 1 for success,
+               -1 if initialization failed,
+               -2 if insufficient samples
+               -3 if tone signal onset not found
+               -4 if tone signal end not found
+*/
+JNIEXPORT jfloatArray JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_glitchTest(
+        JNIEnv *env, jobject obj,
+        jfloat sampleRate, jfloat stimFreq, jfloat onsetThresh,
+        jfloat dbSnrThresh, jshortArray jpcm) {
+    float ret[3];
+    int numSamples = env->GetArrayLength(jpcm);
+    short *pcm = new short[numSamples];
+    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);
+
+    GlitchTest gt;
+    gt.init(sampleRate, stimFreq, onsetThresh, dbSnrThresh);
+    float duration = -1.0;
+    int badFrames = -1;
+    int success = gt.checkToneSnr(pcm, numSamples, &duration, &badFrames);
+    ret[0] = badFrames;
+    ret[1] = success;
+    ret[2] = duration;
+    jfloatArray ja = env->NewFloatArray(3);
+    env->SetFloatArrayRegion(ja, 0, 3, ret);
+    return ja;
+}
+
+/* Returns an array of seven floats.
+   ret[0] = num deltas
+   ret[1] = error code
+   ret[2] = duration
+   ret[3] = onset
+   ret[4] = offset
+   ret[5] = max peak
+   ret[6] = min peak
+   Error code = 1 for success, -1 for failure. */
+JNIEXPORT jfloatArray JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_overflowCheck(
+        JNIEnv *env, jobject obj,
+        jshortArray jpcm, jfloat sampleRate) {
+    float ret[7];
+    int numSamples = env->GetArrayLength(jpcm);
+    short *pcm = new short[numSamples];
+    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);
+
+    float duration = -1.0;
+    int numDeltas = -1, onset = -1, offset = -1;
+    int maxPeak = 0, minPeak = 0;
+    int success = overflowCheck(pcm, numSamples, sampleRate,
+            &duration, &numDeltas, &onset, &offset, &maxPeak, &minPeak);
+    ret[0] = numDeltas;
+    ret[1] = success ? 1 : -1;
+    ret[2] = duration;
+    ret[3] = onset;
+    ret[4] = offset;
+    ret[5] = maxPeak;
+    ret[6] = minPeak;
+    jfloatArray ja = env->NewFloatArray(7);
+    env->SetFloatArrayRegion(ja, 0, 7, ret);
+    return ja;
+}
+
+/* Returns an array of three floats.
+   ret[0] = max deviation,
+   ret[1] = error code,
+   ret[2] = rms deviation.
+   Error code = 1 for success, -1 for failure. */
+JNIEXPORT jfloatArray JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_compareSpectra(
+        JNIEnv *env, jobject obj,
+        jshortArray jpcm, jshortArray jrefPcm, jfloat sampleRate) {
+    float ret[3];
+    int numSamples = env->GetArrayLength(jpcm);
+    short *pcm = new short[numSamples];
+    env->GetShortArrayRegion(jpcm, 0, numSamples, pcm);
+    int nRefSamples = env->GetArrayLength(jrefPcm);
+    short *refPcm = new short[nRefSamples];
+    env->GetShortArrayRegion(jrefPcm, 0, nRefSamples, refPcm);
+
+    float maxDeviation = -1.0, rmsDeviation = -1.0;
+    int success = compareSpectra(pcm, numSamples, refPcm, nRefSamples,
+            sampleRate, &maxDeviation, &rmsDeviation);
+    ret[1] = success ? 1 : -1;
+
+    ret[0] = maxDeviation;
+    ret[2] = rmsDeviation;
+    jfloatArray ja = env->NewFloatArray(3);
+    env->SetFloatArrayRegion(ja, 0, 3, ret);
+    return ja;
+}
+
+/* Return maximum deviation from linearity in dB.
+   On failure returns:
+      -1.0 The input signals or sample counts are missing.
+      -2.0 The number of input signals is < 2.
+      -3.0 The specified sample rate is <= 4000.0
+      -4.0 The dB step size for the increase in stimulus level is <= 0.0
+      -5.0 The specified reverence stimulus number is out of range.
+      -6.0 One or more of the stimuli is too short in duration.
+*/
+JNIEXPORT jfloat JNICALL
+    Java_com_android_cts_verifier_audioquality_Native_linearityTest(
+        JNIEnv *env, jobject obj,
+        jobjectArray jpcms,
+        jfloat sampleRate, jfloat dbStepSize, jint referenceStim) {
+    int numSignals = env->GetArrayLength(jpcms);
+    int *sampleCounts = new int[numSignals];
+    short **pcms = new shortPtr[numSignals];
+    jshortArray ja;
+    for (int i = 0; i < numSignals; i++) {
+        ja = (jshortArray) env->GetObjectArrayElement(jpcms, i);
+        sampleCounts[i] = env->GetArrayLength(ja);
+        pcms[i] = new short[sampleCounts[i]];
+        env->GetShortArrayRegion(ja, 0, sampleCounts[i], pcms[i]);
+    }
+
+    float maxDeviation = -1.0;
+    int ret = linearityTest(pcms, sampleCounts, numSignals,
+            sampleRate, dbStepSize, referenceStim, &maxDeviation);
+    delete[] sampleCounts;
+    for (int i = 0; i < numSignals; i++) {
+        delete[] pcms[i];
+    }
+    delete[] pcms;
+    if (ret < 1) return ret;
+
+    return maxDeviation;
+}
diff --git a/apps/CtsVerifier/jni/verifier/Android.mk b/apps/CtsVerifier/jni/verifier/Android.mk
new file mode 100644
index 0000000..98a4678
--- /dev/null
+++ b/apps/CtsVerifier/jni/verifier/Android.mk
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libctsverifier_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_SRC_FILES := \
+		CtsVerifierJniOnLoad.cpp \
+		com_android_cts_verifier_os_FileUtils.cpp	
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/CtsVerifier/jni/CtsVerifierJniOnLoad.cpp b/apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
similarity index 100%
rename from apps/CtsVerifier/jni/CtsVerifierJniOnLoad.cpp
rename to apps/CtsVerifier/jni/verifier/CtsVerifierJniOnLoad.cpp
diff --git a/apps/CtsVerifier/jni/com_android_cts_verifier_os_FileUtils.cpp b/apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
similarity index 100%
rename from apps/CtsVerifier/jni/com_android_cts_verifier_os_FileUtils.cpp
rename to apps/CtsVerifier/jni/verifier/com_android_cts_verifier_os_FileUtils.cpp
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_error.png b/apps/CtsVerifier/res/drawable-hdpi/fs_error.png
new file mode 100644
index 0000000..8270104
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_error.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_good.png b/apps/CtsVerifier/res/drawable-hdpi/fs_good.png
new file mode 100644
index 0000000..7786ac7
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_good.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_indeterminate.png b/apps/CtsVerifier/res/drawable-hdpi/fs_indeterminate.png
new file mode 100644
index 0000000..68f51ba
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_indeterminate.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_warning.png b/apps/CtsVerifier/res/drawable-hdpi/fs_warning.png
new file mode 100644
index 0000000..12baca5
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_warning.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout/aq_row.xml b/apps/CtsVerifier/res/layout/aq_row.xml
new file mode 100644
index 0000000..5db33f3
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/aq_row.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:background="#FF000000"
+    >
+    <TextView
+        android:id="@+id/testName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="22px"
+        android:textColor="#FFFFFF"
+        android:layout_margin="10px"
+        />
+    <TextView
+        android:id="@+id/testScore"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="right"
+        android:textSize="22px"
+        android:textColor="#FFFFFF"
+        android:layout_margin="10px"
+        />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/aq_sound_level_meter.xml b/apps/CtsVerifier/res/layout/aq_sound_level_meter.xml
new file mode 100644
index 0000000..c3cc8f9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/aq_sound_level_meter.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#FF000000"
+    >
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20px"
+        android:gravity="center_horizontal"
+        android:textSize="18px"
+        android:textColor="#FFFFFF"
+        android:text="@string/aq_calibrate_volume_instructions"
+        />
+    <ProgressBar
+        android:id="@+id/slider"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:max="2000"
+        android:progress="1000"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:padding="10px"
+        />
+    <TextView
+        android:id="@+id/status"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:textSize="18px"
+        android:textColor="#FFFFFF"
+        />
+    <Button
+        android:id="@+id/doneButton"
+        android:text="@string/aq_done"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20px"
+        />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/aq_verifier_activity.xml b/apps/CtsVerifier/res/layout/aq_verifier_activity.xml
new file mode 100644
index 0000000..12703ee
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/aq_verifier_activity.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#FF000000"
+    >
+    <FrameLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1.0"
+        >
+        <ListView
+            android:id="@+id/list"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            />
+        <ProgressBar
+            android:id="@+id/progress"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            android:layout_gravity="center"
+            android:visibility="invisible"
+            />
+    </FrameLayout>
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10px"
+        android:layout_marginBottom="10px"
+        >
+        <Button
+            android:id="@+id/calibrateButton"
+            android:text="@string/aq_calibrate"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+        <Button
+            android:id="@+id/runAllButton"
+            android:text="@string/aq_run_all"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+        <Button
+            android:id="@+id/stopButton"
+            android:text="@string/aq_stop"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+        <Button
+            android:id="@+id/viewResultsButton"
+            android:text="@string/aq_view_results"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+        <Button
+            android:id="@+id/clearButton"
+            android:text="@string/aq_clear"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/aq_view_results.xml b/apps/CtsVerifier/res/layout/aq_view_results.xml
new file mode 100644
index 0000000..b8f020d0
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/aq_view_results.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:gravity="center_horizontal"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:background="#FF000000"
+    >
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1.0"
+        >
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:textSize="18px"
+            android:textColor="#FFFFFF"
+            />
+    </ScrollView>
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10px"
+        android:layout_marginBottom="10px"
+        >
+        <Button
+            android:id="@+id/dismissButton"
+            android:text="@string/aq_dismiss"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+        <Button
+            android:id="@+id/sendResultsButton"
+            android:text="@string/aq_email_results"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+    </LinearLayout>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index c386a1b..987bef7 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -31,6 +31,7 @@
 
     <!-- Strings for TestListActivity -->
     <string name="test_list_title">Manual Test List</string>
+    <string name="test_category_audio">Audio</string>
     <string name="test_category_sensors">Sensors</string>
     <string name="test_category_security">Security</string>
     <string name="test_category_features">Features</string>
@@ -67,4 +68,82 @@
     <string name="no_file_status">Could not stat file...</string>
     <string name="congratulations">Congratulations!</string>
     <string name="no_suid_files">No unauthorized suid files detected!</string>
+
+    <!--  Strings for Audio Quality Verifier -->
+    
+     <!-- Title for Audio Quality Verifier activity -->
+    <string name="aq_verifier">Audio Quality Verifier</string>
+    <string name="aq_verifier_info">
+        1. Click \"Calibrate\". Position the phone in front of the center of
+           the speaker cone with the microphone facing the speaker,
+           and adjust the volume of the speaker until the status message
+           indicates it is correct.
+        \n\n2. Click on any test in the list to run it, or \"Run All\" to run
+           each test in sequence.
+        \n\nIf the sound level check fails, go back to the calibration step before
+            running any other test.
+        \n\n3. Click \"Results\" to view the outcomes. A correctly functioning
+           device should pass all tests.
+        \n\n4. Click \"Send by email\" from the results page to send the
+           results to an e-mail address of your choice. The recordings
+           made are also attached as raw 16 bit, 16 kHz audio files to
+           help you diagnose any failed tests.
+    </string>
+    
+    <!-- Button labels for VerifierActivity -->
+    <string name="aq_calibrate">Calibrate</string>
+    <string name="aq_run_all">Run All</string>
+    <string name="aq_stop">Stop</string>
+    <string name="aq_view_results">Results</string>
+    <string name="aq_email_results">Send by email</string>
+    <string name="aq_clear">Clear</string>
+    
+    <!-- Title for ViewResultsActivity -->
+    <string name="aq_view_results_name">Audio Quality Results</string>
+    <!-- Button label for ViewResultsActivity -->
+    <string name="aq_dismiss">Dismiss</string>
+    <!-- E-mail subject line for test results -->
+    <string name="aq_subject">Android Audio Quality Verifier Test Results</string>
+    
+    <!--  Title for CalibrateVolumeActivity -->
+    <string name="aq_calibrate_volume_name">Calibrate Volume</string>
+    <!--  Instructions for calibrating the volume -->
+    <string name="aq_calibrate_volume_instructions">Adjust volume to the central point</string>
+    <!-- Button label for CalibrateVolumeActivity -->
+    <string name="aq_done">Done</string>
+    <!--  Status values for CalibrateVolumeActivity -->
+    <string name="aq_status_unknown">Status: unknown</string>
+    <string name="aq_status_low">Volume too low</string>
+    <string name="aq_status_high">Volume too high</string>
+    <string name="aq_status_ok">Volume OK</string>
+    
+    <!-- Experiment names -->
+    <string name="aq_default_exp">Unnamed experiment</string>
+    <string name="aq_sound_level_exp">Sound level check</string>
+    <string name="aq_spectrum_shape_exp">Spectrum shape test</string>
+    <string name="aq_glitch_exp">Glitch test</string>
+    <string name="aq_linearity_exp">Gain linearity test</string>
+    <string name="aq_overflow_exp">Overflow check</string>
+    <string name="aq_bias_exp">Bias measurement</string>
+    
+    <!-- Experiment outcomes -->
+    <string name="aq_fail">Fail</string>
+    <string name="aq_pass">Pass</string>
+    <string name="aq_complete">Complete</string>
+    
+    <!-- Experiment reports -->
+    <string name="aq_loopback_report">Experiment ran successfully.</string>
+    <string name="aq_bias_report">Mean = %1$.3g, tolerance = +/- %2$.0f\nRMS = %3$.0f, duration = %4$.1fs</string>
+    <string name="aq_overflow_report_error">Overflow check unsuccessful</string>
+    <string name="aq_overflow_report_short">Insufficient tone detected.\nExpected %1$.1fs tone; observed %2$.1fs</string>
+    <string name="aq_overflow_report_fail">"Overflow check failed due to discontinuities.\nObserved %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
+    <string name="aq_overflow_report_pass">"Observed %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
+    <string name="aq_linearity_report_error">Experiment failed, error code %1$g</string>
+    <string name="aq_linearity_report_normal">Deviation from linearity = %1$.3g dB\nMax allowed = %2$.1f dB</string>
+    <string name="aq_glitch_report_error">Error performing Glitch test.</string>
+    <string name="aq_glitch_report_exact">%1$d glitches detected; expected %2$d, duration %3$.1fs</string>
+    <string name="aq_glitch_report_range">%1$d glitches detected; expected %2$d-%3$d, duration %4$.1fs</string>
+    <string name="aq_level_report">RMS = %1$.0f, target = %2$.0f\nTolerance = %3$.1f%%\nDuration = %4$.1fs</string>
+    <string name="aq_spectrum_report_error">Cannot perform test.\nCheck volume is sufficiently high?</string>
+    <string name="aq_spectrum_report_normal">RMS deviation = %1$.2f\nMax allowed deviation = %2$.1f</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioAssets.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioAssets.java
new file mode 100644
index 0000000..9725a4c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioAssets.java
@@ -0,0 +1,52 @@
+/*
+ * 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.verifier.audioquality;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AudioAssets {
+
+    private static final String TAG = "AudioQualityVerifier";
+
+    private static final String STIM_BASENAME = "audioquality/stim_dt_";
+
+    public static byte[] getStim(Context context, int which) {
+        return readAsset(context, STIM_BASENAME + which);
+    }
+
+    public static byte[] getPinkNoise(Context context, int ampl, int duration) {
+        return readAsset(context, "pink_" + ampl + "_" + duration + "s");
+    }
+
+    private static byte[] readAsset(Context context, String filename) {
+        AssetManager assetManager = context.getAssets();
+        InputStream ais;
+        try {
+            ais = assetManager.open(filename);
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot load asset " + filename);
+            return null;
+        }
+        byte[] buffer = Utils.readFile(ais);
+        return buffer;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java
new file mode 100644
index 0000000..ce9c165
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java
@@ -0,0 +1,278 @@
+/*
+ * 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.verifier.audioquality;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import java.util.ArrayList;
+
+/**
+ * Main UI for the Android Audio Quality Verifier.
+ */
+public class AudioQualityVerifierActivity extends PassFailButtons.Activity implements View.OnClickListener,
+        OnItemClickListener {
+    public static final String TAG = "AudioQualityVerifier";
+
+    public static final int SAMPLE_RATE = 16000;
+    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+    public static final int BYTES_PER_SAMPLE = 2;
+    public static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
+
+    // Intent Extra definitions, which must match those in
+    // com.google.android.voicesearch.speechservice.RecognitionController
+    public static final String EXTRA_RAW_AUDIO =
+            "android.speech.extras.RAW_AUDIO";
+
+    // Communication with ExperimentService
+    public static final String ACTION_EXP_STARTED =
+            "com.android.cts.verifier.audioquality.EXP_STARTED";
+    public static final String ACTION_EXP_FINISHED =
+            "com.android.cts.verifier.audioquality.EXP_FINISHED";
+    public static final String EXTRA_ABORTED =
+            "com.android.cts.verifier.audioquality.ABORTED";
+    public static final String EXTRA_EXP_ID =
+            "com.android.cts.verifier.audioquality.EXP_ID";
+    public static final String EXTRA_RUN_ALL =
+            "com.android.cts.verifier.audioquality.RUN_ALL";
+
+    // Communication with ViewResultsActivity
+    public static final String EXTRA_RESULTS =
+            "com.android.cts.verifier.audioquality.RESULTS";
+
+    private Button mCalibrateButton;
+    private Button mRunAllButton;
+    private Button mStopButton;
+    private Button mViewResultsButton;
+    private Button mClearButton;
+
+    private ListView mList;
+    private TwoColumnAdapter mAdapter;
+
+    private ProgressBar mProgress;
+
+    private ArrayList<Experiment> mExperiments;
+
+    private boolean mRunningExperiment;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.aq_verifier_activity);
+        setInfoResources(R.string.aq_verifier, R.string.aq_verifier_info, -1);
+        setResult(RESULT_CANCELED);
+
+        mCalibrateButton = (Button) findViewById(R.id.calibrateButton);
+        mRunAllButton = (Button) findViewById(R.id.runAllButton);
+        mStopButton = (Button) findViewById(R.id.stopButton);
+        mViewResultsButton = (Button) findViewById(R.id.viewResultsButton);
+        mClearButton = (Button) findViewById(R.id.clearButton);
+
+        mCalibrateButton.setOnClickListener(this);
+        mRunAllButton.setOnClickListener(this);
+        mStopButton.setOnClickListener(this);
+        mViewResultsButton.setOnClickListener(this);
+        mClearButton.setOnClickListener(this);
+
+        mStopButton.setEnabled(false);
+
+        mProgress = (ProgressBar) findViewById(R.id.progress);
+
+        mList = (ListView) findViewById(R.id.list);
+        mAdapter = new TwoColumnAdapter(this);
+
+        mExperiments = VerifierExperiments.getExperiments(this);
+
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                experimentReplied(intent);
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_EXP_STARTED);
+        filter.addAction(ACTION_EXP_FINISHED);
+        registerReceiver(receiver, filter);
+
+        fillAdapter();
+        mList.setAdapter(mAdapter);
+        mList.setOnItemClickListener(this);
+        checkNotSilent();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mAdapter.notifyDataSetChanged(); // Update List UI
+        checkNotSilent();
+    }
+
+    private void checkNotSilent() {
+        AudioManager mgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mgr.setStreamMute(PLAYBACK_STREAM, false);
+        int volume = mgr.getStreamVolume(PLAYBACK_STREAM);
+        int max = mgr.getStreamMaxVolume(PLAYBACK_STREAM);
+        Log.i(TAG, "Volume " + volume + ", max " + max);
+        if (volume <= max / 10) {
+            // Volume level is silent or very quiet; increase to two-thirds
+            mgr.setStreamVolume(PLAYBACK_STREAM, (max * 2) / 3, AudioManager.FLAG_SHOW_UI);
+        }
+    }
+
+    // Called when an experiment has completed
+    private void experimentReplied(Intent intent) {
+        String action = intent.getAction();
+        if (ACTION_EXP_STARTED.equals(action)) {
+            mStopButton.setEnabled(true);
+            mRunAllButton.setEnabled(false);
+        } else if (ACTION_EXP_FINISHED.equals(action)) {
+            boolean mRunAll = intent.getBooleanExtra(EXTRA_RUN_ALL, false);
+            boolean aborted = intent.getBooleanExtra(EXTRA_ABORTED, true);
+            int expID = intent.getIntExtra(EXTRA_EXP_ID, -1);
+            if (mRunAll && !aborted) {
+                while (expID < mExperiments.size() - 1) {
+                    if (runExperiment(++expID, true)) {
+                        // OK, experiment is running
+                        mAdapter.notifyDataSetChanged();
+                        return;
+                    }
+                    // Otherwise, loop back and try the next experiment
+                }
+            }
+            mStopButton.setEnabled(false);
+            mRunAllButton.setEnabled(true);
+            mRunningExperiment = false;
+            mProgress.setVisibility(ProgressBar.INVISIBLE);
+        }
+        mAdapter.notifyDataSetChanged();
+    }
+
+    // Implements AdapterView.OnItemClickListener
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        if (mRunningExperiment) return;
+        runExperiment(position, false);
+    }
+
+    // Begin an experiment. Returns false if the experiment is not enabled.
+    private boolean runExperiment(int which, boolean all) {
+        Experiment exp = mExperiments.get(which);
+        if (!exp.isEnabled()) return false;
+        Intent intent = new Intent(this, ExperimentService.class);
+        intent.putExtra(EXTRA_EXP_ID, which);
+        intent.putExtra(EXTRA_RUN_ALL, all);
+        startService(intent);
+        mRunningExperiment = true;
+        mAdapter.notifyDataSetChanged();
+        mProgress.setVisibility(ProgressBar.VISIBLE);
+        return true;
+    }
+
+    // Implements View.OnClickListener:
+    public void onClick(View v) {
+        if (v == mCalibrateButton) {
+            Intent intent = new Intent(this, CalibrateVolumeActivity.class);
+            startActivity(intent);
+        } else if (v == mRunAllButton) {
+            if (mRunningExperiment) return;
+            int expID = -1;
+            while (expID < mExperiments.size() - 1) {
+                if (runExperiment(++expID, true)) break;
+            }
+        } else if (v == mStopButton) {
+            Intent intent = new Intent(this, ExperimentService.class);
+            stopService(intent);
+        } else if (v == mViewResultsButton) {
+            Intent intent = new Intent(this, ViewResultsActivity.class);
+            intent.putExtra(EXTRA_RESULTS, genReport());
+            startActivity(intent);
+        } else if (v == mClearButton) {
+            clear();
+        }
+    }
+
+    private void fillAdapter() {
+        mAdapter.clear();
+        for (Experiment exp : mExperiments) {
+            mAdapter.add(exp.getName());
+        }
+    }
+
+    class TwoColumnAdapter extends ArrayAdapter<String> {
+        TwoColumnAdapter(Context context) {
+            super(context, R.layout.aq_row);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            LayoutInflater inflater = getLayoutInflater();
+            View row = inflater.inflate(R.layout.aq_row, parent, false);
+            TextView nameField = (TextView) row.findViewById(R.id.testName);
+            TextView scoreField = (TextView) row.findViewById(R.id.testScore);
+            Experiment exp = mExperiments.get(position);
+            nameField.setText(exp.getName());
+            scoreField.setText(exp.getScore());
+            if (exp.isRunning()) {
+                Typeface tf = nameField.getTypeface();
+                nameField.setTypeface(tf, 1);
+            }
+            if (!exp.isEnabled()) {
+                nameField.setTextColor(Color.GRAY);
+            }
+            return row;
+        }
+    }
+
+    private String genReport() {
+        StringBuilder sb = new StringBuilder();
+        for (Experiment exp : mExperiments) {
+            exp.getReport(sb);
+        }
+        return sb.toString();
+    }
+
+    private void clear() {
+        if (mRunningExperiment) {
+            Intent intent = new Intent(this, ExperimentService.class);
+            stopService(intent);
+        }
+        for (Experiment exp : mExperiments) {
+            exp.reset();
+        }
+        mAdapter.notifyDataSetChanged();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java
new file mode 100644
index 0000000..e22d596
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java
@@ -0,0 +1,98 @@
+/*
+ * 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.verifier.audioquality;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+/**
+ * Continuously play background noise in a loop, until halt() is called.
+ * Used to simulate noisy environments, and for sound level calibration.
+ */
+public class BackgroundAudio extends Thread {
+    public static final String TAG = "AudioQualityVerifier";
+
+    private static final int BUFFER_TIME = 100; // Time in ms to buffer for
+
+    private boolean mProceed;
+    private AudioTrack mAudioTrack;
+    private byte[] mData;
+    private int mPos;
+    private int mBufferSize;
+
+    public void halt() {
+        mProceed = false;
+    }
+
+    public BackgroundAudio(byte[] data) {
+        mProceed = true;
+        mData = data;
+        mPos = 0;
+
+        // Calculate suitable buffer size:
+        final int minBufferSize = (BUFFER_TIME * AudioQualityVerifierActivity.SAMPLE_RATE
+                * AudioQualityVerifierActivity.BYTES_PER_SAMPLE) / 1000;
+        final int minHardwareBufferSize =
+                AudioTrack.getMinBufferSize(AudioQualityVerifierActivity.SAMPLE_RATE,
+                        AudioFormat.CHANNEL_OUT_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT);
+        mBufferSize = Math.max(minHardwareBufferSize, minBufferSize);
+        Log.i(TAG, "minBufferSize = " + minBufferSize + ", minHWSize = " + minHardwareBufferSize
+                + ", bufferSize = " + mBufferSize);
+
+        // Start playback:
+        Log.i(TAG, "Looping " + data.length + " bytes of audio, buffer size " + mBufferSize);
+        mAudioTrack = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM,
+                AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO,
+                AudioQualityVerifierActivity.AUDIO_FORMAT, mBufferSize, AudioTrack.MODE_STREAM);
+        if (mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
+            writeAudio();
+            start(); // Begin background thread to push audio data
+            mAudioTrack.play();
+        } else {
+            Log.e(TAG, "Error initializing audio track.");
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            if (!mProceed) {
+                mAudioTrack.stop();
+                return; // End thread
+            }
+            writeAudio();
+        }
+    }
+
+    private void writeAudio() {
+        int len = mData.length;
+        int count;
+        int maxBytes = Math.min(mBufferSize, len - mPos);
+
+        count = mAudioTrack.write(mData, mPos, maxBytes);
+        if (count < 0) {
+            Log.e(TAG, "Error writing looped audio data");
+            halt();
+            return;
+        }
+        mPos += count;
+        if (mPos == len) {
+            mPos = 0; // Wraparound
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/CalibrateVolumeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/CalibrateVolumeActivity.java
new file mode 100644
index 0000000..c6cf34a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/CalibrateVolumeActivity.java
@@ -0,0 +1,234 @@
+/*
+ * 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.verifier.audioquality;
+
+import com.android.cts.verifier.R;
+
+import android.app.Activity;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Play a continuous sound and allow the user to monitor the sound level
+ * at the mic in real time, relative to the range the phone can detect.
+ * This is not an absolute sound level meter, but lets the speaker volume
+ * and position be adjusted so that the clipping point is known.
+ */
+public class CalibrateVolumeActivity extends Activity implements View.OnClickListener {
+    public static final String TAG = "AudioQualityVerifier";
+
+    public static final int OUTPUT_AMPL = 5000;
+    public static final float TARGET_RMS = 5000.0f;
+    public static final float TARGET_AMPL = (float) (TARGET_RMS * Math.sqrt(2.0));
+    private static final float FREQ = 625.0f;
+
+    private static final float TOLERANCE = 1.03f;
+
+    private static final int DEBOUNCE_TIME = 500; // Minimum time in ms between status text changes
+    public static final boolean USE_PINK = false;
+
+    private ProgressBar mSlider;
+    private Button mDoneButton;
+    private TextView mStatus;
+    private BackgroundAudio mBackgroundAudio;
+    private Monitor mMonitor;
+    private Handler mHandler;
+
+    private Native mNative;
+
+    enum Status { LOW, OK, HIGH, UNDEFINED }
+    private static int[] mStatusText = {
+        R.string.aq_status_low, R.string.aq_status_ok, R.string.aq_status_high };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.aq_sound_level_meter);
+
+        mSlider = (ProgressBar) findViewById(R.id.slider);
+        mStatus = (TextView) findViewById(R.id.status);
+        mStatus.setText(R.string.aq_status_unknown);
+
+        mDoneButton = (Button) findViewById(R.id.doneButton);
+        mDoneButton.setOnClickListener(this);
+
+        mNative = Native.getInstance();
+        mHandler = new UpdateHandler();
+    }
+
+    // Implements View.OnClickListener
+    public void onClick(View v) {
+        if (v == mDoneButton) {
+            finish();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        final int DURATION = 1;
+        final float RAMP = 0.0f;
+        byte[] noise;
+        if (USE_PINK) {
+            noise = Utils.getPinkNoise(this, OUTPUT_AMPL, DURATION);
+        } else {
+            short[] sinusoid = mNative.generateSinusoid(FREQ, DURATION,
+                    AudioQualityVerifierActivity.SAMPLE_RATE, OUTPUT_AMPL, RAMP);
+            noise = Utils.shortToByteArray(sinusoid);
+        }
+        float[] results = mNative.measureRms(Utils.byteToShortArray(noise),
+                AudioQualityVerifierActivity.SAMPLE_RATE, -1.0f);
+        float rms = results[Native.MEASURE_RMS_RMS];
+        Log.i(TAG, "Stimulus amplitude " + OUTPUT_AMPL + ", RMS " + rms);
+        mBackgroundAudio = new BackgroundAudio(noise);
+        mMonitor = new Monitor();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mBackgroundAudio.halt();
+        mMonitor.halt();
+    }
+
+    private class UpdateHandler extends Handler {
+        private Status mState = Status.UNDEFINED;
+        private long mTimestamp = 0; // Time of last status change in ms
+
+        @Override
+        public void handleMessage(Message msg) {
+            int rms = msg.arg1;
+            int max = mSlider.getMax();
+            int progress = (max / 2 * rms) / Math.round(TARGET_RMS);
+            if (progress > max) progress = max;
+            mSlider.setProgress(progress);
+
+            Status state;
+            if (rms * TOLERANCE < TARGET_RMS) state = Status.LOW;
+            else if (rms > TARGET_RMS * TOLERANCE) state = Status.HIGH;
+            else state = Status.OK;
+            if (state != mState) {
+                long timestamp = System.currentTimeMillis();
+                if (timestamp - mTimestamp > DEBOUNCE_TIME) {
+                    mStatus.setText(mStatusText[state.ordinal()]);
+                    mState = state;
+                    mTimestamp = timestamp;
+                }
+            }
+        }
+    }
+
+    class Monitor extends Thread {
+        private static final int BUFFER_TIME = 100; // Min time in ms to buffer for
+        private static final int READ_TIME = 25;    // Max length of time in ms to read in one chunk
+        private static final boolean DEBUG = false;
+
+        private AudioRecord mRecord;
+        private int mSamplesToRead;
+        private byte[] mBuffer;
+        private boolean mProceed;
+
+        Monitor() {
+            mProceed = true;
+
+            mSamplesToRead = (READ_TIME * AudioQualityVerifierActivity.SAMPLE_RATE) / 1000;
+            mBuffer = new byte[mSamplesToRead * AudioQualityVerifierActivity.BYTES_PER_SAMPLE];
+
+            final int minBufferSize = (BUFFER_TIME * AudioQualityVerifierActivity.SAMPLE_RATE *
+                    AudioQualityVerifierActivity.BYTES_PER_SAMPLE) / 1000;
+            final int minHardwareBufferSize = AudioRecord.getMinBufferSize(
+                    AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
+                    AudioQualityVerifierActivity.AUDIO_FORMAT);
+            final int bufferSize = Math.max(minHardwareBufferSize, minBufferSize);
+
+            mRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION,
+                    AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
+                    AudioQualityVerifierActivity.AUDIO_FORMAT, bufferSize);
+            if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+                Log.e(TAG, "Couldn't open audio for recording");
+                return;
+            }
+            mRecord.startRecording();
+            if (mRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+                Log.e(TAG, "Couldn't record");
+                return;
+            }
+
+            start(); // Begin background thread
+        }
+
+        public void halt() {
+            mProceed = false;
+        }
+
+        @Override
+        public void run() {
+            int maxBytes = mSamplesToRead * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+            int bytes;
+            while (true) {
+                if (!mProceed) {
+                    mRecord.stop();
+                    mRecord.release();
+                    return; // End thread
+                }
+                bytes = mRecord.read(mBuffer, 0, maxBytes);
+                if (bytes < 0) {
+                    if (bytes == AudioRecord.ERROR_INVALID_OPERATION) {
+                        Log.e(TAG, "Recording object not initalized");
+                    } else if (bytes == AudioRecord.ERROR_BAD_VALUE) {
+                        Log.e(TAG, "Invalid recording parameters");
+                    } else {
+                        Log.e(TAG, "Error during recording");
+                    }
+                    return;
+                }
+                if (bytes >= 2) {
+                    // Note: this won't work well if bytes is small (we should check)
+                    short[] samples = Utils.byteToShortArray(mBuffer, 0, bytes);
+                    float[] results = mNative.measureRms(samples,
+                            AudioQualityVerifierActivity.SAMPLE_RATE, -1.0f);
+                    float rms = results[Native.MEASURE_RMS_RMS];
+                    float duration = results[Native.MEASURE_RMS_DURATION];
+                    float mean = results[Native.MEASURE_RMS_MEAN];
+                    if (DEBUG) {
+                        // Confirm the RMS calculation
+                        float verifyRms = 0.0f;
+                        for (short x : samples) {
+                            verifyRms += x * x;
+                        }
+                        verifyRms /= samples.length;
+                        Log.i(TAG, "RMS: " + rms + ", bytes: " + bytes
+                                + ", duration: " + duration + ", mean: " + mean
+                                + ", manual RMS: " + Math.sqrt(verifyRms));
+                    }
+                    Message msg = mHandler.obtainMessage();
+                    msg.arg1 = Math.round(rms);
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java
new file mode 100644
index 0000000..6f2aa83
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java
@@ -0,0 +1,159 @@
+/*
+ * 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.verifier.audioquality;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.android.cts.verifier.R;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * The base class for all audio experiments.
+ */
+public class Experiment implements Runnable {
+    protected static final String TAG = "AudioQualityVerifier";
+
+    private static final int DEFAULT_TIMEOUT = 5; // In seconds
+
+    private String mName;
+    private String mScore;
+    private String mReport;
+    private List<String> mAudioFileNames;
+
+    enum Status { NotStarted, Running, Stopped, Completed }
+    private Status mStatus;
+    private boolean mEnabled;
+
+    protected Native mNative;
+    protected Context mContext;
+    protected Terminator mTerminator;
+
+    public Experiment(boolean enable) {
+        mEnabled = enable;
+        mNative = Native.getInstance();
+        reset();
+    }
+
+    public void init(Context context) {
+        mName = lookupName(context);
+    }
+
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_default_exp);
+    }
+
+    protected String getString(int resId) {
+        return mContext.getString(resId);
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public void reset() {
+        mStatus = Status.NotStarted;
+        mScore = "";
+        mReport = "";
+        mAudioFileNames = new ArrayList<String>();
+    }
+
+    public void start() {
+        mStatus = Status.Running;
+    }
+
+    protected void setScore(String score) {
+        mScore = score;
+    }
+
+    protected void setReport(String report) {
+        mReport = report;
+    }
+
+    // Implements Runnable
+    public void run() {}
+
+    public void run(Context context, Terminator t) {
+        mContext = context;
+        mTerminator = t;
+        Thread thread = new Thread(this);
+        thread.start();
+    }
+
+    public void setRecording(byte[] data) {
+        setRecording(data, -1);
+    }
+
+    public void setRecording(byte[] data, int num) {
+        // Save captured data to file
+        String filename = Utils.getExternalDir(mContext, this) + "/"
+            + Utils.cleanString(getName())
+            + (num == -1 ? "" : "_" + String.valueOf(num)) + ".raw";
+        Log.i(TAG, "Saving recorded data to " + filename);
+        Utils.saveFile(filename, data);
+        mAudioFileNames.add(filename);
+    }
+
+    public List<String> getAudioFileNames() {
+        return mAudioFileNames;
+    }
+
+    // Timeout in seconds
+    public int getTimeout() {
+        return DEFAULT_TIMEOUT;
+    }
+
+    public void cancel() {
+        mStatus = Status.Stopped;
+    }
+
+    public void stop() {
+        mStatus = Status.Completed;
+    }
+
+    public boolean isRunning() {
+        return mStatus == Status.Running;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getScore() {
+        switch (mStatus) {
+            case NotStarted:
+                return "-";
+            case Running:
+                return "...";
+            case Stopped:
+                return "-";
+            case Completed:
+                return mScore;
+        }
+        return "";
+    }
+
+    public void getReport(StringBuilder sb) {
+        sb.append(getName());
+        sb.append(": ");
+        sb.append(getScore());
+        sb.append("\n");
+        sb.append(mReport);
+        sb.append("\n\n");
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ExperimentService.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ExperimentService.java
new file mode 100644
index 0000000..95fd3b5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ExperimentService.java
@@ -0,0 +1,152 @@
+/*
+ * 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.verifier.audioquality;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Launch an experiment.
+ *
+ * Experiments are decoupled from the UI both so that they can run on a
+ * background thread without freezing the UI, and to support experiments
+ * which take over the screen from the UI (such as invoking Voice Search).
+ */
+public class ExperimentService extends Service implements Terminator {
+    private static final String TAG = "AudioQualityVerifier";
+    private static int BACKGROUND_LOAD = 0;
+
+    private ArrayList<Experiment> mExperiments;
+    private Experiment mExp;
+
+    private TimeoutHandler mHandler = null;
+    private PowerManager.WakeLock mWakeLock = null;
+
+    private boolean mRunAll;
+    private int mExpID;
+
+    private boolean mActive;
+    private LoadGenerator[] mLoadGenerator = null;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        mExperiments = VerifierExperiments.getExperiments(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "Service destroyed");
+        terminate(true);
+    }
+
+    /**
+     * Implements Terminator, to clean up when the experiment indicates it
+     * has completed.
+     */
+    public void terminate(boolean aborted) {
+        if (mLoadGenerator != null) {
+            for (LoadGenerator generator : mLoadGenerator) {
+                generator.halt();
+            }
+            mLoadGenerator = null;
+        }
+        if (!mActive) return;
+        mActive = false;
+        if (mHandler != null) mHandler.clear();
+        if (aborted) {
+            mExp.cancel();
+        } else {
+            mExp.stop();
+        }
+        Intent intent = new Intent(AudioQualityVerifierActivity.ACTION_EXP_FINISHED);
+        intent.putExtra(AudioQualityVerifierActivity.EXTRA_EXP_ID, mExpID);
+        intent.putExtra(AudioQualityVerifierActivity.EXTRA_RUN_ALL, mRunAll);
+        intent.putExtra(AudioQualityVerifierActivity.EXTRA_ABORTED, aborted);
+        sendBroadcast(intent);
+        if (mWakeLock != null) {
+            mWakeLock.release();
+            mWakeLock = null;
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        mActive = true;
+
+        // Obtain wakelock
+        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, TAG);
+        mWakeLock.acquire();
+
+        if (BACKGROUND_LOAD > 0) {
+            mLoadGenerator = new LoadGenerator[BACKGROUND_LOAD];
+            for (int i = 0; i < BACKGROUND_LOAD; i++) {
+                mLoadGenerator[i] = new LoadGenerator();
+            }
+        }
+
+        mExpID = intent.getIntExtra(AudioQualityVerifierActivity.EXTRA_EXP_ID, -1);
+        if (mExpID == -1) {
+            Log.e(TAG, "Invalid test ID");
+            System.exit(0);
+        }
+        mRunAll = intent.getBooleanExtra(AudioQualityVerifierActivity.EXTRA_RUN_ALL, false);
+        mExp = mExperiments.get(mExpID);
+        mExp.start();
+
+        // Inform the VerifierActivity Activity that we have started:
+        Intent feedback = new Intent(AudioQualityVerifierActivity.ACTION_EXP_STARTED);
+        feedback.putExtra(AudioQualityVerifierActivity.EXTRA_EXP_ID, mExpID);
+        sendBroadcast(feedback);
+
+        mHandler = new TimeoutHandler();
+        mHandler.delay(mExp.getTimeout());
+        mExp.run(this, this);
+
+        return START_NOT_STICKY;
+    }
+
+    class TimeoutHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            terminate(true);
+            stopSelf();
+        }
+
+        public void delay(int secs) {
+            removeMessages(0);
+            sendMessageDelayed(obtainMessage(0), secs * 1000);
+        }
+
+        public void clear() {
+            removeMessages(0);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/LoadGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/LoadGenerator.java
new file mode 100644
index 0000000..f19359c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/LoadGenerator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.verifier.audioquality;
+
+/**
+ * Simulate a simple load on the CPU, to determine the effect on recording.
+ * A better approach would be to generate network activity, to
+ * trigger interrupts as well.
+ */
+public class LoadGenerator extends Thread {
+    private boolean mProceed;
+
+    public LoadGenerator() {
+        mProceed = true;
+        start(); // Begin thread
+    }
+
+    public void halt() {
+        mProceed = false;
+    }
+
+    @Override
+    public void run() {
+        long counter = 0;
+        while (mProceed) {
+            counter++; // Busy loop
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Native.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Native.java
new file mode 100644
index 0000000..87b11d1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Native.java
@@ -0,0 +1,71 @@
+/*
+ * 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.verifier.audioquality;
+
+/**
+ * Interface to native (C++) DSP code.
+ */
+public class Native {
+    public native short[] generateSinusoid(float freq, float duration,
+            float sampleRate, float amplitude, float ramp);
+    public native float[] measureRms(short[] pcm, float sampleRate,
+            float onsetThresh);
+    public native float[] glitchTest(float sampleRate, float stimFreq,
+            float onsetThresh, float dbSnrThresh, short[] pcm);
+    public native float[] overflowCheck(short[] pcm, float sampleRate);
+    public native float[] compareSpectra(short[] pcm, short[] refPcm,
+            float sampleRate);
+    public native float linearityTest(short[][] pcms,
+        float sampleRate, float dbStepSize, int referenceStim);
+
+    // The following indexes must match those in wrapper.cc
+    public static final int MEASURE_RMS_RMS = 0;
+    public static final int MEASURE_RMS_STD_DEV = 1;
+    public static final int MEASURE_RMS_DURATION = 2;
+    public static final int MEASURE_RMS_MEAN = 3;
+
+    public static final int OVERFLOW_DELTAS = 0;
+    public static final int OVERFLOW_ERROR = 1;
+    public static final int OVERFLOW_DURATION = 2;
+    public static final int OVERFLOW_ONSET = 3;
+    public static final int OVERFLOW_OFFSET = 4;
+    public static final int OVERFLOW_MAX = 5;
+    public static final int OVERFLOW_MIN = 6;
+
+    public static final int GLITCH_COUNT = 0;
+    public static final int GLITCH_ERROR = 1;
+    public static final int GLITCH_DURATION = 2;
+
+    public static final int SPECTRUM_MAX_DEVIATION = 0;
+    public static final int SPECTRUM_ERROR = 1;
+    public static final int SPECTRUM_RMS_DEVIATION = 2;
+
+    private static Native mInstance = null;
+
+    static {
+        System.loadLibrary("audioquality");
+    }
+
+    private Native() {}
+
+    public static Native getInstance() {
+        if (mInstance == null) {
+            mInstance = new Native();
+        }
+        return mInstance;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt
new file mode 100644
index 0000000..3f9a59c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt
@@ -0,0 +1,96 @@
+Android Audio Quality Verifier App
+==================================
+
+This app runs a set of audio quality tests on an Android device,
+to verify the end-end sound recording path.
+
+If any of these tests fail, the device is probably unsuitable
+for demanding audio tasks such as speech recognition.
+If all the tests pass, the device audio is of a good standard.
+Note that not all possible audio defects can be detected by this
+test suite, so passing does not guarantee ideal audio quality.
+
+Hardware setup
+--------------
+
+The required physical set-up consists of a powered speaker,
+connected to the Android's headphone output by a standard
+audio cable.
+
+For loudspeakers which come in pairs, you only need to use
+one speaker (typically the powered or master speaker); you
+can leave the second speaker disconnected.
+If the speakers are stereo within a single unit (sometimes
+with speakers facing in opposite directions), place the phone
+in front of either of them.
+Speakers with multiple drivers per channel (e.g. a tweeter
+and a woofer) are not suitable.
+
+The phone should be placed in front of the centre of the
+speaker cone. The distance from the speaker will be adjusted
+during calibration; typically you could expect it to be around
+3cm or so.
+Use a supporting platform such as a stack of books to raise
+the phone to the correct height to line up with the speaker.
+
+Bluetooth connection is possible but cable connection is
+usually preferable.
+
+Recommended loudspeakers
+------------------------
+
+Using suitable loudspeakers ensures that test failures highlight
+problems with the Android device under test, and not limitations
+of the loudspeakers. The following loudspeakers work well for this
+purpose:
+
+1. Yamaha NX-B02
+
+Use on AC power, not batteries.
+This speaker works well with Bluetooth as well as a wired connection.
+Note that it's not uncommon for the devices to exhibit different
+bugs under Bluetooth.
+
+2. Cakewalk MA-7A (Edirol / Roland)
+
+The "Bass Enhancer" feature MUST be switched off.
+Note that it turns itself on again every time the speakers are
+powered on, so it is easy to forget to switch it off!
+
+Software setup
+--------------
+
+1. Build the application's apk.
+2. Install the apk using adb.
+3. Run the app.
+4. Click "Calibrate". Position the phone as described in
+   Hardware setup above, with the microphone facing the speaker,
+   and adjust the volume of the speaker until the status message
+   indicates it is correct.
+5. Click on any test in the list to run it, or "Run All" to run
+   each test in sequence.
+6. Click "Results" to view the outcomes. A correctly functioning
+   device should pass all tests.
+7. Click "Send by email" from the results page to send the
+   results to an e-mail address of your choice. The recordings
+   made are also attached as raw 16 bit, 16 kHz audio files to
+   help you diagnose any failed tests.
+
+Q&A
+---
+
+Q. What if the sound level check fails?
+A. Go back to the calibration step before running any other test.
+   Make sure the device has not been moved.
+   We also recommend that once the setup is calibrated there are no
+   moving objects or people near the device under test, since these
+   will change the acoustic properties of the environment from the
+   calibrated state.
+
+Q. Some of the tests sound very loud. Is this normal?
+A. The clipping test will generally be very loud indeed;
+   the others should be at a moderate volume.
+
+Q. What sort of room should the tests be performed in?
+A. Any, as long as the background noise levels are kept low, to
+   avoid interference with the test recordings.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Terminator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Terminator.java
new file mode 100644
index 0000000..b0edc90
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Terminator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.verifier.audioquality;
+
+/**
+ * Interface passed to each Experiment.
+ */
+public interface Terminator {
+    /**
+     * Indicate that the experiment has finished.
+     *
+     * @param aborted is true if the experiment terminated abnormally.
+     */
+    public void terminate(boolean aborted);
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java
new file mode 100644
index 0000000..885e18c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java
@@ -0,0 +1,350 @@
+/*
+ * 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.verifier.audioquality;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * File and data utilities for the Audio Verifier.
+ */
+public class Utils {
+    public static final String TAG = "AudioQualityVerifier";
+    public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
+
+    /**
+     *  Time delay.
+     *
+     *  @param ms time in milliseconds to pause for
+     */
+    public static void delay(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {}
+    }
+
+    public static String getExternalDir(Context context, Object exp) {
+        checkExternalStorageAvailable();
+        // API level 8:
+        // return context.getExternalFilesDir(null).getAbsolutePath();
+        // API level < 8:
+        String dir = Environment.getExternalStorageDirectory().getAbsolutePath();
+        dir += "/Android/data/" + exp.getClass().getPackage().getName() + "/files";
+        checkMakeDir(dir);
+        return dir;
+    }
+
+    private static void checkExternalStorageAvailable() {
+        String state = Environment.getExternalStorageState();
+        if (!Environment.MEDIA_MOUNTED.equals(state)) {
+            // TODO: Raise a Toast and supply internal storage instead
+        }
+    }
+
+    private static void checkMakeDir(String dir) {
+        File f = new File(dir);
+        if (!f.exists()) {
+            f.mkdirs();
+        }
+    }
+
+    /**
+     * Convert a string (e.g. the name of an experiment) to something more suitable
+     * for use as a filename.
+     *
+     * @param s the string to be cleaned
+     * @return a string which is similar (not necessarily unique) and safe for filename use
+     */
+    public static String cleanString(String s) {
+        StringBuilder sb = new StringBuilder();
+        for (char c : s.toCharArray()) {
+            if (Character.isWhitespace(c)) sb.append('_');
+            else if (Character.isLetterOrDigit(c)) sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Convert a sub-array from bytes to shorts.
+     *
+     * @param data array of bytes to be converted
+     * @param start first index to convert (should be even)
+     * @param len number of bytes to convert (should be even)
+     * @return an array of half the length, containing shorts
+     */
+    public static short[] byteToShortArray(byte[] data, int start, int len) {
+        short[] samples = new short[len / 2];
+        ByteBuffer bb = ByteBuffer.wrap(data, start, len);
+        bb.order(BYTE_ORDER);
+        for (int i = 0; i < len / 2; i++) {
+            samples[i] = bb.getShort();
+        }
+        return samples;
+    }
+
+    /**
+     * Convert a byte array to an array of shorts (suitable for the phone test
+     * native library's audio sample data).
+     *
+     * @param data array of bytes to be converted
+     * @return an array of half the length, containing shorts
+     */
+    public static short[] byteToShortArray(byte[] data) {
+        int len = data.length / 2;
+        short[] samples = new short[len];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.order(BYTE_ORDER);
+        for (int i = 0; i < len; i++) {
+            samples[i] = bb.getShort();
+        }
+        return samples;
+    }
+
+    /**
+     * Convert a short array (as returned by the phone test native library)
+     * to an array of bytes.
+     *
+     * @param samples array of shorts to be converted
+     * @return an array of twice the length, broken out into bytes
+     */
+    public static byte[] shortToByteArray(short[] samples) {
+        int len = samples.length;
+        byte[] data = new byte[len * 2];
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.order(BYTE_ORDER);
+        for (int i = 0; i < len; i++) {
+            bb.putShort(samples[i]);
+        }
+        return data;
+    }
+
+    /**
+     * Scale the amplitude of an array of samples.
+     *
+     * @param samples to be scaled
+     * @param db decibels to scale up by (may be negative)
+     * @return the scaled samples
+     */
+    public static short[] scale(short[] samples, float db) {
+        short[] scaled = new short[samples.length];
+        // Convert decibels to a linear ratio:
+        double ratio = Math.pow(10.0, db / 20.0);
+        for (int i = 0; i < samples.length; i++) {
+            scaled[i] = (short) (samples[i] * ratio);
+        }
+        return scaled;
+    }
+
+    /**
+     * Read an entire file into memory.
+     *
+     * @param filename to be opened
+     * @return the file data, or null in case of error
+     */
+    private static byte[] readFile(String filename) {
+        FileInputStream fis;
+        try {
+            fis = new FileInputStream(filename);
+        } catch (FileNotFoundException e1) {
+            return null;
+        }
+
+        File file = new File(filename);
+        int len = (int) file.length();
+        byte[] data = new byte[len];
+
+        int pos = 0;
+        int bytes = 0;
+        int count;
+        while (pos < len) {
+            try {
+                count = fis.read(data, pos, len - pos);
+            } catch (IOException e) {
+                return null;
+            }
+            if (count < 1) return null;
+            pos += count;
+        }
+
+        try {
+            fis.close();
+        } catch (IOException e) {}
+        return data;
+    }
+
+    /**
+     * Read an entire file from an InputStream.
+     * Useful as AssetManager returns these.
+     *
+     * @param stream to read file contents from
+     * @return file data
+     */
+    public static byte[] readFile(InputStream stream) {
+        final int CHUNK_SIZE = 10000;
+        ByteArrayBuilder bab = new ByteArrayBuilder();
+        byte[] buf = new byte[CHUNK_SIZE];
+        int count;
+        while (true) {
+            try {
+                count = stream.read(buf, 0, CHUNK_SIZE);
+            } catch (IOException e) {
+                return null;
+            }
+            if (count == -1) break; // EOF
+            bab.append(buf, count);
+        }
+        return bab.toByteArray();
+    }
+
+    /**
+     * Save binary (audio) data to a file.
+     *
+     * @param filename to be written
+     * @param data contents
+     */
+    public static void saveFile(String filename, byte[] data) {
+        try {
+            FileOutputStream fos = new FileOutputStream(filename);
+            fos.write(data);
+            fos.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing to file " + filename);
+        }
+    }
+
+    /**
+     * Push an entire array of audio data to an AudioTrack.
+     *
+     * @param at destination
+     * @param data to be written
+     * @return true if successful, or false on error
+     */
+    public static boolean writeAudio(AudioTrack at, byte[] data) {
+        int pos = 0;
+        int len = data.length;
+        int count;
+
+        while (pos < len) {
+            count = at.write(data, pos, len - pos);
+            if (count < 0) return false;
+            pos += count;
+        }
+        at.flush();
+        return true;
+    }
+
+    /**
+     * Determine the number of audio samples in a file
+     *
+     * @param filename file containing audio data
+     * @return number of samples in file, or 0 if file does not exist
+     */
+    public static int duration(String filename) {
+        File file = new File(filename);
+        int len = (int) file.length();
+        return len / AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+    }
+
+    /**
+     * Determine the number of audio samples in a stimulus asset
+     *
+     * @param context to look up stimulus
+     * @param stimNum index number of this stimulus
+     * @return number of samples in stimulus
+     */
+    public static int duration(Context context, int stimNum) {
+        byte[] data = AudioAssets.getStim(context, stimNum);
+        return data.length / AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+    }
+
+    public static void playRawFile(String filename) {
+        byte[] data = readFile(filename);
+        if (data == null) {
+            Log.e(TAG, "Cannot read " + filename);
+            return;
+        }
+        playRaw(data);
+    }
+
+    public static void playStim(Context context, int stimNum) {
+        Utils.playRaw(getStim(context, stimNum));
+    }
+
+    public static byte[] getStim(Context context, int stimNum) {
+        return AudioAssets.getStim(context, stimNum);
+    }
+
+    public static byte[] getPinkNoise(Context context, int ampl, int duration) {
+        return AudioAssets.getPinkNoise(context, ampl, duration);
+    }
+
+    public static void playRaw(byte[] data) {
+        Log.i(TAG, "Playing " + data.length + " bytes of pre-recorded audio");
+        AudioTrack at = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM, AudioQualityVerifierActivity.SAMPLE_RATE,
+                AudioFormat.CHANNEL_OUT_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT,
+                data.length, AudioTrack.MODE_STREAM);
+        writeAudio(at, data);
+        at.play();
+    }
+
+    /**
+     * The equivalent of a simplified StringBuilder, but for bytes.
+     */
+    public static class ByteArrayBuilder {
+        private byte[] buf;
+        private int capacity, size;
+
+        public ByteArrayBuilder() {
+            capacity = 100;
+            size = 0;
+            buf = new byte[capacity];
+        }
+
+        public void append(byte[] b, int nBytes) {
+            if (nBytes < 1) return;
+            if (size + nBytes > capacity) expandCapacity(size + nBytes);
+            System.arraycopy(b, 0, buf, size, nBytes);
+            size += nBytes;
+        }
+
+        public byte[] toByteArray() {
+            byte[] result = new byte[size];
+            System.arraycopy(buf, 0, result, 0, size);
+            return result;
+        }
+
+        private void expandCapacity(int min) {
+            capacity *= 2;
+            if (capacity < min) capacity = min;
+            byte[] expanded = new byte[capacity];
+            System.arraycopy(buf, 0, expanded, 0, size);
+            buf = expanded;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java
new file mode 100644
index 0000000..f800907
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java
@@ -0,0 +1,57 @@
+/*
+ * 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.verifier.audioquality;
+
+import com.android.cts.verifier.audioquality.experiments.BiasExperiment;
+import com.android.cts.verifier.audioquality.experiments.OverflowExperiment;
+import com.android.cts.verifier.audioquality.experiments.GainLinearityExperiment;
+import com.android.cts.verifier.audioquality.experiments.GlitchExperiment;
+import com.android.cts.verifier.audioquality.experiments.SoundLevelExperiment;
+import com.android.cts.verifier.audioquality.experiments.SpectrumShapeExperiment;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+
+/**
+ * Data shared between the VerifierActivity and ExperimentService
+ */
+public class VerifierExperiments {
+
+    private static ArrayList<Experiment> mExperiments = null;
+
+    private VerifierExperiments() {
+    }
+
+    public static ArrayList<Experiment> getExperiments(Context context) {
+        if (mExperiments == null) {
+            mExperiments = new ArrayList<Experiment>();
+            mExperiments.add(new SoundLevelExperiment());
+            mExperiments.add(new BiasExperiment());
+            mExperiments.add(new OverflowExperiment());
+            mExperiments.add(new GainLinearityExperiment());
+            mExperiments.add(new SpectrumShapeExperiment());
+            mExperiments.add(new GlitchExperiment(0));
+            mExperiments.add(new GlitchExperiment(7));
+            // mExperiments.add(new VoiceRecognitionExperiment());
+            for (Experiment exp : mExperiments) {
+                exp.init(context);
+            }
+        }
+        return mExperiments;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java
new file mode 100644
index 0000000..6563335
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java
@@ -0,0 +1,101 @@
+/*
+ * 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.verifier.audioquality;
+
+import com.android.cts.verifier.R;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This Activity allows the user to examine the results of the
+ * experiments which have been run so far.
+ */
+public class ViewResultsActivity extends Activity implements View.OnClickListener {
+    private TextView mTextView;
+    private Button mDismissButton;
+    private Button mSendResultsButton;
+    private String mResults;
+
+    private ArrayList<Experiment> mExperiments;
+
+    // The package of the Gmail application
+    private static final String GMAIL_PACKAGE = "com.google.android.gm";
+
+    // The Gmail compose activity name
+    private static final String GMAIL_ACTIVITY = GMAIL_PACKAGE
+            + ".ComposeActivityGmail";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.aq_view_results);
+
+        mDismissButton = (Button) findViewById(R.id.dismissButton);
+        mDismissButton.setOnClickListener(this);
+
+        mSendResultsButton = (Button) findViewById(R.id.sendResultsButton);
+        mSendResultsButton.setOnClickListener(this);
+
+        mTextView = (TextView) findViewById(R.id.textView);
+
+        Intent intent = getIntent();
+        mResults = intent.getStringExtra(AudioQualityVerifierActivity.EXTRA_RESULTS);
+        mTextView.setText(mResults);
+
+        mExperiments = VerifierExperiments.getExperiments(this);
+    }
+
+    private void sendResults() {
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_SEND_MULTIPLE);
+        intent.setComponent(new ComponentName(GMAIL_PACKAGE, GMAIL_ACTIVITY));
+        intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.aq_subject));
+        intent.putExtra(Intent.EXTRA_TEXT, mResults);
+
+        ArrayList<Parcelable> attachments = new ArrayList<Parcelable>();
+        for (Experiment exp : mExperiments) {
+            List<String> filenames = exp.getAudioFileNames();
+            for (String filename : filenames) {
+                attachments.add(Uri.fromFile(new File(filename)));
+            }
+        }
+        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+        startActivity(intent);
+    }
+
+    // Implements View.OnClickListener
+    public void onClick(View v) {
+        if (v == mDismissButton) {
+            finish();
+        } else if (v == mSendResultsButton) {
+            sendResults();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/BiasExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/BiasExperiment.java
new file mode 100644
index 0000000..bb6f8bf
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/BiasExperiment.java
@@ -0,0 +1,58 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Native;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * Experiment to look for excessive DC bias in the recordings.
+ * It calculates the mean of the observed samples.
+ */
+public class BiasExperiment extends LoopbackExperiment {
+    private static final float ONSET_THRESH = 10.0f;
+    private static final float TOLERANCE = 200.0f;
+
+    public BiasExperiment() {
+        super(true);
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_bias_exp);
+    }
+
+    @Override
+    protected void compare(byte[] stim, byte[] record) {
+        short[] pcm = Utils.byteToShortArray(record);
+        float[] results = mNative.measureRms(pcm, AudioQualityVerifierActivity.SAMPLE_RATE, ONSET_THRESH);
+        float rms = results[Native.MEASURE_RMS_RMS];
+        float duration = results[Native.MEASURE_RMS_DURATION];
+        float mean = results[Native.MEASURE_RMS_MEAN];
+        if (mean < -TOLERANCE || mean > TOLERANCE) {
+            setScore(getString(R.string.aq_fail));
+        } else {
+            setScore(getString(R.string.aq_pass));
+        }
+        setReport(String.format(getString(R.string.aq_bias_report),
+                mean, TOLERANCE, rms, duration));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GainLinearityExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GainLinearityExperiment.java
new file mode 100644
index 0000000..05f1602
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GainLinearityExperiment.java
@@ -0,0 +1,85 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * Experiment to test the linearity of the microphone gain response.
+ *
+ * This plays a sequence of identical stimuli at increasing volumes and then
+ * analyzes the set of recordings.
+ */
+public class GainLinearityExperiment extends SequenceExperiment {
+    private static final int LEVELS = 4;
+    private static final float DB_STEP_SIZE = 10.0f;
+    private static final float TOLERANCE = 2.0f; // Maximum allowed deviation from linearity in dB
+    private static final int STIM_NUM = 31;
+
+    private short[] mReference = null;
+
+    public GainLinearityExperiment() {
+        super(true);
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_linearity_exp);
+    }
+
+    @Override
+    protected int getTrials() {
+        return LEVELS;
+    }
+
+    @Override
+    protected byte[] getStim(Context context, int trial) {
+        float db = (trial - (LEVELS - 1)) * DB_STEP_SIZE;
+        if (mReference == null) {
+            mReference = Utils.byteToShortArray(Utils.getStim(context, STIM_NUM));
+        }
+        short[] samples = Utils.scale(mReference, db);
+        return Utils.shortToByteArray(samples);
+    }
+
+    @Override
+    protected void compare(byte[][] stim, byte[][] record) {
+        short[][] pcms = new short[LEVELS][];
+        for (int i = 0; i < LEVELS; i++) {
+            pcms[i] = Utils.byteToShortArray(record[i]);
+        }
+        // We specify the middle stimulus (LEVELS / 2) as the "reference":
+        float deviation = mNative.linearityTest(pcms, AudioQualityVerifierActivity.SAMPLE_RATE,
+                DB_STEP_SIZE, LEVELS / 2);
+        if (deviation < 0.0f) {
+            setScore(getString(R.string.aq_fail));
+            setReport(String.format(getString(R.string.aq_linearity_report_error), deviation));
+        } else if (deviation > TOLERANCE) {
+            setScore(getString(R.string.aq_fail));
+            setReport(String.format(getString(R.string.aq_linearity_report_normal),
+                    deviation, TOLERANCE));
+        } else {
+            setScore(getString(R.string.aq_pass));
+            setReport(String.format(getString(R.string.aq_linearity_report_normal),
+                    deviation, TOLERANCE));
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GlitchExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GlitchExperiment.java
new file mode 100644
index 0000000..143c4a0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/GlitchExperiment.java
@@ -0,0 +1,99 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Native;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+import java.util.Random;
+
+/**
+ * Experiment to detect glitches or dropouts in the signal.
+ * A number of artificial glitches can be optionally introduced.
+ */
+public class GlitchExperiment extends LoopbackExperiment {
+    private static final float FREQ = 625.0f;
+    private static final float AMPL = 10000.0f;
+    private static final float ONSET_THRESH = 80.0f;
+    private static final float SNR_THRESH = 20.0f;
+    private static final float RAMP = 0.01f;
+    private static final float DURATION = 3.0f;
+
+    private int mArtificialGlitches;
+
+    public GlitchExperiment(int artificialGlitches) {
+        super(true);
+        mArtificialGlitches = artificialGlitches;
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        String s = context.getString(R.string.aq_glitch_exp);
+        if (mArtificialGlitches > 0) {
+            s += " (" + mArtificialGlitches + ")";
+        }
+        return s;
+    }
+
+    @Override
+    protected byte[] getStim(Context context) {
+        short[] sinusoid = mNative.generateSinusoid(FREQ, DURATION,
+                AudioQualityVerifierActivity.SAMPLE_RATE, AMPL, RAMP);
+        addGlitches(sinusoid);
+        return Utils.shortToByteArray(sinusoid);
+    }
+
+    private void addGlitches(short[] samples) {
+        Random random = new Random();
+        for (int i = 0; i < mArtificialGlitches; i++) {
+            samples[random.nextInt(samples.length)] = 0;
+        }
+    }
+
+    @Override
+    protected void compare(byte[] stim, byte[] record) {
+        int targetMin = mArtificialGlitches > 0 ? 1 : 0;
+        int targetMax = mArtificialGlitches;
+        short[] pcm = Utils.byteToShortArray(record);
+        float[] ret = mNative.glitchTest(AudioQualityVerifierActivity.SAMPLE_RATE, FREQ,
+                ONSET_THRESH, SNR_THRESH, pcm);
+        int glitches = Math.round(ret[Native.GLITCH_COUNT]);
+        float error = ret[Native.GLITCH_ERROR];
+        float duration = ret[Native.GLITCH_DURATION];
+        if (error < 0.0f) {
+            setScore(getString(R.string.aq_fail));
+            setReport(getString(R.string.aq_glitch_report_error));
+        } else {
+            if (glitches > targetMax || glitches < targetMin) {
+                setScore(getString(R.string.aq_fail));
+            } else {
+                setScore(getString(R.string.aq_pass));
+            }
+            if (targetMin == targetMax) {
+                setReport(String.format(getString(R.string.aq_glitch_report_exact),
+                        glitches, targetMax, duration));
+            } else {
+                setReport(String.format(getString(R.string.aq_glitch_report_range),
+                        glitches, targetMin, targetMax, duration));
+            }
+        }
+   }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/LoopbackExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/LoopbackExperiment.java
new file mode 100644
index 0000000..f4a1e1f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/LoopbackExperiment.java
@@ -0,0 +1,180 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Experiment;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.util.Log;
+
+/**
+ * LoopbackExperiment represents a general class of experiments, all of which
+ * comprise playing an audio stimulus of some kind, whilst simultaneously
+ * recording from the microphone. The recording is then analyzed to determine
+ * the test results (score and report).
+ */
+public class LoopbackExperiment extends Experiment {
+    protected static final int TIMEOUT = 10;
+
+    // Amount of silence in ms before and after playback
+    protected static final int END_DELAY_MS = 500;
+
+    private Recorder mRecorder = null;
+
+    public LoopbackExperiment(boolean enable) {
+        super(enable);
+    }
+
+    protected byte[] getStim(Context context) {
+        int stimNum = 2;
+        byte[] data = Utils.getStim(context, stimNum);
+        return data;
+    }
+
+    @Override
+    public void run() {
+        byte[] playbackData = getStim(mContext);
+        byte[] recordedData = loopback(playbackData);
+
+        compare(playbackData, recordedData);
+        setRecording(recordedData);
+        mTerminator.terminate(false);
+    }
+
+    protected byte[] loopback(byte[] playbackData) {
+        int samples = playbackData.length / 2;
+        int duration = (samples * 1000) / AudioQualityVerifierActivity.SAMPLE_RATE; // In ms
+        int padSamples = (END_DELAY_MS * AudioQualityVerifierActivity.SAMPLE_RATE) / 1000;
+        int totalSamples = samples + 2 * padSamples;
+        byte[] recordedData = new byte[totalSamples * 2];
+
+        mRecorder = new Recorder(recordedData, totalSamples);
+        mRecorder.start();
+        Utils.delay(END_DELAY_MS);
+
+        Utils.playRaw(playbackData);
+
+        int timeout = duration + 2 * END_DELAY_MS;
+        try {
+            mRecorder.join(timeout);
+        } catch (InterruptedException e) {}
+
+        return recordedData;
+    }
+
+    protected void compare(byte[] stim, byte[] record) {
+        setScore(getString(R.string.aq_complete));
+        setReport(getString(R.string.aq_loopback_report));
+    }
+
+    private void halt() {
+        if (mRecorder != null) {
+            mRecorder.halt();
+        }
+    }
+
+    @Override
+    public void cancel() {
+        super.cancel();
+        halt();
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        halt();
+    }
+
+    @Override
+    public int getTimeout() {
+        return TIMEOUT;
+    }
+
+    /* Class which records audio in a background thread, to fill the supplied buffer. */
+    class Recorder extends Thread {
+        private AudioRecord mRecord;
+        private int mSamples;
+        private byte[] mBuffer;
+        private boolean mProceed;
+
+        Recorder(byte[] buffer, int samples) {
+            mBuffer = buffer;
+            mSamples = samples;
+            mProceed = true;
+        }
+
+        public void halt() {
+            mProceed = false;
+        }
+
+        @Override
+        public void run() {
+            final int minBufferSize = AudioQualityVerifierActivity.SAMPLE_RATE
+                    * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+            final int minHardwareBufferSize = AudioRecord.getMinBufferSize(
+                    AudioQualityVerifierActivity.SAMPLE_RATE,
+                    AudioFormat.CHANNEL_IN_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT);
+            final int bufferSize = Math.max(minHardwareBufferSize, minBufferSize);
+
+            mRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION,
+                    AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
+                    AudioQualityVerifierActivity.AUDIO_FORMAT, bufferSize);
+            if (mRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+                Log.e(TAG, "Couldn't open audio for recording");
+                return;
+            }
+            mRecord.startRecording();
+            if (mRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
+                Log.e(TAG, "Couldn't record");
+                return;
+            }
+
+            captureLoop();
+
+            mRecord.stop();
+            mRecord.release();
+            mRecord = null;
+        }
+
+        private void captureLoop() {
+            int totalBytes = mSamples * AudioQualityVerifierActivity.BYTES_PER_SAMPLE;
+            Log.i(TAG, "Recording " + totalBytes + " bytes");
+            int position = 0;
+            int bytes;
+            while (position < totalBytes && mProceed) {
+                bytes = mRecord.read(mBuffer, position, totalBytes - position);
+                if (bytes < 0) {
+                    if (bytes == AudioRecord.ERROR_INVALID_OPERATION) {
+                        Log.e(TAG, "Recording object not initalized");
+                    } else if (bytes == AudioRecord.ERROR_BAD_VALUE) {
+                        Log.e(TAG, "Invalid recording parameters");
+                    } else {
+                        Log.e(TAG, "Error during recording");
+                    }
+                    return;
+                }
+                position += bytes;
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java
new file mode 100644
index 0000000..71deac8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java
@@ -0,0 +1,86 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.CalibrateVolumeActivity;
+import com.android.cts.verifier.audioquality.Native;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * Experiment to test the clipping behaviour of the microphone.
+ *
+ * The stimulus is sinusoidal, and calculated to cause clipping for
+ * part of the waveform. The experiment looks for strange clipping behaviour
+ * by checking if the signal has any discontinuities (which might indicate
+ * wraparound, for example).
+ */
+public class OverflowExperiment extends LoopbackExperiment {
+    private static final float FREQ = 250.0f;
+    private static final float AMPL = 32768.0f * 1.1f * CalibrateVolumeActivity.OUTPUT_AMPL
+            / CalibrateVolumeActivity.TARGET_AMPL;
+    private static final float DURATION = 3.0f; // Duration of tone in seconds
+    private static final float MIN_DURATION = DURATION * 0.9f;
+    private static final float RAMP = 0.01f;
+
+    public OverflowExperiment() {
+        super(true);
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_overflow_exp);
+    }
+
+    @Override
+    protected byte[] getStim(Context context) {
+        short[] sinusoid = mNative.generateSinusoid(FREQ, DURATION,
+                AudioQualityVerifierActivity.SAMPLE_RATE, AMPL, RAMP);
+        return Utils.shortToByteArray(sinusoid);
+    }
+
+    @Override
+    protected void compare(byte[] stim, byte[] record) {
+        short[] pcm = Utils.byteToShortArray(record);
+        float[] ret = mNative.overflowCheck(pcm, AudioQualityVerifierActivity.SAMPLE_RATE);
+        int numDeltas = Math.round(ret[0]);
+        float error = ret[Native.OVERFLOW_ERROR];
+        float duration = ret[Native.OVERFLOW_DURATION];
+        float minPeak = ret[Native.OVERFLOW_MIN];
+        float maxPeak = ret[Native.OVERFLOW_MAX];
+
+        if (error < 0.0f) {
+            setScore(getString(R.string.aq_fail));
+            setReport(getString(R.string.aq_overflow_report_error));
+        } else if (duration < MIN_DURATION) {
+            setScore(getString(R.string.aq_fail));
+            setReport(String.format(getString(R.string.aq_overflow_report_short),
+                    DURATION, duration));
+        } else if (numDeltas > 0) {
+            setScore(getString(R.string.aq_fail));
+            setReport(String.format(getString(R.string.aq_overflow_report_fail),
+                    numDeltas, duration, minPeak, maxPeak));
+        } else {
+            setScore(getString(R.string.aq_pass));
+            setReport(String.format(getString(R.string.aq_overflow_report_pass),
+                    numDeltas, duration, minPeak, maxPeak));
+        }
+   }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java
new file mode 100644
index 0000000..0a3846c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java
@@ -0,0 +1,67 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * An extension to LoopbackExperiment, in which the "playback and record"
+ * cycle is repeated several times. A family of stimuli is defined, and
+ * the experiment outcome may depend on the whole sequence of recordings.
+ */
+public class SequenceExperiment extends LoopbackExperiment {
+    public SequenceExperiment(boolean enable) {
+        super(enable);
+    }
+
+    protected int getTrials() {
+        return 1;
+    }
+
+    protected byte[] getStim(Context context, int trial) {
+        int stimNum = 2;
+        byte[] data = Utils.getStim(context, stimNum);
+        return data;
+    }
+
+    protected void compare(byte[][] stim, byte[][] record) {
+        setScore(getString(R.string.aq_complete));
+        setReport(getString(R.string.aq_loopback_report));
+    }
+
+    @Override
+    public void run() {
+        int n = getTrials();
+        byte[][] playbackData = new byte[n][];
+        byte[][] recordedData = new byte[n][];
+        for (int trial = 0; trial < n; trial++) {
+            playbackData[trial] = getStim(mContext, trial);
+            recordedData[trial] = loopback(playbackData[trial]);
+            setRecording(recordedData[trial], trial);
+        }
+        compare(playbackData, recordedData);
+        mTerminator.terminate(false);
+    }
+
+    @Override
+    public int getTimeout() {
+        return TIMEOUT * getTrials();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SoundLevelExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SoundLevelExperiment.java
new file mode 100644
index 0000000..4435f31
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SoundLevelExperiment.java
@@ -0,0 +1,81 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.CalibrateVolumeActivity;
+import com.android.cts.verifier.audioquality.Native;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * Experiment to verify that the sound level has been correctly set.
+ *
+ * This experiment should be run first; if it fails the others may
+ * fail also.
+ */
+public class SoundLevelExperiment extends LoopbackExperiment {
+    private static final float ONSET_THRESH = 10.0f;
+    private static final int DURATION = 2;
+    private static final float TOLERANCE = 1.05f;
+    private static final float FREQ = 625.0f;
+    private static final float RAMP = 0.0f;
+
+    public SoundLevelExperiment() {
+        super(true);
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_sound_level_exp);
+    }
+
+    @Override
+    protected byte[] getStim(Context context) {
+        if (CalibrateVolumeActivity.USE_PINK) {
+            return Utils.getPinkNoise(context, CalibrateVolumeActivity.OUTPUT_AMPL, DURATION);
+        } else {
+            short[] sinusoid = mNative.generateSinusoid(FREQ, DURATION,
+                    AudioQualityVerifierActivity.SAMPLE_RATE, CalibrateVolumeActivity.OUTPUT_AMPL, RAMP);
+            return Utils.shortToByteArray(sinusoid);
+        }
+    }
+
+    @Override
+    protected void compare(byte[] stim, byte[] record) {
+        short[] pcm = Utils.byteToShortArray(record);
+        float[] results = mNative.measureRms(pcm, AudioQualityVerifierActivity.SAMPLE_RATE, ONSET_THRESH);
+        float rms = results[Native.MEASURE_RMS_RMS];
+        float duration = results[Native.MEASURE_RMS_DURATION];
+        String delta;
+        if (rms * TOLERANCE < CalibrateVolumeActivity.TARGET_RMS) {
+            setScore(getString(R.string.aq_fail));
+            delta = getString(R.string.aq_status_low);
+        } else if (rms > CalibrateVolumeActivity.TARGET_RMS * TOLERANCE) {
+            setScore(getString(R.string.aq_fail));
+            delta = getString(R.string.aq_status_high);
+        } else {
+            setScore(getString(R.string.aq_pass));
+            delta = getString(R.string.aq_status_ok);
+        }
+        setReport(delta + ".\n" + String.format(getString(R.string.aq_level_report),
+                rms, CalibrateVolumeActivity.TARGET_RMS,
+                100.0f * (TOLERANCE - 1.0f), duration));
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SpectrumShapeExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SpectrumShapeExperiment.java
new file mode 100644
index 0000000..148fb471
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SpectrumShapeExperiment.java
@@ -0,0 +1,70 @@
+/*
+ * 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.verifier.audioquality.experiments;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.audioquality.AudioQualityVerifierActivity;
+import com.android.cts.verifier.audioquality.Native;
+import com.android.cts.verifier.audioquality.Utils;
+
+import android.content.Context;
+
+/**
+ * Experiment to check that the frequency profile of the recorded signal
+ * does not differ too much from the stimulus.
+ */
+public class SpectrumShapeExperiment extends LoopbackExperiment {
+    private static final float MAX_RMS_DEVIATION = 7.0f;
+    private static final int AMPL = 10000;
+    private static final int DURATION = 3;
+
+    public SpectrumShapeExperiment() {
+        super(true);
+    }
+
+    @Override
+    protected String lookupName(Context context) {
+        return context.getString(R.string.aq_spectrum_shape_exp);
+    }
+
+   @Override
+    protected byte[] getStim(Context context) {
+        return Utils.getPinkNoise(context, AMPL, DURATION);
+    }
+
+    @Override
+    protected void compare(byte[] stim, byte[] record) {
+        short[] pcm = Utils.byteToShortArray(record);
+        short[] refPcm = Utils.byteToShortArray(stim);
+        float[] ret = mNative.compareSpectra(pcm, refPcm, AudioQualityVerifierActivity.SAMPLE_RATE);
+        float maxDeviation = ret[Native.SPECTRUM_MAX_DEVIATION];
+        float error = ret[Native.SPECTRUM_ERROR];
+        float rmsDeviation = ret[Native.SPECTRUM_RMS_DEVIATION];
+        if (error < 0.0f) {
+            setScore(getString(R.string.aq_fail));
+            setReport(getString(R.string.aq_spectrum_report_error));
+        } else if (rmsDeviation > MAX_RMS_DEVIATION) {
+            setScore(getString(R.string.aq_fail));
+            setReport(String.format(getString(R.string.aq_spectrum_report_normal),
+                    rmsDeviation, MAX_RMS_DEVIATION));
+        } else {
+            setScore(getString(R.string.aq_pass));
+            setReport(String.format(getString(R.string.aq_spectrum_report_normal),
+                    rmsDeviation, MAX_RMS_DEVIATION));
+        }
+    }
+}
diff --git a/tests/ProcessTest/Android.mk b/tests/ProcessTest/Android.mk
index be1e7de..ba58e87 100644
--- a/tests/ProcessTest/Android.mk
+++ b/tests/ProcessTest/Android.mk
@@ -20,7 +20,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_AAPT_FLAGS = -c xx_YY -c cs
 
diff --git a/tests/appsecurity-tests/Android.mk b/tests/appsecurity-tests/Android.mk
index a0f7f41..4ec9922 100644
--- a/tests/appsecurity-tests/Android.mk
+++ b/tests/appsecurity-tests/Android.mk
@@ -23,7 +23,7 @@
 
 LOCAL_MODULE := CtsAppSecurityTests
 
-LOCAL_JAVA_LIBRARIES := hosttestlib ddmlib junit
+LOCAL_JAVA_LIBRARIES := hosttestlib ddmlib-prebuilt junit
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/tests/appsecurity-tests/src/com/android/cts/appsecurity/AppSecurityTests.java b/tests/appsecurity-tests/src/com/android/cts/appsecurity/AppSecurityTests.java
index f528ee1..c0503bc 100644
--- a/tests/appsecurity-tests/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/tests/appsecurity-tests/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -18,10 +18,15 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Map;
 
 import junit.framework.Test;
 
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.InstallException;
 import com.android.ddmlib.Log;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
 import com.android.ddmlib.testrunner.ITestRunListener;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestIdentifier;
@@ -78,7 +83,7 @@
      * Test that an app that declares the same shared uid as an existing app, cannot be installed
      * if it is signed with a different certificate.
      */
-    public void testSharedUidDifferentCerts() throws IOException {
+    public void testSharedUidDifferentCerts() throws InstallException {
         Log.i(LOG_TAG, "installing apks with shared uid, but different certs");
         try {
             // cleanup test apps that might be installed from previous partial test run
@@ -104,7 +109,7 @@
      * Test that an app update cannot be installed over an existing app if it has a different
      * certificate.
      */
-    public void testAppUpgradeDifferentCerts() throws IOException {
+    public void testAppUpgradeDifferentCerts() throws InstallException {
         Log.i(LOG_TAG, "installing app upgrade with different certs");
         try {
             // cleanup test app that might be installed from previous partial test run
@@ -127,7 +132,8 @@
     /**
      * Test that an app cannot access another app's private data.
      */
-    public void testAppFailAccessPrivateData() throws IOException {
+    public void testAppFailAccessPrivateData() throws InstallException, TimeoutException,
+            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
         try {
             // cleanup test app that might be installed from previous partial test run
@@ -155,7 +161,8 @@
     /**
      * Test that an app cannot instrument another app that is signed with different certificate.
      */
-    public void testInstrumentationDiffCert() throws IOException {
+    public void testInstrumentationDiffCert() throws InstallException, TimeoutException,
+            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
         try {
             // cleanup test app that might be installed from previous partial test run
@@ -186,7 +193,8 @@
      * Test that an app cannot use a signature-enforced permission if it is signed with a different
      * certificate than the app that declared the permission.
      */
-    public void testPermissionDiffCert() throws IOException {
+    public void testPermissionDiffCert() throws InstallException, TimeoutException,
+            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
         try {
             // cleanup test app that might be installed from previous partial test run
@@ -225,8 +233,15 @@
      *
      * @param pkgName Android application package for tests
      * @return <code>true</code> if all tests passed.
+     * @throws TimeoutException in case of a timeout on the connection.
+     * @throws AdbCommandRejectedException if adb rejects the command
+     * @throws ShellCommandUnresponsiveException if the device did not output any test result for
+     * a period longer than the max time to output.
+     * @throws IOException if connection to device was lost.
      */
-    private boolean runDeviceTests(String pkgName) {
+    private boolean runDeviceTests(String pkgName)
+            throws TimeoutException, AdbCommandRejectedException,
+            ShellCommandUnresponsiveException, IOException {
         CollectingTestRunListener listener = doRunTests(pkgName);
         return listener.didAllTestsPass();
     }
@@ -235,20 +250,40 @@
      * Helper method to run tests and return the listener that collected the results.
      * @param pkgName Android application package for tests
      * @return the {@link CollectingTestRunListener}
+     * @throws TimeoutException in case of a timeout on the connection.
+     * @throws AdbCommandRejectedException if adb rejects the command
+     * @throws ShellCommandUnresponsiveException if the device did not output any test result for
+     * a period longer than the max time to output.
+     * @throws IOException if connection to device was lost.
      */
-    private CollectingTestRunListener doRunTests(String pkgName) {
+    private CollectingTestRunListener doRunTests(String pkgName)
+            throws TimeoutException, AdbCommandRejectedException,
+            ShellCommandUnresponsiveException, IOException {
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, getDevice());
         CollectingTestRunListener listener = new CollectingTestRunListener();
         testRunner.run(listener);
         return listener;
     }
 
+    /**
+     * Helper method to run the specified packages tests, and return the test run error message.
+     *
+     * @param pkgName Android application package for tests
+     * @return the test run error message or <code>null</code> if test run completed.
+     * @throws IOException if connection to device was lost
+     */
+    private String runDeviceTestsWithRunResult(String pkgName) throws TimeoutException,
+            AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
+        CollectingTestRunListener listener = doRunTests(pkgName);
+        return listener.getTestRunErrorMessage();
+    }
+
     private static class CollectingTestRunListener implements ITestRunListener {
 
         private boolean mAllTestsPassed = true;
         private String mTestRunErrorMessage = null;
 
-        public void testEnded(TestIdentifier test) {
+        public void testEnded(TestIdentifier test,  Map<String, String> metrics) {
             // ignore
         }
 
@@ -259,7 +294,7 @@
             mAllTestsPassed = false;
         }
 
-        public void testRunEnded(long elapsedTime) {
+        public void testRunEnded(long elapsedTime, Map<String, String> resultBundle) {
             // ignore
         }
 
@@ -269,7 +304,7 @@
             mTestRunErrorMessage = errorMessage;
         }
 
-        public void testRunStarted(int testCount) {
+        public void testRunStarted(String runName, int testCount) {
             // ignore
         }
 
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 53ab2cf..683ec9e 100644
--- a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -20,16 +20,66 @@
     An app that declares a permission that requires a matching signature to
     access.
     -->
-
     <permission android:name="com.android.cts.permissionWithSignature"
         android:protectionLevel="signature" />
+    <uses-permission android:name="com.android.cts.permissionWithSignature" />
+
+    <!-- A permission this app will not hold. -->
+    <permission android:name="com.android.cts.permissionNotUsedWithSignature"
+        android:protectionLevel="signature" />
 
     <application>
+        <receiver android:name="GrantUriPermission" android:exported="true">
+        </receiver>
+
         <!-- Need a way for another app to try to access the permission. So create a content
         provider which is enforced by the permission -->
         <provider android:name="PermissionContentProvider"
                 android:authorities="ctspermissionwithsignature"
                 android:readPermission="com.android.cts.permissionWithSignature"
-                android:writePermission="com.android.cts.permissionWithSignature" />
+                android:writePermission="com.android.cts.permissionWithSignature">
+        </provider>
+
+        <!-- Need a way for another app to try to access the permission, but will
+             grant uri access. -->
+        <provider android:name="PermissionContentProviderGranting"
+                android:authorities="ctspermissionwithsignaturegranting"
+                android:readPermission="com.android.cts.permissionWithSignature"
+                android:writePermission="com.android.cts.permissionWithSignature">
+            <grant-uri-permission android:pathPattern="/foo.*" />
+            <grant-uri-permission android:pathPattern="/yes.*" />
+        </provider>
+
+        <!-- Nobody else should get access to this -->
+        <provider android:name="PrivateContentProvider"
+                android:authorities="ctsprivateprovider"
+                android:exported="false">
+        </provider>
+
+        <!-- Nobody else should get access to this, but we will grant uri access -->
+        <provider android:name="PrivateContentProviderGranting"
+                android:authorities="ctsprivateprovidergranting"
+                android:exported="false">
+            <grant-uri-permission android:pathPattern="/foo.*" />
+            <grant-uri-permission android:pathPattern="/yes.*" />
+        </provider>
+
+        <!-- Target for tests about how path permissions interact with granting
+             URI permissions. -->
+        <provider android:name="PermissionContentProviderPath"
+                android:authorities="ctspermissionwithsignaturepath"
+                android:readPermission="com.android.cts.permissionNotUsedWithSignature"
+                android:writePermission="com.android.cts.permissionNotUsedWithSignature">
+            <path-permission
+                    android:pathPrefix="/foo"
+                    android:readPermission="com.android.cts.permissionWithSignature"
+                    android:writePermission="com.android.cts.permissionWithSignature" />
+            <path-permission
+                    android:pathPrefix="/yes"
+                    android:readPermission="com.android.cts.permissionWithSignature"
+                    android:writePermission="com.android.cts.permissionWithSignature" />
+            <grant-uri-permission android:pathPattern=".*" />
+        </provider>
+        
     </application>
 </manifest>
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
new file mode 100644
index 0000000..8c14575
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
@@ -0,0 +1,30 @@
+package com.android.cts.permissiondeclareapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class GrantUriPermission extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Intent newIntent = (Intent)intent.getParcelableExtra("intent");
+        boolean service = intent.getBooleanExtra("service", false);
+        try {
+            if (!service) {
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(newIntent);
+            } else {
+                context.startService(newIntent);
+            }
+            if (isOrderedBroadcast()) {
+                setResultCode(101);
+            }
+        } catch (SecurityException e) {
+            Log.i("GrantUriPermission", "Security exception", e);
+            if (isOrderedBroadcast()) {
+                setResultCode(100);
+            }
+        }
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProvider.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProvider.java
index e68bb1b..900664f 100644
--- a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProvider.java
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProvider.java
@@ -34,7 +34,7 @@
 
     @Override
     public String getType(Uri uri) {
-        return null;
+        return "got/theMIME";
     }
 
     @Override
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java
new file mode 100644
index 0000000..97bd827
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderGranting.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PermissionContentProviderGranting extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderPath.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderPath.java
new file mode 100644
index 0000000..4f301a3
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PermissionContentProviderPath.java
@@ -0,0 +1,45 @@
+package com.android.cts.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PermissionContentProviderPath extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java
new file mode 100644
index 0000000..64ec6e7
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PrivateContentProvider extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java
new file mode 100644
index 0000000..f9ae96b
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/PrivateContentProviderGranting.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class PrivateContentProviderGranting extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index b915ebc..e01bc90 100644
--- a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -17,14 +17,21 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.cts.usespermissiondiffcertapp">
 
-    <!--
-    This app declares it uses a permission that requires same signature as app that declares it.
-    -->
-
+    <!-- We say we want to use the other app's permission, but it is signed with
+         a different cert so it should fail. -->
     <uses-permission android:name="com.android.cts.permissionWithSignature"/>
 
+    <!-- This is a permission we can have, which the other app can require for
+         access. -->
+    <permission android:name="com.android.cts.permissionAllowedWithSignature"
+        android:protectionLevel="signature" />
+    <uses-permission android:name="com.android.cts.permissionAllowedWithSignature"/>
+
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name=".ReceiveUriActivity" android:exported="true"
+                android:launchMode="singleTop" />
+        <service android:name=".ReceiveUriService" android:exported="true" />
     </application>
 
     <instrumentation android:targetPackage="com.android.cts.usespermissiondiffcertapp"
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 510d5e4..63a65e4 100644
--- a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -16,21 +16,809 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
 import android.net.Uri;
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 /**
  * Tests that signature-enforced permissions cannot be accessed by apps signed
  * with different certs than app that declares the permission.
+ * 
+ * Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
  */
 public class AccessPermissionWithDiffSigTest extends AndroidTestCase {
+    static final ComponentName GRANT_URI_PERM_COMP
+            = new ComponentName("com.android.cts.permissiondeclareapp",
+                    "com.android.cts.permissiondeclareapp.GrantUriPermission");
+    static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
+    static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
+    static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
+    static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
+    static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
+
+    static final String EXPECTED_MIME_TYPE = "got/theMIME";
+    
+    public void assertReadingContentUriNotAllowed(Uri uri, String msg) {
+        try {
+            getContext().getContentResolver().query(uri, null, null, null, null);
+            fail("expected SecurityException reading " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
+
+    public void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+        try {
+            getContext().getContentResolver().insert(uri, new ContentValues());
+            fail("expected SecurityException writing " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
 
     /**
      * Test that the ctspermissionwithsignature content provider cannot be read,
      * since this app lacks the required certs
      */
     public void testReadProviderWithDiff() {
-        assertReadingContentUriRequiresPermission(Uri.parse("content://ctspermissionwithsignature"),
+        assertReadingContentUriRequiresPermission(PERM_URI,
                 "com.android.cts.permissionWithSignature");
     }
+
+    /**
+     * Test that the ctspermissionwithsignature content provider cannot be written,
+     * since this app lacks the required certs
+     */
+    public void testWriteProviderWithDiff() {
+        assertWritingContentUriRequiresPermission(PERM_URI,
+                "com.android.cts.permissionWithSignature");
+    }
+
+    /**
+     * Test that the ctsprivateprovider content provider cannot be read,
+     * since it is not exported from its app.
+     */
+    public void testReadProviderWhenPrivate() {
+        assertReadingContentUriNotAllowed(PRIV_URI,
+                "shouldn't read private provider");
+    }
+
+    /**
+     * Test that the ctsprivateprovider content provider cannot be written,
+     * since it is not exported from its app.
+     */
+    public void testWriteProviderWhenPrivate() {
+        assertWritingContentUriNotAllowed(PRIV_URI,
+                "shouldn't write private provider");
+    }
+
+    public void doTryGrantUriActivityPermissionToSelf(Uri uri, int mode) {
+        Intent grantIntent = new Intent();
+        grantIntent.setData(uri);
+        grantIntent.addFlags(mode | Intent.FLAG_ACTIVITY_NEW_TASK);
+        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
+        try {
+            ReceiveUriActivity.clearStarted();
+            getContext().startActivity(grantIntent);
+            ReceiveUriActivity.waitForStart();
+            fail("expected SecurityException granting " + uri + " to activity");
+        } catch (SecurityException e) {
+            // This is what we want.
+        }
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantReadUriActivityPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantWriteUriActivityPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantReadUriActivityPrivateToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantWriteUriActivityPrivateToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    public void doTryGrantUriServicePermissionToSelf(Uri uri, int mode) {
+        Intent grantIntent = new Intent();
+        grantIntent.setData(uri);
+        grantIntent.addFlags(mode);
+        grantIntent.setClass(getContext(), ReceiveUriService.class);
+        try {
+            getContext().startService(grantIntent);
+            fail("expected SecurityException granting " + uri + " to service");
+        } catch (SecurityException e) {
+            // This is what we want.
+        }
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantReadUriServicePermissionToSelf() {
+        doTryGrantUriServicePermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantWriteUriServicePermissionToSelf() {
+        doTryGrantUriServicePermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantReadUriServicePrivateToSelf() {
+        doTryGrantUriServicePermissionToSelf(
+                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that we can't grant a permission to ourself.
+     */
+    public void testGrantWriteUriServicePrivateToSelf() {
+        doTryGrantUriServicePermissionToSelf(
+                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    static class GrantResultReceiver extends BroadcastReceiver {
+        boolean mHaveResult = false;
+        boolean mGoodResult = false;
+        boolean mSucceeded = false;
+        
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (this) {
+                mHaveResult = true;
+                switch (getResultCode()) {
+                    case 100:
+                        mGoodResult = true;
+                        mSucceeded = false;
+                        break;
+                    case 101:
+                        mGoodResult = true;
+                        mSucceeded = true;
+                        break;
+                    default:
+                        mGoodResult = false;
+                        break;
+                }
+                notifyAll();
+            }
+        }
+        
+        void assertSuccess(String failureMessage) {
+            synchronized (this) {
+                final long startTime = SystemClock.uptimeMillis();
+                while (!mHaveResult) {
+                    try {
+                        wait(5000);
+                    } catch (InterruptedException e) {
+                    }
+                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                        throw new RuntimeException("Timeout");
+                    }
+                }
+                if (!mGoodResult) {
+                    fail("Broadcast receiver did not return good result");
+                }
+                if (!mSucceeded) {
+                    fail(failureMessage);
+                }
+            }
+        }
+        
+        void assertFailure(String failureMessage) {
+            synchronized (this) {
+                final long startTime = SystemClock.uptimeMillis();
+                while (!mHaveResult) {
+                    try {
+                        wait(5000);
+                    } catch (InterruptedException e) {
+                    }
+                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                        throw new RuntimeException("Timeout");
+                    }
+                }
+                if (!mGoodResult) {
+                    fail("Broadcast receiver did not return good result");
+                }
+                if (mSucceeded) {
+                    fail(failureMessage);
+                }
+            }
+        }
+    }
+    
+    void grantUriPermissionFail(Uri uri, int mode, boolean service) {
+        Intent grantIntent = new Intent();
+        grantIntent.setData(uri);
+        grantIntent.addFlags(mode);
+        grantIntent.setClass(getContext(),
+                service ? ReceiveUriService.class : ReceiveUriActivity.class);
+        Intent intent = new Intent();
+        intent.setComponent(GRANT_URI_PERM_COMP);
+        intent.putExtra("intent", grantIntent);
+        intent.putExtra("service", service);
+        GrantResultReceiver receiver = new GrantResultReceiver();
+        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
+        receiver.assertFailure("Able to grant URI permission to " + uri + " when should not");
+    }
+
+    void doTestGrantUriPermissionFail(Uri uri) {
+        grantUriPermissionFail(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
+        grantUriPermissionFail(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+        grantUriPermissionFail(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
+        grantUriPermissionFail(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
+    }
+    
+    /**
+     * Test that the ctspermissionwithsignature content provider can not grant
+     * URI permissions to others.
+     */
+    public void testGrantPermissionNonGrantingFail() {
+        doTestGrantUriPermissionFail(PERM_URI);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturegranting content provider can not grant
+     * URI permissions to paths outside of the grant tree
+     */
+    public void testGrantPermissionOutsideGrantingFail() {
+        doTestGrantUriPermissionFail(PERM_URI_GRANTING);
+        doTestGrantUriPermissionFail(Uri.withAppendedPath(PERM_URI_GRANTING, "invalid"));
+    }
+
+    /**
+     * Test that the ctsprivateprovider content provider can not grant
+     * URI permissions to others.
+     */
+    public void testGrantPrivateNonGrantingFail() {
+        doTestGrantUriPermissionFail(PRIV_URI);
+    }
+
+    /**
+     * Test that the ctsprivateprovidergranting content provider can not grant
+     * URI permissions to paths outside of the grant tree
+     */
+    public void testGrantPrivateOutsideGrantingFail() {
+        doTestGrantUriPermissionFail(PRIV_URI_GRANTING);
+        doTestGrantUriPermissionFail(Uri.withAppendedPath(PRIV_URI_GRANTING, "invalid"));
+    }
+
+    void grantUriPermission(Uri uri, int mode, boolean service) {
+        Intent grantIntent = new Intent();
+        grantIntent.setData(uri);
+        grantIntent.addFlags(mode);
+        grantIntent.setClass(getContext(),
+                service ? ReceiveUriService.class : ReceiveUriActivity.class);
+        Intent intent = new Intent();
+        intent.setComponent(GRANT_URI_PERM_COMP);
+        intent.putExtra("intent", grantIntent);
+        intent.putExtra("service", service);
+        getContext().sendBroadcast(intent);
+    }
+
+    void doTestGrantActivityUriReadPermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        // Precondition: no current access.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read when starting test");
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read when starting test");
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearStarted();
+        grantUriPermission(subUri, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().query(subUri, null, null, null, null);
+
+        // But not writing.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write from granted read");
+
+        // And not to the base path.
+        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
+
+        // And not to a sub path.
+        assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearNewIntent();
+        grantUriPermission(sub2Uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().query(sub2Uri, null, null, null, null);
+
+        // And still have access to the original URI.
+        getContext().getContentResolver().query(subUri, null, null, null, null);
+
+        // But not writing.
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write from granted read");
+
+        // And not to the base path.
+        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
+
+        // And not to a sub path.
+        assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
+
+        // And make sure we can't generate a permission to a running activity.
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(uri, "hah"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(uri, "hah"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // --------------------------------
+
+        // Dispose of activity.
+        ReceiveUriActivity.finishCurInstanceSync();
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // Ensure reading no longer allowed.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read after losing granted URI");
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read after losing granted URI");
+    }
+
+    void doTestGrantActivityUriWritePermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        // Precondition: no current access.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write when starting test");
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write when starting test");
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearStarted();
+        grantUriPermission(subUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().insert(subUri, new ContentValues());
+
+        // But not reading.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read from granted write");
+
+        // And not to the base path.
+        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
+
+        // And not a sub-path.
+        assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearNewIntent();
+        grantUriPermission(sub2Uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().insert(sub2Uri, new ContentValues());
+
+        // And still have access to the original URI.
+        getContext().getContentResolver().insert(subUri, new ContentValues());
+
+        // But not reading.
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read from granted write");
+
+        // And not to the base path.
+        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
+
+        // And not a sub-path.
+        assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
+
+        // And make sure we can't generate a permission to a running activity.
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(uri, "hah"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(uri, "hah"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // --------------------------------
+
+        // Dispose of activity.
+        ReceiveUriActivity.finishCurInstanceSync();
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // Ensure writing no longer allowed.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write after losing granted URI");
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write after losing granted URI");
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturegranting content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPermissionFromStartActivity() {
+        doTestGrantActivityUriReadPermission(PERM_URI_GRANTING);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturegranting content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePermissionFromStartActivity() {
+        doTestGrantActivityUriWritePermission(PERM_URI_GRANTING);
+    }
+
+    /**
+     * Test that the ctsprivateprovidergranting content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPrivateFromStartActivity() {
+        doTestGrantActivityUriReadPermission(PRIV_URI_GRANTING);
+    }
+
+    /**
+     * Test that the ctsprivateprovidergranting content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePrivateFromStartActivity() {
+        doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING);
+    }
+
+    void doTestGrantServiceUriReadPermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        ReceiveUriService.stop(getContext());
+
+        // Precondition: no current access.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read when starting test");
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read when starting test");
+
+        // --------------------------------
+
+        ReceiveUriService.clearStarted();
+        grantUriPermission(subUri, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
+        ReceiveUriService.waitForStart();
+
+        int firstStartId = ReceiveUriService.getCurStartId();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().query(subUri, null, null, null, null);
+
+        // But not writing.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write from granted read");
+
+        // And not to the base path.
+        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
+
+        // And not to a sub path.
+        assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
+
+        // --------------------------------
+
+        // Send another Intent to it.
+        ReceiveUriService.clearStarted();
+        grantUriPermission(sub2Uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
+        ReceiveUriService.waitForStart();
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().query(sub2Uri, null, null, null, null);
+
+        // And still to the previous URI.
+        getContext().getContentResolver().query(subUri, null, null, null, null);
+
+        // But not writing.
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write from granted read");
+
+        // And not to the base path.
+        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
+
+        // And not to a sub path.
+        assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
+
+        // --------------------------------
+
+        // Stop the first command.
+        ReceiveUriService.stopCurWithId(firstStartId);
+
+        // Ensure reading no longer allowed.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read after losing granted URI");
+
+        // And make sure we can't generate a permission to a running service.
+        doTryGrantUriActivityPermissionToSelf(subUri,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        doTryGrantUriActivityPermissionToSelf(subUri,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // --------------------------------
+
+        // Dispose of service.
+        ReceiveUriService.stopSync(getContext());
+
+        // Ensure reading no longer allowed.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read after losing granted URI");
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read after losing granted URI");
+    }
+
+    void doTestGrantServiceUriWritePermission(Uri uri) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        ReceiveUriService.stop(getContext());
+
+        // Precondition: no current access.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write when starting test");
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write when starting test");
+
+        // --------------------------------
+
+        ReceiveUriService.clearStarted();
+        grantUriPermission(subUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
+        ReceiveUriService.waitForStart();
+
+        int firstStartId = ReceiveUriService.getCurStartId();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().insert(subUri, new ContentValues());
+
+        // But not reading.
+        assertReadingContentUriNotAllowed(subUri, "shouldn't read from granted write");
+
+        // And not to the base path.
+        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
+
+        // And not a sub-path.
+        assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
+
+        // --------------------------------
+
+        // Send another Intent to it.
+        ReceiveUriService.clearStarted();
+        grantUriPermission(sub2Uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
+        ReceiveUriService.waitForStart();
+
+        // See if we now have access to the provider.
+        getContext().getContentResolver().insert(sub2Uri, new ContentValues());
+
+        // And still to the previous URI.
+        getContext().getContentResolver().insert(subUri, new ContentValues());
+
+        // But not reading.
+        assertReadingContentUriNotAllowed(sub2Uri, "shouldn't read from granted write");
+
+        // And not to the base path.
+        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
+
+        // And not a sub-path.
+        assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
+
+        if (false) {
+            synchronized (this) {
+                Log.i("**", "******************************* WAITING!!!");
+                try {
+                    wait(10000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        // --------------------------------
+
+        // Stop the first command.
+        ReceiveUriService.stopCurWithId(firstStartId);
+
+        // Ensure writing no longer allowed.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write after losing granted URI");
+
+        // And make sure we can't generate a permission to a running service.
+        doTryGrantUriActivityPermissionToSelf(subUri,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        doTryGrantUriActivityPermissionToSelf(subUri,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // --------------------------------
+
+        // Dispose of service.
+        ReceiveUriService.stopSync(getContext());
+
+        // Ensure writing no longer allowed.
+        assertWritingContentUriNotAllowed(subUri, "shouldn't write after losing granted URI");
+        assertWritingContentUriNotAllowed(sub2Uri, "shouldn't write after losing granted URI");
+    }
+
+    public void testGrantReadPermissionFromStartService() {
+        doTestGrantServiceUriReadPermission(PERM_URI_GRANTING);
+    }
+
+    public void testGrantWritePermissionFromStartService() {
+        doTestGrantServiceUriWritePermission(PERM_URI_GRANTING);
+    }
+
+    public void testGrantReadPrivateFromStartService() {
+        doTestGrantServiceUriReadPermission(PRIV_URI_GRANTING);
+    }
+
+    public void testGrantWritePrivateFromStartService() {
+        doTestGrantServiceUriWritePermission(PRIV_URI_GRANTING);
+    }
+
+    /**
+     * Test that ctspermissionwithsignaturepath can't grant read permissions
+     * on paths it doesn't have permission to.
+     */
+    public void testGrantReadUriActivityPathPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that ctspermissionwithsignaturepath can't grant write permissions
+     * on paths it doesn't have permission to.
+     */
+    public void testGrantWriteUriActivityPathPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    /**
+     * Test that ctspermissionwithsignaturepath can't grant read permissions
+     * on paths it doesn't have permission to.
+     */
+    public void testGrantReadUriActivitySubPathPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_PATH, "foo"),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+    }
+
+    /**
+     * Test that ctspermissionwithsignaturepath can't grant write permissions
+     * on paths it doesn't have permission to.
+     */
+    public void testGrantWriteUriActivitySubPathPermissionToSelf() {
+        doTryGrantUriActivityPermissionToSelf(
+                Uri.withAppendedPath(PERM_URI_PATH, "foo"),
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturepath content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPathPermissionFromStartActivity() {
+        doTestGrantActivityUriReadPermission(PERM_URI_PATH);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturepath content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePathPermissionFromStartActivity() {
+        doTestGrantActivityUriWritePermission(PERM_URI_PATH);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturepath content provider can grant a read
+     * permission.
+     */
+    public void testGrantReadPathPermissionFromStartService() {
+        doTestGrantServiceUriReadPermission(PERM_URI_PATH);
+    }
+
+    /**
+     * Test that the ctspermissionwithsignaturepath content provider can grant a write
+     * permission.
+     */
+    public void testGrantWritePathPermissionFromStartService() {
+        doTestGrantServiceUriWritePermission(PERM_URI_PATH);
+    }
+
+    public void testGetMimeTypePermission() {
+        // Precondition: no current access.
+        assertWritingContentUriNotAllowed(PERM_URI, "shouldn't write when starting test");
+        assertWritingContentUriNotAllowed(PERM_URI, "shouldn't write when starting test");
+        
+        // All apps should be able to get MIME type regardless of permission.
+        assertEquals(getContext().getContentResolver().getType(PERM_URI), EXPECTED_MIME_TYPE);
+    }
+
+    public void testGetMimeTypePrivate() {
+        // Precondition: no current access.
+        assertWritingContentUriNotAllowed(PRIV_URI, "shouldn't write when starting test");
+        assertWritingContentUriNotAllowed(PRIV_URI, "shouldn't write when starting test");
+        
+        // All apps should be able to get MIME type even if provider is private.
+        assertEquals(getContext().getContentResolver().getType(PRIV_URI), EXPECTED_MIME_TYPE);
+    }
 }
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
new file mode 100644
index 0000000..0ddc4a5
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
@@ -0,0 +1,148 @@
+/*
+ * 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.usespermissiondiffcertapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.MessageQueue.IdleHandler;
+import android.util.Log;
+
+public class ReceiveUriActivity extends Activity {
+    static final String TAG = "ReceiveUriActivity";
+    private static final Object sLock = new Object();
+    private static boolean sStarted;
+    private static boolean sNewIntent;
+    private static boolean sDestroyed;
+    private static ReceiveUriActivity sCurInstance;
+
+    Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        synchronized (sLock) {
+            Log.i(TAG, "onCreate: sCurInstance=" + sCurInstance);
+            if (sCurInstance != null) {
+                finishCurInstance();
+            }
+            sCurInstance = this;
+            sStarted = true;
+            sDestroyed = false;
+            sLock.notifyAll();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        synchronized (sLock) {
+            Log.i(TAG, "onNewIntent: sCurInstance=" + sCurInstance);
+            sNewIntent = true;
+            sLock.notifyAll();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        Log.i(TAG, "onDestroy: sCurInstance=" + sCurInstance);
+        Looper.myQueue().addIdleHandler(new IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                synchronized (sLock) {
+                    sDestroyed = true;
+                    sLock.notifyAll();
+                }
+                return false;
+            }
+        });
+    }
+
+    public static void finishCurInstance() {
+        synchronized (sLock) {
+            if (sCurInstance != null) {
+                sCurInstance.finish();
+                sCurInstance = null;
+            }
+        }
+    }
+
+    public static void finishCurInstanceSync() {
+        finishCurInstance();
+
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sDestroyed) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+
+    public static void clearStarted() {
+        synchronized (sLock) {
+            sStarted = false;
+        }
+    }
+
+    public static void clearNewIntent() {
+        synchronized (sLock) {
+            sNewIntent = false;
+        }
+    }
+
+    public static void waitForStart() {
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sStarted) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+
+    public static void waitForNewIntent() {
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sNewIntent) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+}
diff --git a/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriService.java b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriService.java
new file mode 100644
index 0000000..68d78c0
--- /dev/null
+++ b/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriService.java
@@ -0,0 +1,129 @@
+/*
+ * 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.usespermissiondiffcertapp;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.MessageQueue.IdleHandler;
+
+public class ReceiveUriService extends Service {
+    private static final Object sLock = new Object();
+    private static boolean sStarted;
+    private static boolean sDestroyed;
+    private static int sCurStartId;
+    private static ReceiveUriService sCurInstance;
+
+    Handler mHandler = new Handler();
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        synchronized (sLock) {
+            sCurStartId = startId;
+            sCurInstance = this;
+            sStarted = true;
+            sDestroyed = false;
+            sLock.notifyAll();
+        }
+
+        return START_REDELIVER_INTENT;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Looper.myQueue().addIdleHandler(new IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                synchronized (sLock) {
+                    sDestroyed = true;
+                    sCurInstance = null;
+                    sLock.notifyAll();
+                }
+                return false;
+            }
+        });
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    public static void stop(Context context) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(
+                "com.android.cts.usespermissiondiffcertapp",
+                "com.android.cts.usespermissiondiffcertapp.ReceiveUriService"));
+        context.stopService(intent);
+    }
+
+    public static int getCurStartId() {
+        synchronized (sLock) {
+            return sCurStartId;
+        }
+    }
+
+    public static void stopCurWithId(int id) {
+        synchronized (sLock) {
+            sCurInstance.stopSelf(id);
+        }
+    }
+
+    public static void stopSync(Context context) {
+        stop(context);
+
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sDestroyed) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+
+    public static void clearStarted() {
+        synchronized (sLock) {
+            sStarted = false;
+        }
+    }
+
+    public static void waitForStart() {
+        synchronized (sLock) {
+            final long startTime = SystemClock.uptimeMillis();
+            while (!sStarted) {
+                try {
+                    sLock.wait(5000);
+                } catch (InterruptedException e) {
+                }
+                if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    throw new RuntimeException("Timeout");
+                }
+            }
+        }
+    }
+}
diff --git a/tests/core/annotation/Android.mk b/tests/core/annotation/Android.mk
deleted file mode 100644
index 4778710..0000000
--- a/tests/core/annotation/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Annotation Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/annotation/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.annotation
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/annotation/AndroidManifest.xml b/tests/core/annotation/AndroidManifest.xml
deleted file mode 100644
index 7e34d2a..0000000
--- a/tests/core/annotation/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.annotation">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/archive/Android.mk b/tests/core/archive/Android.mk
deleted file mode 100644
index 78edd09..0000000
--- a/tests/core/archive/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Archive Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/archive/src/test/java/org) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.archive
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/archive/AndroidManifest.xml b/tests/core/archive/AndroidManifest.xml
deleted file mode 100644
index 22ba6f7..0000000
--- a/tests/core/archive/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.archive">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/concurrent/Android.mk b/tests/core/concurrent/Android.mk
deleted file mode 100644
index d42c6da..0000000
--- a/tests/core/concurrent/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Concurrent Tests
-##########################################################
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/concurrent/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.concurrent
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/concurrent/AndroidManifest.xml b/tests/core/concurrent/AndroidManifest.xml
deleted file mode 100644
index b3ee187..0000000
--- a/tests/core/concurrent/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.concurrent">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/crypto/Android.mk b/tests/core/crypto/Android.mk
deleted file mode 100644
index 2450ad1..0000000
--- a/tests/core/crypto/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# crypto Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/crypto/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java/) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.crypto
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/crypto/AndroidManifest.xml b/tests/core/crypto/AndroidManifest.xml
deleted file mode 100644
index 25052ee..0000000
--- a/tests/core/crypto/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.crypto">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/ctscore.mk b/tests/core/ctscore.mk
index ab7f7be..33fc6f3 100644
--- a/tests/core/ctscore.mk
+++ b/tests/core/ctscore.mk
@@ -17,7 +17,7 @@
 # and when built explicitly put them in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle
 LOCAL_STATIC_JAVA_LIBRARIES := CtsTestAnnotationsLib
 
 LOCAL_PROGUARD_ENABLED := disabled
diff --git a/tests/core/dom/Android.mk b/tests/core/dom/Android.mk
index 47b3dbb..69d4af3 100644
--- a/tests/core/dom/Android.mk
+++ b/tests/core/dom/Android.mk
@@ -23,10 +23,13 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/dom/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := $(call all-java-files-under,../../../../libcore/dom/src/test/java) \
+	$(call all-java-files-under,../../../../libcore/junit/src/test/java/junit) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java/)
 
 LOCAL_PACKAGE_NAME := android.core.tests.dom
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/logging/Android.mk b/tests/core/logging/Android.mk
deleted file mode 100644
index 89d1ca9..0000000
--- a/tests/core/logging/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Logging Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/logging/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.logging
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/logging/AndroidManifest.xml b/tests/core/logging/AndroidManifest.xml
deleted file mode 100644
index c6e5bee..0000000
--- a/tests/core/logging/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.logging">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/luni-io/Android.mk b/tests/core/luni-io/Android.mk
index f2aae36..ed39e03 100644
--- a/tests/core/luni-io/Android.mk
+++ b/tests/core/luni-io/Android.mk
@@ -23,14 +23,16 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/tests/api/java/io) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/io) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java/) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/pkg1) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/pkg2) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/luni/AllTestsIo.java \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := $(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/java/io) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/io) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java/) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/pkg1) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/pkg2) \
+	../../../../libcore/luni/src/test/java/tests/luni/AllTestsIo.java
 
 LOCAL_PACKAGE_NAME := android.core.tests.luni.io
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/luni-lang/Android.mk b/tests/core/luni-lang/Android.mk
index 225bdf9..a31b881 100644
--- a/tests/core/luni-lang/Android.mk
+++ b/tests/core/luni-lang/Android.mk
@@ -23,12 +23,14 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/lang) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/tests/api/java/lang) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java/) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/luni/AllTestsLang.java \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := $(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/lang) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/java/lang) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java/) \
+	../../../../libcore/luni/src/test/java/tests/luni/AllTestsLang.java
 
 LOCAL_PACKAGE_NAME := android.core.tests.luni.lang
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/luni-net/Android.mk b/tests/core/luni-net/Android.mk
index 1092ff1..8a6508c 100644
--- a/tests/core/luni-net/Android.mk
+++ b/tests/core/luni-net/Android.mk
@@ -23,14 +23,17 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/net) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/tests/api/java/net) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/http) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java/) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/luni/AllTestsNet.java \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/libcore/java/net) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/http) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/internal/net/www/protocol/https) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/net) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java/) \
+	../../../../libcore/luni/src/test/java/tests/luni/AllTestsNet.java
 
 LOCAL_PACKAGE_NAME := android.core.tests.luni.net
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/luni-util/Android.mk b/tests/core/luni-util/Android.mk
index 101cde8..5468c17 100644
--- a/tests/core/luni-util/Android.mk
+++ b/tests/core/luni-util/Android.mk
@@ -23,12 +23,14 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/util) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/tests/api/java/util) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java/) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/luni/AllTestsUtil.java \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := $(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/luni/tests/java/util) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/java/util) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java/) \
+	../../../../libcore/luni/src/test/java/tests/luni/AllTestsUtil.java
 
 LOCAL_PACKAGE_NAME := android.core.tests.luni.util
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/math/Android.mk b/tests/core/math/Android.mk
deleted file mode 100644
index 4c559cd..0000000
--- a/tests/core/math/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Math Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/math/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.math
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/math/AndroidManifest.xml b/tests/core/math/AndroidManifest.xml
deleted file mode 100644
index f87fa13..0000000
--- a/tests/core/math/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.math">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/nio/Android.mk b/tests/core/nio/Android.mk
deleted file mode 100644
index 6ebd1bc..0000000
--- a/tests/core/nio/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Nio Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/nio/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.nio
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/nio/AndroidManifest.xml b/tests/core/nio/AndroidManifest.xml
deleted file mode 100644
index 4c79bfa..0000000
--- a/tests/core/nio/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.nio">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/nio_char/Android.mk b/tests/core/nio_char/Android.mk
deleted file mode 100644
index e3b463a..0000000
--- a/tests/core/nio_char/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# NioChar Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/nio_char/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.nio_char
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/nio_char/AndroidManifest.xml b/tests/core/nio_char/AndroidManifest.xml
deleted file mode 100644
index b8de2c9..0000000
--- a/tests/core/nio_char/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.nio_char">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/prefs/Android.mk b/tests/core/prefs/Android.mk
deleted file mode 100644
index f8f225c..0000000
--- a/tests/core/prefs/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Prefs Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/prefs/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.prefs
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/prefs/AndroidManifest.xml b/tests/core/prefs/AndroidManifest.xml
deleted file mode 100644
index d994c72..0000000
--- a/tests/core/prefs/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.prefs">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/regex/Android.mk b/tests/core/regex/Android.mk
deleted file mode 100644
index 70927a0..0000000
--- a/tests/core/regex/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Regex Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/regex/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.regex
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/regex/AndroidManifest.xml b/tests/core/regex/AndroidManifest.xml
deleted file mode 100644
index ddd2165..0000000
--- a/tests/core/regex/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.regex">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java b/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
index b5f1919..21eb2a6 100644
--- a/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
+++ b/tests/core/runner/src/android/test/InstrumentationCtsTestRunner.java
@@ -87,6 +87,7 @@
         System.setProperty("java.home", cacheDir.getAbsolutePath());
         System.setProperty("user.home", cacheDir.getAbsolutePath());
         System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+        System.setProperty("user.dir", cacheDir.getAbsolutePath());
         System.setProperty("javax.net.ssl.trustStore",
                 "/etc/security/cacerts.bks");
 
diff --git a/tests/core/security/Android.mk b/tests/core/security/Android.mk
deleted file mode 100644
index 76c0ca5..0000000
--- a/tests/core/security/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Security Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/security/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.security
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/security/AndroidManifest.xml b/tests/core/security/AndroidManifest.xml
deleted file mode 100644
index 3960a82..0000000
--- a/tests/core/security/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.security">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/sql/Android.mk b/tests/core/sql/Android.mk
deleted file mode 100644
index fe9b4cc..0000000
--- a/tests/core/sql/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Sql Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/sql/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.sql
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/sql/AndroidManifest.xml b/tests/core/sql/AndroidManifest.xml
deleted file mode 100644
index 0ebcab6..0000000
--- a/tests/core/sql/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.sql">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/text/Android.mk b/tests/core/text/Android.mk
deleted file mode 100644
index 8b9fd1f..0000000
--- a/tests/core/text/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Text Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/text/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.text
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/text/AndroidManifest.xml b/tests/core/text/AndroidManifest.xml
deleted file mode 100644
index baf0031..0000000
--- a/tests/core/text/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.text">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/xml/Android.mk b/tests/core/xml/Android.mk
index ba784b9..4f8af07 100644
--- a/tests/core/xml/Android.mk
+++ b/tests/core/xml/Android.mk
@@ -23,12 +23,20 @@
 ##########################################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/xml/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/dom/src/test) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
+LOCAL_SRC_FILES := $(call all-java-files-under,../../../../libcore/xml/src/test/java) \
+	$(call all-java-files-under,../../../../libcore/dom/src/test) \
+	$(call all-java-files-under,../../../../libcore/junit/src/test/java/junit) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/org/apache/harmony/xml) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/javax/xml/parsers) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/org/xml/sax) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/api/org/xml/sax/support) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/org/w3c/dom) \
+	$(call all-java-files-under,../../../../libcore/luni/src/test/java/tests/xml) \
+	$(call all-java-files-under,../../../../libcore/support/src/test/java)
 
 LOCAL_PACKAGE_NAME := android.core.tests.xml
 
+# for java.* javax.* support classes in libcore/support/src/test/java
+LOCAL_DX_FLAGS := --core-library
+
 include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/xnet/Android.mk b/tests/core/xnet/Android.mk
deleted file mode 100644
index 2415f38..0000000
--- a/tests/core/xnet/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-ifeq ($(BUILD_CTSCORE_PACKAGE),)
-    $(error BUILD_CTSCORE_PACKAGE must be defined)
-endif
-
-#
-# Xnet Tests
-##########################################################
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under,../../../../dalvik/libcore/x-net/src/test/java) \
-	$(call all-java-files-under,../../../../dalvik/libcore/luni/src/test/java/junit) \
-	$(call all-java-files-under,../../../../dalvik/libcore/support/src/test/java) \
-	../../../../dalvik/libcore/luni/src/test/java/tests/TestSuiteFactory.java
-
-LOCAL_PACKAGE_NAME := android.core.tests.xnet
-
-include $(BUILD_CTSCORE_PACKAGE)
diff --git a/tests/core/xnet/AndroidManifest.xml b/tests/core/xnet/AndroidManifest.xml
deleted file mode 100644
index 82e5dd7..0000000
--- a/tests/core/xnet/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.core.tests.xnet">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationCtsTestRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/res/values-large/configVarying.xml b/tests/res/values-large/configVarying.xml
new file mode 100755
index 0000000..7b2df7c
--- /dev/null
+++ b/tests/res/values-large/configVarying.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+    <item type="configVarying" name="simple">simple large</item>
+    <bag type="configVarying" name="bag">
+        <item name="testString">bag large</item>
+    </bag>
+    <item type="configVarying" name="large">large</item>
+</resources>
diff --git a/tests/res/values-normal/configVarying.xml b/tests/res/values-normal/configVarying.xml
new file mode 100755
index 0000000..b45ee49
--- /dev/null
+++ b/tests/res/values-normal/configVarying.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+    <item type="configVarying" name="simple">simple normal</item>
+    <bag type="configVarying" name="bag">
+        <item name="testString">bag normal</item>
+    </bag>
+    <item type="configVarying" name="normal">normal</item>
+</resources>
diff --git a/tests/res/values-small/configVarying.xml b/tests/res/values-small/configVarying.xml
new file mode 100755
index 0000000..15a9f8f
--- /dev/null
+++ b/tests/res/values-small/configVarying.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+    <item type="configVarying" name="simple">simple small</item>
+    <bag type="configVarying" name="bag">
+        <item name="testString">bag small</item>
+    </bag>
+    <item type="configVarying" name="small">small</item>
+</resources>
diff --git a/tests/res/values-xlarge/configVarying.xml b/tests/res/values-xlarge/configVarying.xml
new file mode 100755
index 0000000..fb9cad7
--- /dev/null
+++ b/tests/res/values-xlarge/configVarying.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+    <item type="configVarying" name="simple">simple xlarge</item>
+    <bag type="configVarying" name="bag">
+        <item name="testString">bag xlarge</item>
+    </bag>
+    <item type="configVarying" name="xlarge">xlarge</item>
+</resources>
diff --git a/tests/res/values/configVarying.xml b/tests/res/values/configVarying.xml
index a2d5b97..de1b09e 100755
--- a/tests/res/values/configVarying.xml
+++ b/tests/res/values/configVarying.xml
@@ -19,4 +19,8 @@
     <bag type="configVarying" name="bag">
         <item name="testString">bag default</item>
     </bag>
+    <item type="configVarying" name="small">default</item>
+    <item type="configVarying" name="normal">default</item>
+    <item type="configVarying" name="large">default</item>
+    <item type="configVarying" name="xlarge">default</item>
 </resources>
diff --git a/tests/src/android/hardware/cts/CameraStubActivity.java b/tests/src/android/hardware/cts/CameraStubActivity.java
index 50bd13b..8ab7fbd 100644
--- a/tests/src/android/hardware/cts/CameraStubActivity.java
+++ b/tests/src/android/hardware/cts/CameraStubActivity.java
@@ -24,9 +24,7 @@
 import com.android.cts.stub.R;
 
 public class CameraStubActivity extends Activity {
-
-    public static SurfaceView mSurfaceView;
-
+    private SurfaceView mSurfaceView;
     private final int LAYOUT_WIDTH = 480;
     private final int LAYOUT_HEIGHT = 320;
 
@@ -43,4 +41,8 @@
         mSurfaceView.getHolder().setFixedSize(LAYOUT_WIDTH, LAYOUT_HEIGHT);
         mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
     }
+
+    public SurfaceView getSurfaceView() {
+        return mSurfaceView;
+    }
 }
diff --git a/tests/src/android/widget/cts/WidgetTestUtils.java b/tests/src/android/widget/cts/WidgetTestUtils.java
index 0a8f6dc..2df6629 100644
--- a/tests/src/android/widget/cts/WidgetTestUtils.java
+++ b/tests/src/android/widget/cts/WidgetTestUtils.java
@@ -48,7 +48,8 @@
         }
 
         // b1 and b2 are all not null.
-        if (b1.getWidth() != b2.getWidth() || b1.getHeight() != b2.getHeight()) {
+        if (b1.getWidth() != b2.getWidth() || b1.getHeight() != b2.getHeight()
+            || b1.getConfig() != b2.getConfig()) {
             Assert.fail("the bitmaps are not equal");
         }
 
@@ -119,4 +120,19 @@
         options.inScaled = false;
         return BitmapFactory.decodeResource(resources, resId, options);
     }
+
+    /**
+     * Retrieve a dithered bitmap that can be used for comparison on any density
+     * @param resources
+     * @param config the preferred config for the returning bitmap
+     * @return the {@link Bitmap} or <code>null</code>
+     */
+    public static Bitmap getUnscaledAndDitheredBitmap(Resources resources,
+            int resId, Bitmap.Config config) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inDither = true;
+        options.inScaled = false;
+        options.inPreferredConfig = config;
+        return BitmapFactory.decodeResource(resources, resId, options);
+    }
 }
diff --git a/tests/tests/app/src/android/app/cts/DialogTest.java b/tests/tests/app/src/android/app/cts/DialogTest.java
index 4d02896..6a48358 100644
--- a/tests/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/tests/app/src/android/app/cts/DialogTest.java
@@ -518,24 +518,22 @@
         assertTrue(d.onKeyDownReturn);
     }
 
-    @TestTargetNew(
-        level = TestLevel.COMPLETE,
-        method = "onKeyMultiple",
-        args = {int.class, int.class, android.view.KeyEvent.class}
-    )
-    public void testOnKeyMultiple() {
-        popDialog(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        final TestDialog d = (TestDialog) mActivity.getDialog();
+     @TestTargetNew(
+         level = TestLevel.COMPLETE,
+         method = "onKeyMultiple",
+         args = {int.class, int.class, android.view.KeyEvent.class}
+     )
+     public void testOnKeyMultiple() {
+         popDialog(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
+         final TestDialog d = (TestDialog) mActivity.getDialog();
 
-        // System call onTouchEvent
-        assertNull(d.keyMultipleEvent);
-        mInstrumentation.sendKeySync(
-                new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_UNKNOWN));
-        assertTrue(d.isOnKeyMultipleCalled);
-        assertFalse(d.onKeyMultipleReturn);
-        assertEquals(KeyEvent.KEYCODE_UNKNOWN, d.keyMultipleEvent.getKeyCode());
-        assertEquals(KeyEvent.ACTION_MULTIPLE, d.keyMultipleEvent.getAction());
-    }
+         assertNull(d.keyMultipleEvent);
+         d.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_UNKNOWN));
+         assertTrue(d.isOnKeyMultipleCalled);
+         assertFalse(d.onKeyMultipleReturn);
+         assertEquals(KeyEvent.KEYCODE_UNKNOWN, d.keyMultipleEvent.getKeyCode());
+         assertEquals(KeyEvent.ACTION_MULTIPLE, d.keyMultipleEvent.getAction());
+     }
 
     @TestTargets({
         @TestTargetNew(
diff --git a/tests/tests/app/src/android/app/cts/InstrumentationTest.java b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
index 5ce33a0..1861ae2 100644
--- a/tests/tests/app/src/android/app/cts/InstrumentationTest.java
+++ b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
@@ -36,10 +36,12 @@
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.test.InstrumentationTestCase;
+import android.view.InputQueue;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.SurfaceHolder;
 import android.view.View;
 import android.view.Window;
 import android.view.ViewGroup.LayoutParams;
@@ -956,6 +958,14 @@
             @Override
             public void togglePanel(int featureId, KeyEvent event) {
             }
+            
+            @Override
+            public void takeSurface(SurfaceHolder.Callback2 callback) {
+            }
+            
+            @Override
+            public void takeInputQueue(InputQueue.Callback queue) {
+            }
         }
     }
 
diff --git a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
index ac3d03a..d4fabac 100644
--- a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -30,9 +30,12 @@
 import android.hardware.Camera;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
+import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
 import android.location.LocationManager;
+import android.net.sip.SipManager;
 import android.net.wifi.WifiManager;
+import android.nfc.NfcAdapter;
 import android.telephony.TelephonyManager;
 import android.test.InstrumentationTestCase;
 
@@ -104,10 +107,39 @@
     }
 
     public void testCameraFeatures() {
+        int numCameras = Camera.getNumberOfCameras();
+        if (numCameras == 0) {
+            assertNotAvailable(PackageManager.FEATURE_CAMERA);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_AUTOFOCUS);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_FRONT);
+        } else {
+            checkFrontCamera();
+            checkRearCamera();
+        }
+    }
+
+    private void checkFrontCamera() {
+        CameraInfo info = new CameraInfo();
+        int numCameras = Camera.getNumberOfCameras();
+        int frontCameraId = -1;
+        for (int i = 0; i < numCameras; i++) {
+            Camera.getCameraInfo(i, info);
+            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+                frontCameraId = i;
+            }
+        }
+
+        if (frontCameraId > -1) {
+            assertAvailable(PackageManager.FEATURE_CAMERA_FRONT);
+        } else {
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_FRONT);
+        }
+    }
+
+    private void checkRearCamera() {
         Camera camera = null;
         try {
-            // Try getting a camera. This is unlikely to fail but implentations without a camera
-            // could return null or throw an exception.
             camera = Camera.open();
             if (camera != null) {
                 assertAvailable(PackageManager.FEATURE_CAMERA);
@@ -129,10 +161,6 @@
                 assertNotAvailable(PackageManager.FEATURE_CAMERA_AUTOFOCUS);
                 assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
             }
-        } catch (RuntimeException e) {
-            assertNotAvailable(PackageManager.FEATURE_CAMERA);
-            assertNotAvailable(PackageManager.FEATURE_CAMERA_AUTOFOCUS);
-            assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
         } finally {
             if (camera != null) {
                 camera.release();
@@ -167,6 +195,14 @@
         }
     }
 
+    public void testNfcFeatures() {
+        if (NfcAdapter.getDefaultAdapter() != null) {
+            assertAvailable(PackageManager.FEATURE_NFC);
+        } else {
+            assertNotAvailable(PackageManager.FEATURE_NFC);
+        }
+    }
+
     /**
      * Check that the sensor features reported by the PackageManager correspond to the sensors
      * returned by {@link SensorManager#getSensorList(int)}.
@@ -176,8 +212,12 @@
 
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_ACCELEROMETER,
                 Sensor.TYPE_ACCELEROMETER);
+        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_BAROMETER,
+                Sensor.TYPE_PRESSURE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_COMPASS,
                 Sensor.TYPE_MAGNETIC_FIELD);
+        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_GYROSCOPE,
+                Sensor.TYPE_GYROSCOPE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_LIGHT,
                 Sensor.TYPE_LIGHT);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_PROXIMITY,
@@ -201,6 +241,29 @@
         return features;
     }
 
+    public void testSipFeatures() {
+        if (SipManager.newInstance(mContext) != null) {
+            assertAvailable(PackageManager.FEATURE_SIP);
+        } else {
+            assertNotAvailable(PackageManager.FEATURE_SIP);
+            assertNotAvailable(PackageManager.FEATURE_SIP_VOIP);
+        }
+
+        if (SipManager.isApiSupported(mContext)) {
+            assertAvailable(PackageManager.FEATURE_SIP);
+        } else {
+            assertNotAvailable(PackageManager.FEATURE_SIP);
+            assertNotAvailable(PackageManager.FEATURE_SIP_VOIP);
+        }
+
+        if (SipManager.isVoipSupported(mContext)) {
+            assertAvailable(PackageManager.FEATURE_SIP);
+            assertAvailable(PackageManager.FEATURE_SIP_VOIP);
+        } else {
+            assertNotAvailable(PackageManager.FEATURE_SIP_VOIP);
+        }
+    }
+
     /**
      * Check that if the PackageManager declares a sensor feature that the device has at least
      * one sensor that matches that feature. Also check that if a PackageManager does not declare
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
new file mode 100644
index 0000000..bc81c4c
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.content.cts;
+
+import com.android.cts.stub.R;
+
+import dalvik.annotation.BrokenTest;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+import dalvik.annotation.ToBeFixed;
+
+import android.app.QueuedWork;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Test {@link SharedPreferences}.
+ */
+@TestTargetClass(SharedPreferences.class)
+public class SharedPreferencesTest extends AndroidTestCase {
+    private static final String TAG = "SharedPreferencesTest";
+
+    private Context mContext;
+    private ContextWrapper mContextWrapper;
+
+    private File mPrefsFile;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getContext();
+        mContextWrapper = new ContextWrapper(mContext);
+
+        SharedPreferences prefs = getPrefs();
+        prefs.edit().clear().commit();
+
+        // Duplicated from ContextImpl.java.  Not ideal, but there wasn't a better
+        // way to reach into Context{Wrapper,Impl} to ask where this file lives.
+        mPrefsFile = new File("/data/data/com.android.cts.stub/shared_prefs",
+                              "com.android.cts.stub_preferences.xml");
+        mPrefsFile.delete();
+    }
+
+    private SharedPreferences getPrefs() {
+        return PreferenceManager.getDefaultSharedPreferences(mContext);
+    }
+
+    public void testNoFileInitially() {
+        assertFalse(mPrefsFile.exists());
+    }
+
+    public void testCommitCreatesFiles() {
+        SharedPreferences prefs = getPrefs();
+        assertFalse(mPrefsFile.exists());
+        prefs.edit().putString("foo", "bar").commit();
+        assertTrue(mPrefsFile.exists());
+    }
+
+    public void testDefaults() {
+        SharedPreferences prefs = getPrefs();
+        String key = "not-set";
+        assertFalse(prefs.contains(key));
+        assertEquals(0, prefs.getAll().size());
+        assertTrue(prefs.getAll().isEmpty());
+        assertEquals(false, prefs.getBoolean(key, false));
+        assertEquals(true, prefs.getBoolean(key, true));
+        assertEquals(0.5f, prefs.getFloat(key, 0.5f));
+        assertEquals(123, prefs.getInt(key, 123));
+        assertEquals(999L, prefs.getLong(key, 999L));
+        assertEquals("default", prefs.getString(key, "default"));
+    }
+
+    private abstract class RedundantWriteTest {
+        // Do some initial operation on editor.  No commit needed.
+        public abstract void setUp(SharedPreferences.Editor editor);
+
+        // Do some later operation on editor (e.g. a redundant edit).
+        // No commit needed.
+        public abstract void subsequentEdit(SharedPreferences.Editor editor);
+
+        public boolean expectingMutation() {
+            return false;
+        }
+
+        // Tests that a redundant edit after an initital setup doesn't
+        // result in a duplicate write-out to disk.
+        public final void test() throws Exception {
+            SharedPreferences prefs = getPrefs();
+            SharedPreferences.Editor editor;
+
+            assertFalse(mPrefsFile.exists());
+            prefs.edit().commit();
+            assertTrue(mPrefsFile.exists());
+
+            editor = prefs.edit();
+            setUp(editor);
+            editor.commit();
+            long modtimeMillis1 = mPrefsFile.lastModified();
+
+            // Wait a second and modify the preferences in a dummy,
+            // redundant way.  Wish I could inject a clock or disk mock
+            // here, but can't.  Instead relying on checking modtime of
+            // file on disk.
+            Thread.sleep(1000); // ms
+
+            editor = prefs.edit();
+            subsequentEdit(editor);
+            editor.commit();
+
+            long modtimeMillis2 = mPrefsFile.lastModified();
+            assertEquals(expectingMutation(), modtimeMillis1 != modtimeMillis2);
+        }
+    };
+
+    public void testRedundantBoolean() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.putBoolean("foo", true);
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.putBoolean("foo", true);
+            }
+        }.test();
+    }
+
+    public void testRedundantString() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.putString("foo", "bar");
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.putString("foo", "bar");
+            }
+        }.test();
+    }
+
+    public void testNonRedundantString() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.putString("foo", "bar");
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.putString("foo", "baz");
+            }
+            public boolean expectingMutation() {
+                return true;
+            }
+        }.test();
+    }
+
+    public void testRedundantClear() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.clear();
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.clear();
+            }
+        }.test();
+    }
+
+    public void testNonRedundantClear() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.putString("foo", "bar");
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.clear();
+            }
+            public boolean expectingMutation() {
+                return true;
+            }
+        }.test();
+    }
+
+    public void testRedundantRemove() throws Exception {
+        new RedundantWriteTest() {
+            public void setUp(SharedPreferences.Editor editor) {
+                editor.putString("foo", "bar");
+            }
+            public void subsequentEdit(SharedPreferences.Editor editor) {
+                editor.remove("not-exist-key");
+            }
+        }.test();
+    }
+
+    public void testRedundantCommitWritesFileIfNotAlreadyExisting() {
+        SharedPreferences prefs = getPrefs();
+        assertFalse(mPrefsFile.exists());
+        prefs.edit().putString("foo", "bar").commit();
+        assertTrue(mPrefsFile.exists());
+
+        // Delete the file out from under it.  (not sure why this
+        // would happen in practice, but perhaps the app did it for
+        // some reason...)
+        mPrefsFile.delete();
+
+        // And verify that a redundant edit (which would otherwise not
+        // write do disk), still does write to disk if the file isn't
+        // there.
+        prefs.edit().putString("foo", "bar").commit();
+        assertTrue(mPrefsFile.exists());
+    }
+
+    public void testTorture() {
+        Map<String, String> expectedMap = new HashMap<String, String>();
+        Random rand = new Random();
+
+        SharedPreferences prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE);
+        prefs.edit().clear().commit();
+
+        for (int i = 0; i < 100; i++) {
+            prefs = mContext.getSharedPreferences("torture", Context.MODE_PRIVATE);
+            assertEquals(expectedMap, prefs.getAll());
+
+            String key = new Integer(rand.nextInt(25)).toString();
+            String value = new Integer(i).toString();
+            SharedPreferences.Editor editor = prefs.edit();
+
+            if (rand.nextInt(100) < 85) {
+                Log.d(TAG, "Setting " + key + "=" + value);
+                editor.putString(key, value);
+                expectedMap.put(key, value);
+            } else {
+                Log.d(TAG, "Removing " + key);
+                editor.remove(key);
+                expectedMap.remove(key);
+            }
+
+            // Use apply on most, but commit some too.
+            if (rand.nextInt(100) < 85) {
+                Log.d(TAG, "apply.");
+                editor.apply();
+            } else {
+                Log.d(TAG, "commit.");
+                editor.commit();
+            }
+
+            assertEquals(expectedMap, prefs.getAll());
+        }
+    }
+
+    // Checks that an in-memory commit doesn't mutate a data structure
+    // still being used while writing out to disk.
+    public void testTorture2() {
+        Random rand = new Random();
+
+        for (int fi = 0; fi < 100; fi++) {
+            String prefsName = "torture_" + fi;
+            SharedPreferences prefs = mContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE);
+            prefs.edit().clear().commit();
+            Map<String, String> expectedMap = new HashMap<String, String>();
+
+            for (int applies = 0; applies < 3; applies++) {
+                SharedPreferences.Editor editor = prefs.edit();
+                for (int n = 0; n < 1000; n++) {
+                    String key = new Integer(rand.nextInt(25)).toString();
+                    String value = new Integer(n).toString();
+                    editor.putString(key, value);
+                    expectedMap.put(key, value);
+                }
+                editor.apply();
+            }
+            QueuedWork.waitToFinish();
+
+            String clonePrefsName = prefsName + "_clone";
+            File prefsFile = mContext.getSharedPrefsFile(prefsName);
+            File prefsFileClone = mContext.getSharedPrefsFile(clonePrefsName);
+            prefsFileClone.delete();
+            try {
+                FileOutputStream fos = new FileOutputStream(prefsFileClone);
+                FileInputStream fis = new FileInputStream(prefsFile);
+                byte buf[] = new byte[1024];
+                int n;
+                while ((n = fis.read(buf)) > 0) {
+                    fos.write(buf, 0, n);
+                }
+                fis.close();
+                fos.close();
+            } catch (IOException e) {
+            }
+
+            SharedPreferences clonePrefs = mContext.getSharedPreferences(
+                clonePrefsName, Context.MODE_PRIVATE);
+            assertEquals(expectedMap, clonePrefs.getAll());
+
+            prefsFile.delete();
+            prefsFileClone.delete();
+        }
+    }
+
+}
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigTest.java b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
index 276cb35..d68fd19 100755
--- a/tests/tests/content/src/android/content/res/cts/ConfigTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
@@ -43,7 +43,8 @@
         ORIENTATION,
         WIDTH,
         HEIGHT,
-        DENSITY
+        DENSITY,
+        SCREENLAYOUT
     }
 
     private static void checkValue(final Resources res, final int resId,
@@ -129,9 +130,11 @@
                     break;
                 case DENSITY:
                     // this is the ratio from the standard
-
                     mMetrics.density = (((float)value)/((float)DisplayMetrics.DENSITY_DEFAULT));
                     break;
+                case SCREENLAYOUT:
+                    mConfig.screenLayout = value;
+                    break;
                 default:
                     assert(false);
                     break;
@@ -303,6 +306,34 @@
         checkValue(res, R.configVarying.simple, "simple square");
         checkValue(res, R.configVarying.bag,
                 R.styleable.TestConfig, new String[]{"bag square"});
+
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_SMALL);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple small");
+        checkValue(res, R.configVarying.bag,
+                R.styleable.TestConfig, new String[]{"bag small"});
+
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_NORMAL);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple normal");
+        checkValue(res, R.configVarying.bag,
+                R.styleable.TestConfig, new String[]{"bag normal"});
+
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_LARGE);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple large");
+        checkValue(res, R.configVarying.bag,
+                R.styleable.TestConfig, new String[]{"bag large"});
+
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_XLARGE);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple xlarge");
+        checkValue(res, R.configVarying.bag,
+                R.styleable.TestConfig, new String[]{"bag xlarge"});
     }
     
     @MediumTest
@@ -378,6 +409,47 @@
                 R.styleable.TestConfig, new String[]{"bag 240dpi"});
     }
 
+    @MediumTest
+    public void testScreenSize() throws Exception {
+        // ensure that we fall back to the best available screen size
+        // for a given configuration.
+        TotalConfig config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_SMALL);
+        Resources res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple small");
+        checkValue(res, R.configVarying.small, "small");
+        checkValue(res, R.configVarying.normal, "default");
+        checkValue(res, R.configVarying.large, "default");
+        checkValue(res, R.configVarying.xlarge, "default");
+        
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_NORMAL);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple normal");
+        checkValue(res, R.configVarying.small, "default");
+        checkValue(res, R.configVarying.normal, "normal");
+        checkValue(res, R.configVarying.large, "default");
+        checkValue(res, R.configVarying.xlarge, "default");
+        
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_LARGE);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple large");
+        checkValue(res, R.configVarying.small, "default");
+        checkValue(res, R.configVarying.normal, "normal");
+        checkValue(res, R.configVarying.large, "large");
+        checkValue(res, R.configVarying.xlarge, "default");
+        
+        config = new TotalConfig();
+        config.setProperty(Properties.SCREENLAYOUT, Configuration.SCREENLAYOUT_SIZE_XLARGE);
+        res = config.getResources();
+        checkValue(res, R.configVarying.simple, "simple xlarge");
+        checkValue(res, R.configVarying.small, "default");
+        checkValue(res, R.configVarying.normal, "normal");
+        checkValue(res, R.configVarying.large, "large");
+        checkValue(res, R.configVarying.xlarge, "xlarge");
+    }
+
 // TODO - add tests for special cases - ie, other key params seem ignored if 
 // nokeys is set
 
@@ -421,7 +493,8 @@
          */
 
         /**
-         * Precidence order: mcc, mnc, locale, orientation, density,
+         * Precidence order: mcc, mnc, locale, screenlayout-size,
+         * screenlayout-long, orientation, density,
          * touchscreen, hidden, keyboard, navigation, width-height
          */
 
diff --git a/tests/tests/database/src/android/database/cts/DatabaseCursorTest.java b/tests/tests/database/src/android/database/cts/DatabaseCursorTest.java
index 9969f40..4d0c6ac 100644
--- a/tests/tests/database/src/android/database/cts/DatabaseCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/DatabaseCursorTest.java
@@ -415,11 +415,17 @@
 
     @LargeTest
     public void testManyRowsLong() throws Exception {
-        mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
-
+        mDatabase.beginTransaction();
         final int count = 9000;
-        for (int i = 0; i < count; i++) {
-            mDatabase.execSQL("INSERT INTO test (data) VALUES (" + i + ");");
+        try {
+            mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+            for (int i = 0; i < count; i++) {
+                mDatabase.execSQL("INSERT INTO test (data) VALUES (" + i + ");");
+            }
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
         }
 
         Cursor testCursor = getTestCursor(mDatabase.query("test", new String[] { "data" },
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index f3bfcaf..7287834 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -15,11 +15,12 @@
  */
 package android.graphics.cts;
 
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
+import com.android.cts.stub.R;
+
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -33,16 +34,14 @@
 import android.test.AndroidTestCase;
 import android.widget.cts.WidgetTestUtils;
 
-import com.android.cts.stub.R;
-
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
 
 @TestTargetClass(Bitmap.class)
 public class BitmapTest extends AndroidTestCase {
-    private static final int BUFFER_SIZE = 1016;
     private Resources mRes;
     private Bitmap mBitmap;
     private BitmapFactory.Options mOptions;
@@ -133,49 +132,51 @@
         )
     })
     public void testCopyPixelsToBuffer(){
+        final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight();
+        final int tooSmall = pixSize / 2;
+
         // abnormal case: unsupported Buffer subclass
         try{
-            mBitmap.copyPixelsToBuffer(CharBuffer.allocate(BUFFER_SIZE));
+            mBitmap.copyPixelsToBuffer(CharBuffer.allocate(pixSize));
             fail("shouldn't come to here");
         }catch(RuntimeException e1){
         }
 
         // abnormal case: Buffer not large enough for pixels
         try{
-            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(BUFFER_SIZE));
+            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall));
             fail("shouldn't come to here");
         }catch(RuntimeException e2){
         }
 
         // normal case
-        long pixSize = mBitmap.getRowBytes() * mBitmap.getHeight();
-        ByteBuffer byteBuf = ByteBuffer.allocate(101608);
+        ByteBuffer byteBuf = ByteBuffer.allocate(pixSize);
         assertEquals(0, byteBuf.position());
         mBitmap.copyPixelsToBuffer(byteBuf);
         assertEquals(pixSize, byteBuf.position());
 
         // abnormal case: Buffer not large enough for pixels
         try{
-            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(16));
+            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall));
             fail("shouldn't come to here");
         }catch(RuntimeException e3){
         }
 
         // normal case
-        ShortBuffer shortBuf = ShortBuffer.allocate(BUFFER_SIZE);
+        ShortBuffer shortBuf = ShortBuffer.allocate(pixSize);
         assertEquals(0, shortBuf.position());
         mBitmap.copyPixelsToBuffer(shortBuf);
         assertEquals(pixSize >> 1, shortBuf.position());
 
         // abnormal case: Buffer not large enough for pixels
         try{
-            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(16));
+            mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall));
             fail("shouldn't come to here");
         }catch(RuntimeException e4){
         }
 
         // normal case
-        IntBuffer intBuf1 = IntBuffer.allocate(BUFFER_SIZE);
+        IntBuffer intBuf1 = IntBuffer.allocate(pixSize);
         assertEquals(0, intBuf1.position());
         mBitmap.copyPixelsToBuffer(intBuf1);
         assertEquals(pixSize >> 2, intBuf1.position());
@@ -183,7 +184,7 @@
         Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(),
                 mBitmap.getConfig());
         bitmap.copyPixelsFromBuffer(intBuf1);
-        IntBuffer intBuf2 = IntBuffer.allocate(BUFFER_SIZE);
+        IntBuffer intBuf2 = IntBuffer.allocate(pixSize);
         bitmap.copyPixelsToBuffer(intBuf2);
 
         assertEquals(intBuf1.position(), intBuf2.position());
diff --git a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
index d69dd4f..041de94 100644
--- a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
@@ -34,7 +34,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.Suppress;
 
 @TestTargetClass(NinePatch.class)
 public class NinePatchTest extends AndroidTestCase {
@@ -174,7 +173,6 @@
         method = "hasAlpha",
         args = {}
     )
-    @Suppress // Suppressed for current release
     public void testHasAlpha() {
         assertFalse(mNinePatch.hasAlpha());
         assertEquals(mNinePatch.hasAlpha(), mBitmap.hasAlpha());
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index 864a1ab..a261914 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -25,10 +25,10 @@
 import android.graphics.BitmapFactory;
 import android.graphics.ImageFormat;
 import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.ErrorCallback;
 import android.hardware.Camera.Parameters;
 import android.hardware.Camera.PictureCallback;
-import android.hardware.Camera.PreviewCallback;
 import android.hardware.Camera.ShutterCallback;
 import android.hardware.Camera.Size;
 import android.media.ExifInterface;
@@ -37,6 +37,7 @@
 import android.os.Environment;
 import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
+import android.test.MoreAsserts;
 import android.test.UiThreadTest;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
@@ -46,6 +47,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -61,7 +64,7 @@
             "/test.jpg";
     private byte[] mJpegData;
 
-    private boolean mRawPreviewCallbackResult = false;
+    private boolean mPreviewCallbackResult = false;
     private boolean mShutterCallbackResult = false;
     private boolean mRawPictureCallbackResult = false;
     private boolean mJpegPictureCallbackResult = false;
@@ -72,7 +75,7 @@
     private static final int WAIT_FOR_FOCUS_TO_COMPLETE = 3000;
     private static final int WAIT_FOR_SNAPSHOT_TO_COMPLETE = 5000;
 
-    private RawPreviewCallback mRawPreviewCallback = new RawPreviewCallback();
+    private PreviewCallback mPreviewCallback = new PreviewCallback();
     private TestShutterCallback mShutterCallback = new TestShutterCallback();
     private RawPictureCallback mRawPictureCallback = new RawPictureCallback();
     private JpegPictureCallback mJpegPictureCallback = new JpegPictureCallback();
@@ -111,7 +114,7 @@
      * Initializes the message looper so that the Camera object can
      * receive the callback messages.
      */
-    private void initializeMessageLooper() {
+    private void initializeMessageLooper(final int cameraId) {
         final ConditionVariable startDone = new ConditionVariable();
         new Thread() {
             @Override
@@ -122,7 +125,7 @@
                 // Save the looper so that we can terminate this thread
                 // after we are done with it.
                 mLooper = Looper.myLooper();
-                mCamera = Camera.open();
+                mCamera = Camera.open(cameraId);
                 Log.v(TAG, "camera is opened");
                 startDone.open();
                 Looper.loop(); // Blocks forever until Looper.quit() is called.
@@ -153,18 +156,13 @@
     }
 
     //Implement the previewCallback
-    private final class RawPreviewCallback implements PreviewCallback {
-        public void onPreviewFrame(byte [] rawData, Camera camera) {
-            if (LOGV) Log.v(TAG, "Preview callback start");
-            int rawDataLength = 0;
-            if (rawData != null) {
-                rawDataLength = rawData.length;
-            }
-            if (rawDataLength > 0) {
-                mRawPreviewCallbackResult = true;
-            } else {
-                mRawPreviewCallbackResult = false;
-            }
+    private final class PreviewCallback
+            implements android.hardware.Camera.PreviewCallback {
+        public void onPreviewFrame(byte [] data, Camera camera) {
+            assertNotNull(data);
+            Size size = camera.getParameters().getPreviewSize();
+            assertEquals(size.width * size.height * 3 / 2, data.length);
+            mPreviewCallbackResult = true;
             mCamera.stopPreview();
             if (LOGV) Log.v(TAG, "notify the preview callback");
             mPreviewDone.open();
@@ -265,10 +263,7 @@
     }
 
     private void checkPreviewCallback() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         if (LOGV) Log.v(TAG, "check preview callback");
         mCamera.startPreview();
         waitForPreviewDone();
@@ -315,11 +310,17 @@
     })
     @UiThreadTest
     public void testTakePicture() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testTakePictureByCamera(id);
+        }
+    }
+
+    private void testTakePictureByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         Size pictureSize = mCamera.getParameters().getPictureSize();
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         mCamera.startPreview();
         mCamera.autoFocus(mAutoFocusCallback);
         assertTrue(waitForFocusDone());
@@ -329,16 +330,12 @@
         terminateMessageLooper();
         assertTrue(mShutterCallbackResult);
         assertTrue(mJpegPictureCallbackResult);
-        assertTrue(mJpegData != null);
+        assertNotNull(mJpegData);
         Bitmap b = BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length);
-        assertEquals(b.getWidth(), pictureSize.width);
-        assertEquals(b.getHeight(), pictureSize.height);
+        assertEquals(pictureSize.width, b.getWidth());
+        assertEquals(pictureSize.height, b.getHeight());
     }
 
-    /*
-     * Test case 2: Set the preview and
-     * verify the RawPreviewCallback is called
-     */
     @TestTargets({
         @TestTargetNew(
             level = TestLevel.COMPLETE,
@@ -377,13 +374,43 @@
         )
     })
     @UiThreadTest
-    public void testCheckPreview() throws Exception {
-        initializeMessageLooper();
-        mCamera.setPreviewCallback(mRawPreviewCallback);
+    public void testPreviewCallback() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testPreviewCallbackByCamera(id);
+        }
+    }
+
+    private void testPreviewCallbackByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setPreviewCallback(mPreviewCallback);
         mCamera.setErrorCallback(mErrorCallback);
         checkPreviewCallback();
         terminateMessageLooper();
-        assertTrue(mRawPreviewCallbackResult);
+        assertTrue(mPreviewCallbackResult);
+
+        mPreviewCallbackResult = false;
+        initializeMessageLooper(cameraId);
+        checkPreviewCallback();
+        terminateMessageLooper();
+        assertFalse(mPreviewCallbackResult);
+
+        // Test all preview sizes.
+        initializeMessageLooper(cameraId);
+        Parameters parameters = mCamera.getParameters();
+        for (Size size: parameters.getSupportedPreviewSizes()) {
+            mPreviewCallbackResult = false;
+            mCamera.setPreviewCallback(mPreviewCallback);
+            parameters.setPreviewSize(size.width, size.height);
+            mCamera.setParameters(parameters);
+            assertEquals(size, mCamera.getParameters().getPreviewSize());
+            mCamera.startPreview();
+            waitForPreviewDone();
+            assertTrue(mPreviewCallbackResult);
+            mCamera.stopPreview();
+        }
+        terminateMessageLooper();
     }
 
     @TestTargetNew(
@@ -393,17 +420,25 @@
     )
     @UiThreadTest
     public void testSetOneShotPreviewCallback() throws Exception {
-        initializeMessageLooper();
-        mCamera.setOneShotPreviewCallback(mRawPreviewCallback);
-        checkPreviewCallback();
-        terminateMessageLooper();
-        assertTrue(mRawPreviewCallbackResult);
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testSetOneShotPreviewCallbackByCamera(id);
+        }
+    }
 
-        mRawPreviewCallbackResult = false;
-        initializeMessageLooper();
+    private void testSetOneShotPreviewCallbackByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
         checkPreviewCallback();
         terminateMessageLooper();
-        assertFalse(mRawPreviewCallbackResult);
+        assertTrue(mPreviewCallbackResult);
+
+        mPreviewCallbackResult = false;
+        initializeMessageLooper(cameraId);
+        checkPreviewCallback();
+        terminateMessageLooper();
+        assertFalse(mPreviewCallbackResult);
     }
 
     @TestTargetNew(
@@ -413,38 +448,45 @@
     )
     @UiThreadTest
     public void testSetPreviewDisplay() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testSetPreviewDisplayByCamera(id);
+        }
+    }
+
+    private void testSetPreviewDisplayByCamera(int cameraId) throws Exception {
+        SurfaceHolder holder = getActivity().getSurfaceView().getHolder();
+        initializeMessageLooper(cameraId);
 
         // Check the order: startPreview->setPreviewDisplay.
-        mCamera.setOneShotPreviewCallback(mRawPreviewCallback);
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
         mCamera.startPreview();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(holder);
         waitForPreviewDone();
         terminateMessageLooper();
-        assertTrue(mRawPreviewCallbackResult);
+        assertTrue(mPreviewCallbackResult);
 
         // Check the order: setPreviewDisplay->startPreview.
-        initializeMessageLooper();
-        mRawPreviewCallbackResult = false;
-        mCamera.setOneShotPreviewCallback(mRawPreviewCallback);
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        initializeMessageLooper(cameraId);
+        mPreviewCallbackResult = false;
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
+        mCamera.setPreviewDisplay(holder);
         mCamera.startPreview();
         waitForPreviewDone();
         mCamera.stopPreview();
-        assertTrue(mRawPreviewCallbackResult);
+        assertTrue(mPreviewCallbackResult);
 
         // Check the order: setting preview display to null->startPreview->
         // setPreviewDisplay.
-        mRawPreviewCallbackResult = false;
-        mCamera.setOneShotPreviewCallback(mRawPreviewCallback);
+        mPreviewCallbackResult = false;
+        mCamera.setOneShotPreviewCallback(mPreviewCallback);
         mCamera.setPreviewDisplay(null);
         mCamera.startPreview();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(holder);
         waitForPreviewDone();
         terminateMessageLooper();
-        assertTrue(mRawPreviewCallbackResult);
+        assertTrue(mPreviewCallbackResult);
     }
 
     @TestTargetNew(
@@ -454,7 +496,15 @@
     )
     @UiThreadTest
     public void testDisplayOrientation() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testDisplayOrientationByCamera(id);
+        }
+    }
+
+    private void testDisplayOrientationByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
 
         // Check valid arguments.
         mCamera.setDisplayOrientation(0);
@@ -471,9 +521,7 @@
         }
 
         // Start preview.
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         mCamera.startPreview();
 
         // Check setting orientation during preview is not allowed.
@@ -500,8 +548,16 @@
         )
     })
     @UiThreadTest
-    public void testAccessParameters() throws Exception {
-        initializeMessageLooper();
+    public void testParameters() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testParametersByCamera(id);
+        }
+    }
+
+    private void testParametersByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         // we can get parameters just by getxxx method due to the private constructor
         Parameters pSet = mCamera.getParameters();
         assertParameters(pSet);
@@ -531,10 +587,10 @@
         assertTrue(origPreviewFrameRate > 0);
 
         // The default preview format must be yuv420 (NV21).
-        assertTrue(origPreviewFormat == ImageFormat.NV21);
+        assertEquals(ImageFormat.NV21, origPreviewFormat);
 
         // The default picture format must be Jpeg.
-        assertTrue(origPictureFormat == ImageFormat.JPEG);
+        assertEquals(ImageFormat.JPEG, origPictureFormat);
 
         // If camera supports flash, the default flash mode must be off.
         String flashMode = parameters.getFlashMode();
@@ -551,52 +607,62 @@
         float focalLength = parameters.getFocalLength();
         float horizontalViewAngle = parameters.getHorizontalViewAngle();
         float verticalViewAngle = parameters.getVerticalViewAngle();
+        int jpegQuality = parameters.getJpegQuality();
+        int jpegThumnailQuality = parameters.getJpegThumbnailQuality();
         assertTrue(previewSizes != null && previewSizes.size() != 0);
         assertTrue(pictureSizes != null && pictureSizes.size() != 0);
         assertTrue(previewFormats != null && previewFormats.size() != 0);
         assertTrue(pictureFormats != null && pictureFormats.size() != 0);
         assertTrue(frameRates != null && frameRates.size() != 0);
         assertTrue(focusModes != null && focusModes.size() != 0);
-        assertTrue(focusMode != null);
+        assertNotNull(focusMode);
         assertTrue(focalLength > 0);
         assertTrue(horizontalViewAngle > 0 && horizontalViewAngle <= 360);
         assertTrue(verticalViewAngle > 0 && verticalViewAngle <= 360);
         Size previewSize = previewSizes.get(0);
         Size pictureSize = pictureSizes.get(0);
+        assertTrue(jpegQuality >= 1 && jpegQuality <= 100);
+        assertTrue(jpegThumnailQuality >= 1 && jpegThumnailQuality <= 100);
 
         // If a parameter is supported, both getXXX and getSupportedXXX have to
         // be non null.
         if (parameters.getWhiteBalance() != null) {
-            assertTrue(parameters.getSupportedWhiteBalance() != null);
+            assertNotNull(parameters.getSupportedWhiteBalance());
         }
         if (parameters.getSupportedWhiteBalance() != null) {
-            assertTrue(parameters.getWhiteBalance() != null);
+            assertNotNull(parameters.getWhiteBalance());
         }
         if (parameters.getColorEffect() != null) {
-            assertTrue(parameters.getSupportedColorEffects() != null);
+            assertNotNull(parameters.getSupportedColorEffects());
         }
         if (parameters.getSupportedColorEffects() != null) {
-            assertTrue(parameters.getColorEffect() != null);
+            assertNotNull(parameters.getColorEffect());
         }
         if (parameters.getAntibanding() != null) {
-            assertTrue(parameters.getSupportedAntibanding() != null);
+            assertNotNull(parameters.getSupportedAntibanding());
         }
         if (parameters.getSupportedAntibanding() != null) {
-            assertTrue(parameters.getAntibanding() != null);
+            assertNotNull(parameters.getAntibanding());
         }
         if (parameters.getSceneMode() != null) {
-            assertTrue(parameters.getSupportedSceneModes() != null);
+            assertNotNull(parameters.getSupportedSceneModes());
         }
         if (parameters.getSupportedSceneModes() != null) {
-            assertTrue(parameters.getSceneMode() != null);
+            assertNotNull(parameters.getSceneMode());
         }
         if (parameters.getFlashMode() != null) {
-            assertTrue(parameters.getSupportedFlashModes() != null);
+            assertNotNull(parameters.getSupportedFlashModes());
         }
         if (parameters.getSupportedFlashModes() != null) {
-            assertTrue(parameters.getFlashMode() != null);
+            assertNotNull(parameters.getFlashMode());
         }
 
+        // Check if the sizes value contain invalid characters.
+        assertNoLetters(parameters.get("preview-size-values"), "preview-size-values");
+        assertNoLetters(parameters.get("picture-size-values"), "picture-size-values");
+        assertNoLetters(parameters.get("jpeg-thumbnail-size-values"),
+                "jpeg-thumbnail-size-values");
+
         // Set the parameters.
         parameters.setPictureFormat(PICTURE_FORMAT);
         assertEquals(PICTURE_FORMAT, parameters.getPictureFormat());
@@ -614,20 +680,19 @@
         mCamera.setParameters(parameters);
         Parameters paramActual = mCamera.getParameters();
 
-        // camera may not accept exact parameters, but values must be in valid range
         assertTrue(isValidPixelFormat(paramActual.getPictureFormat()));
-        assertEquals(paramActual.getPictureSize().width, pictureSize.width);
-        assertEquals(paramActual.getPictureSize().height, pictureSize.height);
+        assertEquals(pictureSize.width, paramActual.getPictureSize().width);
+        assertEquals(pictureSize.height, paramActual.getPictureSize().height);
         assertTrue(isValidPixelFormat(paramActual.getPreviewFormat()));
-        assertEquals(paramActual.getPreviewSize().width, previewSize.width);
-        assertEquals(paramActual.getPreviewSize().height, previewSize.height);
+        assertEquals(previewSize.width, paramActual.getPreviewSize().width);
+        assertEquals(previewSize.height, paramActual.getPreviewSize().height);
         assertTrue(paramActual.getPreviewFrameRate() > 0);
 
         checkExposureCompensation(parameters);
     }
 
     private void checkExposureCompensation(Parameters parameters) {
-        assertEquals(parameters.getExposureCompensation(), 0);
+        assertEquals(0, parameters.getExposureCompensation());
         int max = parameters.getMaxExposureCompensation();
         int min = parameters.getMinExposureCompensation();
         float step = parameters.getExposureCompensationStep();
@@ -664,7 +729,15 @@
     })
     @UiThreadTest
     public void testJpegThumbnailSize() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testJpegThumbnailSizeByCamera(id);
+        }
+    }
+
+    private void testJpegThumbnailSizeByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         // Thumbnail size parameters should have valid values.
         Parameters p = mCamera.getParameters();
         Size size = p.getJpegThumbnailSize();
@@ -675,57 +748,58 @@
         assertTrue(sizes.contains(mCamera.new Size(0, 0)));
 
         // Test if the thumbnail size matches the setting.
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         mCamera.startPreview();
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
-        assertEquals(mJpegPictureCallbackResult, true);
+        assertTrue(mJpegPictureCallbackResult);
         ExifInterface exif = new ExifInterface(JPEG_PATH);
         assertTrue(exif.hasThumbnail());
         byte[] thumb = exif.getThumbnail();
         Bitmap b = BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
-        assertEquals(b.getWidth(), size.width);
-        assertEquals(b.getHeight(), size.height);
+        assertEquals(size.width, b.getWidth());
+        assertEquals(size.height, b.getHeight());
 
         // Test no thumbnail case.
         p.setJpegThumbnailSize(0, 0);
         mCamera.setParameters(p);
+        Size actual = mCamera.getParameters().getJpegThumbnailSize();
+        assertEquals(0, actual.width);
+        assertEquals(0, actual.height);
         mCamera.startPreview();
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
-        assertEquals(mJpegPictureCallbackResult, true);
+        assertTrue(mJpegPictureCallbackResult);
         exif = new ExifInterface(JPEG_PATH);
-        assertTrue(!exif.hasThumbnail());
+        assertFalse(exif.hasThumbnail());
 
         terminateMessageLooper();
     }
 
     @UiThreadTest
     public void testJpegExif() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testJpegExifByCamera(id);
+        }
+    }
+
+    private void testJpegExifByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         Camera.Parameters parameters = mCamera.getParameters();
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         mCamera.startPreview();
         double focalLength = (double)parameters.getFocalLength();
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
         ExifInterface exif = new ExifInterface(JPEG_PATH);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_MAKE) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_MODEL) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_DATETIME) != null);
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_MAKE));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_MODEL));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_DATETIME));
         assertTrue(exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0) != 0);
         assertTrue(exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0) != 0);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP), null);
-        assertEquals(exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD), null);
+        checkGpsDataNull(exif);
         double exifFocalLength = (double)exif.getAttributeDouble(
                 ExifInterface.TAG_FOCAL_LENGTH, -1);
         assertEquals(focalLength, exifFocalLength, 0.001);
@@ -742,17 +816,36 @@
         mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
         waitForSnapshotDone();
         exif = new ExifInterface(JPEG_PATH);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP) != null);
-        assertTrue(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP) != null);
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
         assertEquals(thirtyTwoCharacters,
                 exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+
+        // Test gps tags do not exist after calling removeGpsData.
+        mCamera.startPreview();
+        parameters.removeGpsData();
+        mCamera.setParameters(parameters);
+        mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
+        waitForSnapshotDone();
+        exif = new ExifInterface(JPEG_PATH);
+        checkGpsDataNull(exif);
         terminateMessageLooper();
     }
 
+    private void checkGpsDataNull(ExifInterface exif) {
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP));
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+    }
+
     @TestTargets({
         @TestTargetNew(
             level = TestLevel.COMPLETE,
@@ -767,10 +860,18 @@
     })
     @UiThreadTest
     public void testLockUnlock() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testLockUnlockByCamera(id);
+        }
+    }
+
+    private void testLockUnlockByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         Camera.Parameters parameters = mCamera.getParameters();
         SurfaceHolder surfaceHolder;
-        surfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
+        surfaceHolder = getActivity().getSurfaceView().getHolder();
         Size size = parameters.getPreviewSize();
         mCamera.setParameters(parameters);
         mCamera.setPreviewDisplay(surfaceHolder);
@@ -834,41 +935,60 @@
     })
     @UiThreadTest
     public void testPreviewCallbackWithBuffer() throws Exception {
-        initializeMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testPreviewCallbackWithBufferByCamera(id);
+        }
+    }
+
+    private void testPreviewCallbackWithBufferByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
         SurfaceHolder surfaceHolder;
-        surfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
+        surfaceHolder = getActivity().getSurfaceView().getHolder();
         mCamera.setPreviewDisplay(surfaceHolder);
-        Size size = mCamera.getParameters().getPreviewSize();
+        Parameters parameters = mCamera.getParameters();
         PreviewCallbackWithBuffer callback = new PreviewCallbackWithBuffer();
-        callback.mBuffer1 = new byte[size.width * size.height * 3 / 2 + 1];
-        callback.mBuffer2 = new byte[size.width * size.height * 3 / 2 + 1];
-        callback.mBuffer3 = new byte[size.width * size.height * 3 / 2 + 1];
+        // Test all preview sizes.
+        for (Size size: parameters.getSupportedPreviewSizes()) {
+            parameters.setPreviewSize(size.width, size.height);
+            mCamera.setParameters(parameters);
+            assertEquals(size, mCamera.getParameters().getPreviewSize());
+            callback.mNumCbWithBuffer1 = 0;
+            callback.mNumCbWithBuffer2 = 0;
+            callback.mNumCbWithBuffer3 = 0;
+            callback.mBuffer1 = new byte[size.width * size.height * 3 / 2];
+            callback.mBuffer2 = new byte[size.width * size.height * 3 / 2];
+            callback.mBuffer3 = new byte[size.width * size.height * 3 / 2];
 
-        // Test if we can get the preview callbacks with specified buffers.
-        mCamera.addCallbackBuffer(callback.mBuffer1);
-        mCamera.addCallbackBuffer(callback.mBuffer2);
-        mCamera.setPreviewCallbackWithBuffer(callback);
-        mCamera.startPreview();
-        waitForPreviewDone();
-        assertEquals(1, callback.mNumCbWithBuffer1);
-        assertEquals(1, callback.mNumCbWithBuffer2);
-        assertEquals(0, callback.mNumCbWithBuffer3);
+            // Test if we can get the preview callbacks with specified buffers.
+            mCamera.addCallbackBuffer(callback.mBuffer1);
+            mCamera.addCallbackBuffer(callback.mBuffer2);
+            mCamera.setPreviewCallbackWithBuffer(callback);
+            mCamera.startPreview();
+            waitForPreviewDone();
+            assertEquals(1, callback.mNumCbWithBuffer1);
+            assertEquals(1, callback.mNumCbWithBuffer2);
+            assertEquals(0, callback.mNumCbWithBuffer3);
 
-        // Test if preview callback with buffer still works during preview.
-        callback.mNumCbWithBuffer1 = callback.mNumCbWithBuffer2 = 0;
-        mCamera.addCallbackBuffer(callback.mBuffer3);
-        waitForPreviewDone();
-        assertEquals(0, callback.mNumCbWithBuffer1);
-        assertEquals(0, callback.mNumCbWithBuffer2);
-        assertEquals(1, callback.mNumCbWithBuffer3);
+            // Test if preview callback with buffer still works during preview.
+            mCamera.addCallbackBuffer(callback.mBuffer3);
+            waitForPreviewDone();
+            assertEquals(1, callback.mNumCbWithBuffer1);
+            assertEquals(1, callback.mNumCbWithBuffer2);
+            assertEquals(1, callback.mNumCbWithBuffer3);
+            mCamera.setPreviewCallbackWithBuffer(null);
+            mCamera.stopPreview();
+        }
         terminateMessageLooper();
     }
 
-    private final class PreviewCallbackWithBuffer implements PreviewCallback {
+    private final class PreviewCallbackWithBuffer
+            implements android.hardware.Camera.PreviewCallback {
         public int mNumCbWithBuffer1, mNumCbWithBuffer2, mNumCbWithBuffer3;
         public byte[] mBuffer1, mBuffer2, mBuffer3;
         public void onPreviewFrame(byte[] data, Camera camera) {
-            assert(data != null);
+            assertNotNull(data);
             if (data == mBuffer1) {
                 mNumCbWithBuffer1++;
             } else if (data == mBuffer2) {
@@ -905,55 +1025,63 @@
     })
     @UiThreadTest
     public void testZoom() throws Exception {
-        initializeMessageLooper();
-        testImmediateZoom();
-        testSmoothZoom();
-        terminateMessageLooper();
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            initializeMessageLooper(id);
+            testImmediateZoom();
+            testSmoothZoom();
+            terminateMessageLooper();
+        }
     }
 
     private void testImmediateZoom() throws Exception {
         Parameters parameters = mCamera.getParameters();
         if (!parameters.isZoomSupported()) return;
 
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+
         // Test the zoom parameters.
-        assertEquals(parameters.getZoom(), 0);  // default zoom should be 0.
-        int maxZoom = parameters.getMaxZoom();
-        assertTrue(maxZoom >= 0);
-        if (maxZoom > 0) {
+        assertEquals(0, parameters.getZoom());  // default zoom should be 0.
+        for (Size size: parameters.getSupportedPreviewSizes()) {
+            parameters = mCamera.getParameters();
+            parameters.setPreviewSize(size.width, size.height);
+            mCamera.setParameters(parameters);
+            parameters = mCamera.getParameters();
+            int maxZoom = parameters.getMaxZoom();
+            assertTrue(maxZoom >= 0);
+
             // Zoom ratios should be sorted from small to large.
             List<Integer> ratios = parameters.getZoomRatios();
-            assertEquals(ratios.size(), maxZoom + 1);
-            assertEquals(ratios.get(0).intValue(), 100);
+            assertEquals(maxZoom + 1, ratios.size());
+            assertEquals(100, ratios.get(0).intValue());
             for (int i = 0; i < ratios.size() - 1; i++) {
                 assertTrue(ratios.get(i) < ratios.get(i + 1));
             }
-        }
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
-        mCamera.startPreview();
-        waitForPreviewDone();
+            mCamera.startPreview();
+            waitForPreviewDone();
 
-        // Test each zoom step.
-        for (int i = 0; i <= maxZoom; i++) {
-            parameters.setZoom(i);
-            mCamera.setParameters(parameters);
-            assertEquals(i, parameters.getZoom());
-        }
+            // Test each zoom step.
+            for (int i = 0; i <= maxZoom; i++) {
+                parameters.setZoom(i);
+                mCamera.setParameters(parameters);
+                assertEquals(i, mCamera.getParameters().getZoom());
+            }
 
-        // It should throw exception if an invalid value is passed.
-        try {
-            parameters.setZoom(maxZoom + 1);
-            mCamera.setParameters(parameters);
-            fail("setZoom should throw exception.");
-        } catch (RuntimeException e) {
-            // expected
-        }
-        parameters = mCamera.getParameters();
-        assertEquals(maxZoom, parameters.getZoom());
+            // It should throw exception if an invalid value is passed.
+            try {
+                parameters.setZoom(maxZoom + 1);
+                mCamera.setParameters(parameters);
+                fail("setZoom should throw exception.");
+            } catch (RuntimeException e) {
+                // expected
+            }
+            assertEquals(maxZoom, mCamera.getParameters().getZoom());
 
-        mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
-        waitForSnapshotDone();
+            mCamera.takePicture(mShutterCallback, mRawPictureCallback,
+                                mJpegPictureCallback);
+            waitForSnapshotDone();
+        }
     }
 
     private void testSmoothZoom() throws Exception {
@@ -961,10 +1089,8 @@
         if (!parameters.isSmoothZoomSupported()) return;
         assertTrue(parameters.isZoomSupported());
 
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = CameraStubActivity.mSurfaceView.getHolder();
         ZoomListener zoomListener = new ZoomListener();
-        mCamera.setPreviewDisplay(mSurfaceHolder);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
         mCamera.setZoomChangeListener(zoomListener);
         mCamera.startPreview();
         waitForPreviewDone();
@@ -973,8 +1099,10 @@
         int maxZoom = parameters.getMaxZoom();
         parameters.setZoom(maxZoom);
         mCamera.setParameters(parameters);
+        assertEquals(maxZoom, mCamera.getParameters().getZoom());
         parameters.setZoom(0);
         mCamera.setParameters(parameters);
+        assertEquals(0, mCamera.getParameters().getZoom());
         assertFalse(zoomListener.mZoomDone.block(500));
 
         // Nothing will happen if zoom is not moving.
@@ -1000,7 +1128,7 @@
             Log.e(TAG, "zoomListener.mStopped = " + zoomListener.mStopped);
             zoomListener.mZoomDone.close();
             mCamera.startSmoothZoom(maxZoom / 2);
-            assertEquals(true, zoomListener.mZoomDone.block(5000));
+            assertTrue(zoomListener.mZoomDone.block(5000));
             assertEquals(maxZoom - (maxZoom / 2), zoomListener.mValues.size());
             int i = maxZoom - 1;
             for(Integer value: zoomListener.mValues) {
@@ -1023,6 +1151,7 @@
         zoomListener.mZoomDone.close();
         parameters.setZoom(0);
         mCamera.setParameters(parameters);
+        assertEquals(0, mCamera.getParameters().getZoom());
         mCamera.startSmoothZoom(maxZoom);
         mCamera.stopSmoothZoom();
         assertTrue(zoomListener.mZoomDone.block(5000));
@@ -1041,11 +1170,484 @@
         public void onZoomChange(int value, boolean stopped, Camera camera) {
             mValues.add(value);
             assertEquals(value, camera.getParameters().getZoom());
-            assertEquals(false, mStopped);
+            assertFalse(mStopped);
             mStopped = stopped;
             if (stopped) {
                 mZoomDone.open();
             }
         }
     }
+
+    @UiThreadTest
+    public void testFocusDistances() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testFocusDistancesByCamera(id);
+        }
+    }
+
+    private void testFocusDistancesByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        mCamera.startPreview();
+        waitForPreviewDone();
+        Parameters parameters = mCamera.getParameters();
+
+        // Test every supported focus mode.
+        for (String focusMode: parameters.getSupportedFocusModes()) {
+            parameters.setFocusMode(focusMode);
+            mCamera.setParameters(parameters);
+            parameters = mCamera.getParameters();
+            assertEquals(focusMode, parameters.getFocusMode());
+            checkFocusDistances(parameters);
+            if (Parameters.FOCUS_MODE_AUTO.equals(focusMode)
+                    || Parameters.FOCUS_MODE_MACRO.equals(focusMode)) {
+                mCamera.autoFocus(mAutoFocusCallback);
+                assertTrue(waitForFocusDone());
+                parameters = mCamera.getParameters();
+                checkFocusDistances(parameters);
+            }
+        }
+
+        // Test if the method throws exception if the argument is invalid.
+        try {
+            parameters.getFocusDistances(null);
+            fail("getFocusDistances should not accept null.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            parameters.getFocusDistances(new float[2]);
+            fail("getFocusDistances should not accept a float array with two elements.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            parameters.getFocusDistances(new float[4]);
+            fail("getFocusDistances should not accept a float array with four elements.");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+        terminateMessageLooper();
+    }
+
+    private void checkFocusDistances(Parameters parameters) {
+        float[] distances = new float[3];
+        parameters.getFocusDistances(distances);
+
+        // Focus distances should be greater than 0.
+        assertTrue(distances[Parameters.FOCUS_DISTANCE_NEAR_INDEX] > 0);
+        assertTrue(distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX] > 0);
+        assertTrue(distances[Parameters.FOCUS_DISTANCE_FAR_INDEX] > 0);
+
+        // Make sure far focus distance >= optimal focus distance >= near focus distance.
+        assertTrue(distances[Parameters.FOCUS_DISTANCE_FAR_INDEX] >=
+                   distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX]);
+        assertTrue(distances[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX] >=
+                   distances[Parameters.FOCUS_DISTANCE_NEAR_INDEX]);
+
+        // Far focus distance should be infinity in infinity focus mode.
+        if (Parameters.FOCUS_MODE_INFINITY.equals(parameters.getFocusMode())) {
+            assertEquals(Float.POSITIVE_INFINITY,
+                         distances[Parameters.FOCUS_DISTANCE_FAR_INDEX]);
+        }
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "cancelAutofocus",
+            args = {}
+        )
+    })
+    @UiThreadTest
+    public void testCancelAutofocus() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testCancelAutofocusByCamera(id);
+        }
+    }
+
+    private void testCancelAutofocusByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        mCamera.startPreview();
+
+        // No op if autofocus is not in progress.
+        mCamera.cancelAutoFocus();
+
+        // Try to cancel autofocus immediately.
+        mCamera.autoFocus(mAutoFocusCallback);
+        mCamera.cancelAutoFocus();
+        checkFocusDistanceNotChanging();
+
+        // Try to cancel autofocus after it starts for some time.
+        mCamera.autoFocus(mAutoFocusCallback);
+        Thread.sleep(500);
+        mCamera.cancelAutoFocus();
+        checkFocusDistanceNotChanging();
+
+        // Try to cancel autofocus after it completes. It should be no op.
+        mCamera.autoFocus(mAutoFocusCallback);
+        assertTrue(waitForFocusDone());
+        mCamera.cancelAutoFocus();
+
+        // Test the case calling cancelAutoFocus and release in a row.
+        mCamera.autoFocus(mAutoFocusCallback);
+        mCamera.cancelAutoFocus();
+        mCamera.release();
+
+        // Ensure the camera can be opened if release is called right after AF.
+        mCamera = Camera.open(cameraId);
+        mCamera.startPreview();
+        mCamera.autoFocus(mAutoFocusCallback);
+        mCamera.release();
+
+        terminateMessageLooper();
+    }
+
+    private void checkFocusDistanceNotChanging() throws Exception {
+        float[] distances1 = new float[3];
+        float[] distances2 = new float[3];
+        Parameters parameters = mCamera.getParameters();
+        parameters.getFocusDistances(distances1);
+        Thread.sleep(100);
+        parameters = mCamera.getParameters();
+        parameters.getFocusDistances(distances2);
+        assertEquals(distances1[Parameters.FOCUS_DISTANCE_NEAR_INDEX],
+                     distances2[Parameters.FOCUS_DISTANCE_NEAR_INDEX]);
+        assertEquals(distances1[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX],
+                     distances2[Parameters.FOCUS_DISTANCE_OPTIMAL_INDEX]);
+        assertEquals(distances1[Parameters.FOCUS_DISTANCE_FAR_INDEX],
+                     distances2[Parameters.FOCUS_DISTANCE_FAR_INDEX]);
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getNumberOfCameras",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getCameraInfo",
+            args = {int.class, CameraInfo.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "open",
+            args = {int.class}
+        )
+    })
+    @UiThreadTest
+    public void testMultipleCameras() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        Log.v(TAG, "total " + nCameras + " cameras");
+        assertTrue(nCameras >= 0);
+
+        boolean backCameraExist = false;
+        CameraInfo info = new CameraInfo();
+        for (int i = 0; i < nCameras; i++) {
+            Camera.getCameraInfo(i, info);
+            if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
+                backCameraExist = true;
+                break;
+            }
+        }
+        // Make sure original open still works. It must return a back-facing
+        // camera.
+        mCamera = Camera.open();
+        if (mCamera != null) {
+            mCamera.release();
+            assertTrue(backCameraExist);
+        } else {
+            assertFalse(backCameraExist);
+        }
+
+        for (int id = -1; id <= nCameras; id++) {
+            Log.v(TAG, "testing camera #" + id);
+
+            boolean isBadId = (id < 0 || id >= nCameras);
+
+            try {
+                Camera.getCameraInfo(id, info);
+                if (isBadId) {
+                    fail("getCameraInfo should not accept bad cameraId (" + id + ")");
+                }
+            } catch (RuntimeException e) {
+                if (!isBadId) throw e;
+            }
+
+            int facing = info.facing;
+            int orientation = info.orientation;
+            assertTrue(facing == CameraInfo.CAMERA_FACING_BACK ||
+                       facing == CameraInfo.CAMERA_FACING_FRONT);
+            assertTrue(orientation == 0 || orientation == 90 ||
+                       orientation == 180 || orientation == 270);
+
+            Camera camera = null;
+            try {
+                camera = Camera.open(id);
+                if (isBadId) {
+                    fail("open() should not accept bad cameraId (" + id + ")");
+                }
+            } catch (RuntimeException e) {
+                if (!isBadId) throw e;
+            } finally {
+                if (camera != null) {
+                    camera.release();
+                }
+            }
+        }
+    }
+
+    @UiThreadTest
+    public void testPreviewPictureSizesCombination() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testPreviewPictureSizesCombinationByCamera(id);
+        }
+    }
+
+    private void testPreviewPictureSizesCombinationByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        Parameters parameters = mCamera.getParameters();
+        PreviewCbForPreviewPictureSizesCombination callback =
+            new PreviewCbForPreviewPictureSizesCombination();
+
+        // Test all combination of preview sizes and picture sizes.
+        for (Size previewSize: parameters.getSupportedPreviewSizes()) {
+            for (Size pictureSize: parameters.getSupportedPictureSizes()) {
+                Log.v(TAG, "Test previewSize=(" + previewSize.width + "," +
+                        previewSize.height + ") pictureSize=(" +
+                        pictureSize.width + "," + pictureSize.height + ")");
+                mPreviewCallbackResult = false;
+                mCamera.setPreviewCallback(callback);
+                callback.expectedPreviewSize = previewSize;
+                parameters.setPreviewSize(previewSize.width, previewSize.height);
+                parameters.setPictureSize(pictureSize.width, pictureSize.height);
+                mCamera.setParameters(parameters);
+                assertEquals(previewSize, mCamera.getParameters().getPreviewSize());
+                assertEquals(pictureSize, mCamera.getParameters().getPictureSize());
+
+                // Check if the preview size is the same as requested.
+                mCamera.startPreview();
+                waitForPreviewDone();
+                assertTrue(mPreviewCallbackResult);
+
+                // Check if the picture size is the same as requested.
+                mCamera.takePicture(mShutterCallback, mRawPictureCallback, mJpegPictureCallback);
+                waitForSnapshotDone();
+                assertTrue(mJpegPictureCallbackResult);
+                assertNotNull(mJpegData);
+                Bitmap b = BitmapFactory.decodeByteArray(mJpegData, 0, mJpegData.length);
+                assertEquals(pictureSize.width, b.getWidth());
+                assertEquals(pictureSize.height, b.getHeight());
+                b.recycle();
+                b = null;
+            }
+        }
+        terminateMessageLooper();
+    }
+
+    private final class PreviewCbForPreviewPictureSizesCombination
+            implements android.hardware.Camera.PreviewCallback {
+        public Size expectedPreviewSize;
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            assertNotNull(data);
+            Size size = camera.getParameters().getPreviewSize();
+            assertEquals(expectedPreviewSize, size);
+            assertEquals(size.width * size.height * 3 / 2, data.length);
+            camera.setPreviewCallback(null);
+            mPreviewCallbackResult = true;
+            mPreviewDone.open();
+        }
+    }
+
+    public void testPreviewFpsRange() throws Exception {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            Log.v(TAG, "Camera id=" + id);
+            testPreviewFpsRangeByCamera(id);
+        }
+    }
+
+    private void testPreviewFpsRangeByCamera(int cameraId) throws Exception {
+        initializeMessageLooper(cameraId);
+        mCamera.setPreviewDisplay(getActivity().getSurfaceView().getHolder());
+        mCamera.startPreview();
+
+        // Test if the parameters exists and minimum fps <= maximum fps.
+        int[] defaultFps = new int[2];
+        Parameters parameters = mCamera.getParameters();
+        parameters.getPreviewFpsRange(defaultFps);
+        List<int[]> fpsList = parameters.getSupportedPreviewFpsRange();
+        assertTrue(fpsList.size() > 0);
+        boolean found = false;
+        for(int[] fps: fpsList) {
+            assertTrue(fps[Parameters.PREVIEW_FPS_MIN_INDEX] > 0);
+            assertTrue(fps[Parameters.PREVIEW_FPS_MIN_INDEX] <=
+                       fps[Parameters.PREVIEW_FPS_MAX_INDEX]);
+            if (!found && Arrays.equals(defaultFps, fps)) {
+                found = true;
+            }
+        }
+        assertTrue("Preview fps range must be in the supported list.", found);
+
+        // Test if the list is properly sorted.
+        for (int i = 0; i < fpsList.size() - 1; i++) {
+            int minFps1 = fpsList.get(i)[Parameters.PREVIEW_FPS_MIN_INDEX];
+            int maxFps1 = fpsList.get(i)[Parameters.PREVIEW_FPS_MAX_INDEX];
+            int minFps2 = fpsList.get(i + 1)[Parameters.PREVIEW_FPS_MIN_INDEX];
+            int maxFps2 = fpsList.get(i + 1)[Parameters.PREVIEW_FPS_MAX_INDEX];
+            assertTrue(maxFps1 < maxFps2
+                    || (maxFps1 == maxFps2 && minFps1 < minFps2));
+        }
+
+        // Test if the actual fps is within fps range.
+        Size size = parameters.getPreviewSize();
+        byte[] buffer1 = new byte[size.width * size.height * 3 / 2];
+        byte[] buffer2 = new byte[size.width * size.height * 3 / 2];
+        byte[] buffer3 = new byte[size.width * size.height * 3 / 2];
+        FpsRangePreviewCb callback = new FpsRangePreviewCb();
+        int[] readBackFps = new int[2];
+        for (int[] fps: fpsList) {
+            parameters = mCamera.getParameters();
+            parameters.setPreviewFpsRange(fps[Parameters.PREVIEW_FPS_MIN_INDEX],
+                                          fps[Parameters.PREVIEW_FPS_MAX_INDEX]);
+            callback.reset(fps[Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.0,
+                           fps[Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.0);
+            mCamera.setParameters(parameters);
+            parameters = mCamera.getParameters();
+            parameters.getPreviewFpsRange(readBackFps);
+            MoreAsserts.assertEquals(fps, readBackFps);
+            mCamera.addCallbackBuffer(buffer1);
+            mCamera.addCallbackBuffer(buffer2);
+            mCamera.addCallbackBuffer(buffer3);
+            mCamera.setPreviewCallbackWithBuffer(callback);
+            mCamera.startPreview();
+            try {
+                // Test the frame rate for a while.
+                Thread.sleep(3000);
+            } catch(Exception e) {
+                // ignore
+            }
+            mCamera.stopPreview();
+        }
+
+        // Test the invalid fps cases.
+        parameters = mCamera.getParameters();
+        parameters.setPreviewFpsRange(-1, -1);
+        try {
+            mCamera.setParameters(parameters);
+            fail("Should throw an exception if fps range is negative.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+        parameters.setPreviewFpsRange(10, 5);
+        try {
+            mCamera.setParameters(parameters);
+            fail("Should throw an exception if fps range is invalid.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        terminateMessageLooper();
+    }
+
+    private final class FpsRangePreviewCb
+            implements android.hardware.Camera.PreviewCallback {
+        private double mMinFps, mMaxFps, mMaxFrameInterval, mMinFrameInterval;
+        // An array storing the arrival time of the frames in the last second.
+        private ArrayList<Long> mFrames = new ArrayList<Long>();
+        private long firstFrameArrivalTime;
+
+        public void reset(double minFps, double maxFps) {
+            this.mMinFps = minFps;
+            this.mMaxFps = maxFps;
+            mMaxFrameInterval = 1000.0 / mMinFps;
+            mMinFrameInterval = 1000.0 / mMaxFps;
+            Log.v(TAG, "Min fps=" + mMinFps + ". Max fps=" + mMaxFps
+                    + ". Min frame interval=" + mMinFrameInterval
+                    + ". Max frame interval=" + mMaxFrameInterval);
+            mFrames.clear();
+            firstFrameArrivalTime = 0;
+        }
+
+        // This method tests if the actual fps is between minimum and maximum.
+        // It also tests if the frame interval is too long.
+        public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
+            long arrivalTime = System.currentTimeMillis();
+            camera.addCallbackBuffer(data);
+            if (firstFrameArrivalTime == 0) firstFrameArrivalTime = arrivalTime;
+
+            // Remove the frames that arrived before the last second.
+            Iterator<Long> it = mFrames.iterator();
+            while(it.hasNext()) {
+                long time = it.next();
+                if (arrivalTime - time > 1000 && mFrames.size() > 1) {
+                    it.remove();
+                } else {
+                    break;
+                }
+            }
+
+            // Start the test after one second.
+            if (arrivalTime - firstFrameArrivalTime > 1000) {
+                assertTrue(mFrames.size() >= 2);
+
+                // Check the frame interval and fps. The interval check
+                // considers the time variance passing frames from the camera
+                // hardware to the callback. It should be a constant time, not a
+                // ratio. The fps check is more strict because individual
+                // variance is averaged out.
+
+                // Check if the frame interval is too large or too small.
+                double intervalMargin = 30;  // ms
+                long lastArrivalTime = mFrames.get(mFrames.size() - 1);
+                double interval = arrivalTime - lastArrivalTime;
+                if (LOGV) Log.v(TAG, "Frame interval=" + interval);
+                assertTrue("Frame interval (" + interval + "ms) is too large." +
+                        " mMaxFrameInterval=" + mMaxFrameInterval + "ms",
+                        interval < mMaxFrameInterval + intervalMargin);
+                assertTrue("Frame interval (" + interval + "ms) is too small." +
+                        " mMinFrameInterval=" + mMinFrameInterval + "ms",
+                        interval > mMinFrameInterval - intervalMargin);
+
+                // Check if the fps is within range.
+                double fpsMargin = 0.03;
+                double avgInterval = (double)(arrivalTime - mFrames.get(0))
+                        / mFrames.size();
+                double fps = 1000.0 / avgInterval;
+                assertTrue("Actual fps (" + fps + ") should be larger than " +
+                           "min fps (" + mMinFps + ")",
+                           fps >= mMinFps * (1 - fpsMargin));
+                assertTrue("Actual fps (" + fps + ") should be smaller than " +
+                           "max fps (" + mMaxFps + ")",
+                           fps <= mMaxFps * (1 + fpsMargin));
+            }
+            // Add the arrival time of this frame to the list.
+            mFrames.add(arrivalTime);
+        }
+    }
+
+    private void assertEquals(Size expected, Size actual) {
+        assertEquals(expected.width, actual.width);
+        assertEquals(expected.height, actual.height);
+    }
+
+    private void assertNoLetters(String value, String key) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            assertFalse("Parameter contains invalid characters. key,value=("
+                    + key + "," + value + ")",
+                    Character.isLetter(c) && c != 'x');
+        }
+    }
 }
diff --git a/tests/tests/jni/libjnitest/helper.h b/tests/tests/jni/libjnitest/helper.h
index 6771d2f..58a3407 100644
--- a/tests/tests/jni/libjnitest/helper.h
+++ b/tests/tests/jni/libjnitest/helper.h
@@ -47,7 +47,7 @@
  * @param ... printf-style arguments
  * @return an allocated (char *) containing the formatted result
  */
-char *failure(const char *format, ...);
+char *failure(const char *format, ...) __attribute__((format(printf, 1, 2)));
 
 /**
  * Runs a list of tests. It will run all the tests, collecting as output
diff --git a/tests/tests/location/src/android/location/cts/AddressTest.java b/tests/tests/location/src/android/location/cts/AddressTest.java
index 5b17e39..5c77d94 100644
--- a/tests/tests/location/src/android/location/cts/AddressTest.java
+++ b/tests/tests/location/src/android/location/cts/AddressTest.java
@@ -548,7 +548,10 @@
         assertEquals(address.getAdminArea(), parcel.readString());
         assertEquals(address.getSubAdminArea(), parcel.readString());
         assertEquals(address.getLocality(), parcel.readString());
+        assertEquals(address.getSubLocality(), parcel.readString());
         assertEquals(address.getThoroughfare(), parcel.readString());
+        assertEquals(address.getSubThoroughfare(), parcel.readString());
+        assertEquals(address.getPremises(), parcel.readString());
         assertEquals(address.getPostalCode(), parcel.readString());
         assertEquals(address.getCountryCode(), parcel.readString());
         assertEquals(address.getCountryName(), parcel.readString());
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index a917d1d..48cdade 100755
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -475,13 +475,6 @@
         }
 
         try {
-            mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, listener, null);
-            fail("Should throw IllegalArgumentException if param looper is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
             mManager.removeUpdates( (LocationListener) null );
             fail("Should throw IllegalArgumentException if listener is null!");
         } catch (IllegalArgumentException e) {
diff --git a/tests/tests/media/src/android/media/cts/AudioEffectTest.java b/tests/tests/media/src/android/media/cts/AudioEffectTest.java
new file mode 100644
index 0000000..514f6a7
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioEffectTest.java
@@ -0,0 +1,1379 @@
+/*
+ * 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.media.cts;
+
+import com.android.cts.stub.R;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.audiofx.PresetReverb;
+import android.media.audiofx.EnvironmentalReverb;
+import android.media.audiofx.Equalizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+import java.util.UUID;
+
+@TestTargetClass(AudioEffect.class)
+public class AudioEffectTest extends AndroidTestCase {
+
+    private String TAG = "AudioEffectTest";
+    private final static int MIN_NUMBER_EFFECTS = 5;
+    // allow +/- 5% tolerance between set and get delays
+    private final static float DELAY_TOLERANCE = 1.05f;
+    // allow +/- 5% tolerance between set and get ratios
+    private final static float RATIO_TOLERANCE = 1.05f;
+
+    private AudioEffect mEffect = null;
+    private AudioEffect mEffect2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private MediaPlayer mMediaPlayer = null;
+    private int mError = 0;
+
+    private final Object mLock = new Object();
+
+
+    //-----------------------------------------------------------------
+    // AUDIOEFFECT TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - static methods
+    //----------------------------------
+
+    //Test case 0.0: test queryEffects() and platfrom at least provides Equalizer, Bass Boost,
+    // Virtualizer, Environmental reverb and Preset reverb effects
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "queryEffects",
+            args = {}
+        )
+    })
+    public void test0_0QueryEffects() throws Exception {
+
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+
+        assertTrue("test0_0QueryEffects: number of effects < MIN_NUMBER_EFFECTS: "+desc.length,
+                (desc.length >= MIN_NUMBER_EFFECTS));
+
+        boolean hasEQ = false;
+        boolean hasBassBoost = false;
+        boolean hasVirtualizer = false;
+        boolean hasEnvReverb = false;
+        boolean hasPresetReverb = false;
+
+        for (int i = 0; i < desc.length; i++) {
+            if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
+                hasEQ = true;
+            } else if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) {
+                hasBassBoost = true;
+            } else if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
+                hasVirtualizer = true;
+            } else if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)) {
+                hasEnvReverb = true;
+            } else if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)) {
+                hasPresetReverb = true;
+            }
+        }
+        assertTrue("test0_0QueryEffects: equalizer not found", hasEQ);
+        assertTrue("test0_0QueryEffects: bass boost not found", hasBassBoost);
+        assertTrue("test0_0QueryEffects: virtualizer not found", hasVirtualizer);
+        assertTrue("test0_0QueryEffects: environmental reverb not found", hasEnvReverb);
+        assertTrue("test0_0QueryEffects: preset reverb not found", hasPresetReverb);
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - constructor
+    //----------------------------------
+
+    //Test case 1.0: test constructor from effect type and get effect ID
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioEffect",
+            args = {UUID.class, UUID.class, int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test1_0ConstructorFromType() throws Exception {
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+        assertTrue("no effects found", (desc.length != 0));
+        for (int i = 0; i < desc.length; i++) {
+            try {
+                AudioEffect effect = new AudioEffect(desc[i].type,
+                        AudioEffect.EFFECT_TYPE_NULL,
+                        0,
+                        0);
+                assertNotNull("could not create AudioEffect", effect);
+                try {
+                    assertTrue("invalid effect ID", (effect.getId() != 0));
+                } catch (IllegalStateException e) {
+                    fail("AudioEffect not initialized");
+                } finally {
+                    effect.release();
+                }
+            } catch (IllegalArgumentException e) {
+                fail("Effect not found: "+desc[i].name);
+            } catch (UnsupportedOperationException e) {
+                fail("Effect library not loaded");
+            }
+        }
+    }
+
+    //Test case 1.1: test constructor from effect uuid
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioEffect",
+            args = {UUID.class, UUID.class, int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test1_1ConstructorFromUuid() throws Exception {
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+        assertTrue("no effects found", (desc.length != 0));
+        for (int i = 0; i < desc.length; i++) {
+            try {
+                AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_NULL,
+                        desc[i].uuid,
+                        0,
+                        0);
+                assertNotNull("could not create AudioEffect", effect);
+                try {
+                    assertTrue("invalid effect ID", (effect.getId() != 0));
+                } catch (IllegalStateException e) {
+                    fail("AudioEffect not initialized");
+                } finally {
+                    effect.release();
+                }
+            } catch (IllegalArgumentException e) {
+                fail("Effect not found: "+desc[i].name);
+            } catch (UnsupportedOperationException e) {
+                fail("Effect library not loaded");
+            }
+        }
+    }
+
+    //Test case 1.2: test constructor failure from unknown type
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioEffect",
+            args = {UUID.class, UUID.class, int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test1_2ConstructorUnknownType() throws Exception {
+
+        try {
+            AudioEffect effect = new AudioEffect(UUID.randomUUID(),
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            fail("could create random AudioEffect");
+            if (effect != null) {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //Test case 1.3: test getEnabled() failure when called on released effect
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioEffect",
+            args = {UUID.class, UUID.class, int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test1_3GetEnabledAfterRelease() throws Exception {
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.getEnabled();
+                fail("getEnabled() processed after release()");
+            } catch (IllegalStateException e) {
+
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //Test case 1.4: test contructor on mediaPlayer audio session
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "MediaPlayer.getAudioSessionId",
+            args = {}
+        )
+    })
+    public void test1_4InsertOnMediaPlayer() throws Exception {
+        MediaPlayer mp = new MediaPlayer();
+        assertNotNull("could not create mediaplayer", mp);
+        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
+        mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        afd.close();
+        getEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, mp.getAudioSessionId());
+        try {
+            mEffect.setEnabled(true);
+
+        } catch (IllegalStateException e) {
+            fail("AudioEffect not initialized");
+        } finally {
+            mp.release();
+            releaseEffect();
+        }
+    }
+
+    //Test case 1.5: test auxiliary effect attachement on MediaPlayer
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "MediaPlayer.attachAuxEffect",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "MediaPlayer.setAuxEffectSendLevel",
+            args = {}
+        )
+    })
+    public void test1_5AuxiliaryOnMediaPlayer() throws Exception {
+        createMediaPlayerLooper();
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);  // mMediaPlayer has been initialized?
+        mError = 0;
+
+        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
+        mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        afd.close();
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            synchronized(mLock) {
+                try {
+                    mMediaPlayer.attachAuxEffect(mEffect.getId());
+                    mMediaPlayer.setAuxEffectSendLevel(1.0f);
+                    mLock.wait(200);
+                } catch(Exception e) {
+                    fail("Attach effect: wait was interrupted.");
+                }
+            }
+            assertTrue("error on attachAuxEffect", mError == 0);
+
+        } catch (IllegalStateException e) {
+            fail("attach aux effect failed");
+        } finally {
+            terminateMediaPlayerLooper();
+            releaseEffect();
+        }
+    }
+
+    //Test case 1.6: test auxiliary effect attachement failure before setDatasource
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "MediaPlayer.attachAuxEffect",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "MediaPlayer.setAuxEffectSendLevel",
+            args = {}
+        )
+    })
+    public void test1_6AuxiliaryOnMediaPlayerFailure() throws Exception {
+        createMediaPlayerLooper();
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);  // mMediaPlayer has been initialized?
+        mError = 0;
+
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            synchronized(mLock) {
+                try {
+                    mMediaPlayer.attachAuxEffect(mEffect.getId());
+                    mLock.wait(1000);
+                } catch(Exception e) {
+                    fail("Attach effect: wait was interrupted.");
+                }
+            }
+            assertTrue("no error on attachAuxEffect", mError != 0);
+
+        } catch (IllegalStateException e) {
+            fail("attach aux effect failed");
+        } finally {
+            terminateMediaPlayerLooper();
+            releaseEffect();
+        }
+    }
+
+
+    //Test case 1.7: test auxiliary effect attachement on AudioTrack
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioTrack.attachAuxEffect",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "AudioTrack.setAuxEffectSendLevel",
+            args = {}
+        )
+    })
+    public void test1_7AuxiliaryOnAudioTrack() throws Exception {
+        AudioTrack track = null;
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            track = new AudioTrack(
+                                AudioManager.STREAM_MUSIC,
+                                44100,
+                                AudioFormat.CHANNEL_OUT_MONO,
+                                AudioFormat.ENCODING_PCM_16BIT,
+                                AudioTrack.getMinBufferSize(44100,
+                                                            AudioFormat.CHANNEL_OUT_MONO,
+                                                            AudioFormat.ENCODING_PCM_16BIT),
+                                                            AudioTrack.MODE_STREAM);
+            assertNotNull("could not create AudioTrack", track);
+
+            int status = track.attachAuxEffect(mEffect.getId());
+            if (status != AudioTrack.SUCCESS) {
+                fail("could not attach aux effect");
+            }
+            status = track.setAuxEffectSendLevel(1.0f);
+            if (status != AudioTrack.SUCCESS) {
+                fail("could not set send level");
+            }
+        } catch (IllegalStateException e) {
+            fail("could not attach aux effect");
+        } catch (IllegalArgumentException e) {
+            fail("could not create AudioTrack");
+        } finally {
+            if (track != null) {
+                track.release();
+            }
+            releaseEffect();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - enable/ disable
+    //----------------------------------
+
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            try {
+                effect.setEnabled(true);
+                assertTrue("invalid state from getEnabled", effect.getEnabled());
+                effect.setEnabled(false);
+                assertFalse("invalid state to getEnabled", effect.getEnabled());
+
+            } catch (IllegalStateException e) {
+                fail("setEnabled() in wrong state");
+            } finally {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.setEnabled(true);
+                fail("setEnabled() processed after release");
+            } catch (IllegalStateException e) {
+                // test passed
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - set/get parameters
+    //----------------------------------
+
+    //Test case 3.0: test setParameter(byte[], byte[]) / getParameter(byte[], byte[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {byte[].class, byte[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {byte[].class, byte[].class}
+        )
+    })
+    public void test3_0SetParameterByteArrayByteArray() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            byte[] param = mEffect.intToByteArray(PresetReverb.PARAM_PRESET);
+            byte[] value = new byte[2];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            short preset = PresetReverb.PRESET_SMALLROOM;
+            if (mEffect.byteArrayToShort(value) == preset) {
+                preset = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            value = mEffect.shortToByteArray(preset);
+            status = mEffect.setParameter(param, value);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            assertEquals("get/set Parameter failed", preset,
+                    mEffect.byteArrayToShort(value));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.1: test setParameter(int, int) / getParameter(int, int[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int.class, int[].class}
+        )
+    })
+    public void test3_1SetParameterIntInt() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
+            int[] value = new int[1];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            int time = 500;
+            if (value[0] == time) {
+                time = 1000;
+            }
+            status = mEffect.setParameter(param, time);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            assertTrue("got incorrect decay time",
+                    ((float)value[0] > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)value[0] < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.2: test setParameter(int, short) / getParameter(int, short[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int.class, short[].class}
+        )
+    })
+    public void test3_2SetParameterIntShort() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            int param = PresetReverb.PARAM_PRESET;
+            short[] value = new short[1];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            short preset = PresetReverb.PRESET_SMALLROOM;
+            if (value[0] == preset) {
+                preset = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            status = mEffect.setParameter(param, preset);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            assertEquals("get/set Parameter failed", preset, value[0]);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.3: test setParameter(int, byte[]) / getParameter(int, byte[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, byte[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int.class, byte[].class}
+        )
+    })
+    public void test3_3SetParameterIntByteArray() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
+            byte[] value = new byte[4];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            int time = 500;
+            if (mEffect.byteArrayToInt(value) == time) {
+                time = 1000;
+            }
+            value = mEffect.intToByteArray(time);
+            status = mEffect.setParameter(param, value);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            int time2 = mEffect.byteArrayToInt(value);
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.4: test setParameter(int[], int[]) / getParameter(int[], int[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int[].class, int[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int[].class, int[].class}
+        )
+    })
+    public void test3_4SetParameterIntArrayIntArray() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            int[] value = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            int[] time = new int[1];
+            time[0] = 500;
+            if (value[0] == time[0]) {
+                time[0] = 1000;
+            }
+            status = mEffect.setParameter(param, time);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            assertTrue("got incorrect decay time",
+                    ((float)value[0] > (float)(time[0] / DELAY_TOLERANCE)) &&
+                    ((float)value[0] < (float)(time[0] * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.5: test setParameter(int[], short[]) / getParameter(int[], short[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int[].class, short[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int[].class, short[].class}
+        )
+    })
+
+    public void test3_5SetParameterIntArrayShortArray() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            param[0] = PresetReverb.PARAM_PRESET;
+            short[] value = new short[1];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            short[] preset = new short[1];
+            preset[0] = PresetReverb.PRESET_SMALLROOM;
+            if (value[0] == preset[0]) {
+                preset[0] = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            status = mEffect.setParameter(param, preset);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            assertEquals("get/set Parameter failed", preset[0], value[0]);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.6: test setParameter(int[], byte[]) / getParameter(int[], byte[])
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int[].class, byte[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int[].class, byte[].class}
+        )
+    })
+    public void test3_6SetParameterIntArrayByteArray() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            byte[] value = new byte[4];
+            int status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 1 failed", AudioEffect.SUCCESS, status);
+            int time = 500;
+            if (mEffect.byteArrayToInt(value) == time) {
+                time = 1000;
+            }
+
+            status = mEffect.setParameter(param, mEffect.intToByteArray(time));
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertEquals("getParameter 2 failed", AudioEffect.SUCCESS, status);
+            int time2 = mEffect.byteArrayToInt(value);
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.7: test setParameter() throws exception after release()
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, short.class}
+        )
+    })
+    public void test3_7SetParameterAfterRelease() throws Exception {
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            effect.setParameter(PresetReverb.PARAM_PRESET, PresetReverb.PRESET_SMALLROOM);
+            fail("setParameter() processed after release");
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+    }
+
+    //Test case 3.8: test getParameter() throws exception after release()
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, short[].class}
+        )
+    })
+    public void test3_8GetParameterAfterRelease() throws Exception {
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            short[] value = new short[1];
+            effect.getParameter(PresetReverb.PARAM_PRESET, value);
+            fail("getParameter() processed after release");
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("getParameter() rejected");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 4 priority and listeners
+    //----------------------------------
+
+    //Test case 4.0: test control passed to higher priority client
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "hasControl",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test4_0setEnabledLowerPriority() throws Exception {
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull("could not create AudioEffect", effect1);
+            assertNotNull("could not create AudioEffect", effect2);
+
+            assertTrue("Effect2 does not have control", effect2.hasControl());
+            assertFalse("Effect1 has control", effect1.hasControl());
+            assertTrue("Effect1 can enable",
+                    effect1.setEnabled(true) == AudioEffect.ERROR_INVALID_OPERATION);
+            assertFalse("Effect1 has enabled", effect2.getEnabled());
+
+        } catch (IllegalArgumentException e) {
+            fail("Effect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+    }
+
+    //Test case 4.1: test control passed to higher priority client
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getParameter",
+            args = {int.class, short[].class}
+        )
+    })
+    public void test4_1setParameterLowerPriority() throws Exception {
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull("could not create AudioEffect", effect1);
+            assertNotNull("could not create AudioEffect", effect2);
+
+            int status = effect2.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_SMALLROOM);
+            assertEquals("Effect2 setParameter failed",
+                    AudioEffect.SUCCESS, status);
+
+            status = effect1.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_MEDIUMROOM);
+            assertEquals("Effect1 setParameter did not fail",
+                    AudioEffect.ERROR_INVALID_OPERATION, status);
+
+            short[] value = new short[1];
+            status = effect2.getParameter(PresetReverb.PARAM_PRESET, value);
+            assertEquals("Effect2 getParameter failed",
+                    AudioEffect.SUCCESS, status);
+            assertEquals("Effect1 changed parameter", PresetReverb.PRESET_SMALLROOM
+                    , value[0]);
+
+
+        } catch (IllegalArgumentException e) {
+            fail("Effect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+    }
+
+    //Test case 4.2: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test4_2ControlStatusListener() throws Exception {
+
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Create second effect: wait was interrupted.");
+            } finally {
+                releaseEffect();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 4.3: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test4_3EnableStatusListener() throws Exception {
+
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mEffect2.setEnabled(true);
+        mIsEnabled = true;
+
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        assertTrue("effect not enabled", mEffect.getEnabled());
+        synchronized(mLock) {
+            try {
+                mEffect.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Second effect setEnabled: wait was interrupted.");
+            } finally {
+                releaseEffect();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 4.4: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {AudioEffect.OnParameterChangeListener.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameter",
+            args = {int.class, short.class}
+        )
+    })
+    public void test4_4ParameterChangedListener() throws Exception {
+
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        int status = mEffect2.setParameter(PresetReverb.PARAM_PRESET,
+                PresetReverb.PRESET_SMALLROOM);
+        assertEquals("mEffect2 setParameter failed",
+                AudioEffect.SUCCESS, status);
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mEffect.setParameter(PresetReverb.PARAM_PRESET,
+                        PresetReverb.PRESET_MEDIUMROOM);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                fail("set Parameter: wait was interrupted.");
+            } finally {
+                releaseEffect();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                PresetReverb.PARAM_PRESET, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // 5 command method
+    //----------------------------------
+
+
+    //Test case 5.0: test command method
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "command",
+            args = {int.class, byte[].class, byte[].class}
+        )
+    })
+    public void test5_0Command() throws Exception {
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            byte[] cmd = new byte[0];
+            byte[] reply = new byte[4];
+            // command 3 is ENABLE
+            int status = mEffect.command(3, cmd, reply);
+            assertEquals("command failed", AudioEffect.SUCCESS, status);
+            assertTrue("effect not enabled", mEffect.getEnabled());
+
+        } catch (IllegalStateException e) {
+            fail("command in illegal state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getEffect(UUID type, int session) {
+         if (mEffect == null || session != mSession) {
+             if (session != mSession && mEffect != null) {
+                 mEffect.release();
+                 mEffect = null;
+             }
+             try {
+                 mEffect = new AudioEffect(type,
+                                             AudioEffect.EFFECT_TYPE_NULL,
+                                             0,
+                                             session);
+                 mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getEffect() AudioEffect not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getEffect() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mEffect", mEffect);
+    }
+
+    private void releaseEffect() {
+        if (mEffect != null) {
+            mEffect.release();
+            mEffect = null;
+        }
+    }
+
+    // Initializes the equalizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mEffect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                        AudioEffect.EFFECT_TYPE_NULL,
+                        0,
+                        0);
+                assertNotNull("could not create Equalizer2", mEffect2);
+
+                if (mControl) {
+                    mEffect2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mEffect2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mEffect2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mEffect2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mEffect2.setParameterListener(new AudioEffect.OnParameterChangeListener() {
+                        public void onParameterChange(AudioEffect effect, int status, byte[] param,
+                                byte[] value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mEffect2) {
+                                    mChangedParameter = mEffect2.byteArrayToInt(param);
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffect2 != null) {
+            mEffect2.release();
+            mEffect2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+    /*
+     * Initializes the message looper so that the MediaPlayer object can
+     * receive the callback messages.
+     */
+    private void createMediaPlayerLooper() {
+        mInitialized = false;
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mMediaPlayer = new MediaPlayer();
+                mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                    public boolean onError(MediaPlayer player, int what, int extra) {
+                        synchronized(mLock) {
+                            mError = what;
+                            mLock.notify();
+                        }
+                        return true;
+                    }
+                });
+                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                    public void onCompletion(MediaPlayer player) {
+                        synchronized(mLock) {
+                            mLock.notify();
+                        }
+                    }
+                });
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the message looper thread.
+     */
+    private void terminateMediaPlayerLooper() {
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/BassBoostTest.java b/tests/tests/media/src/android/media/cts/BassBoostTest.java
new file mode 100644
index 0000000..9cea0b0
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/BassBoostTest.java
@@ -0,0 +1,506 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.BassBoost;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(BassBoost.class)
+public class BassBoostTest extends AndroidTestCase {
+
+    private String TAG = "BassBoostTest";
+    private final static short TEST_STRENGTH = 500;
+    private final static short TEST_STRENGTH2 = 1000;
+    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
+
+    private BassBoost mBassBoost = null;
+    private BassBoost mBassBoost2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+
+    //-----------------------------------------------------------------
+    // BASS BOOST TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "BassBoost",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        BassBoost eq = null;
+        try {
+            eq = new BassBoost(0, 0);
+            assertNotNull("could not create BassBoost", eq);
+            try {
+                assertTrue("invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("BassBoost not initialized");
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("BassBoost not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getStrengthSupported",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setStrength",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getRoundedStrength",
+            args = {}
+        )
+    })
+    public void test1_0Strength() throws Exception {
+        getBassBoost(0);
+        try {
+            if (mBassBoost.getStrengthSupported()) {
+                short strength = mBassBoost.getRoundedStrength();
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+                mBassBoost.setStrength((short)strength);
+                short strength2 = mBassBoost.getRoundedStrength();
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
+            } else {
+                short strength = mBassBoost.getRoundedStrength();
+                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 1.1: test properties
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getProperties",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setProperties",
+            args = {BassBoost.Settings.class}
+        )
+    })
+    public void test1_1Properties() throws Exception {
+        getBassBoost(0);
+        try {
+            BassBoost.Settings settings = mBassBoost.getProperties();
+            String str = settings.toString();
+            settings = new BassBoost.Settings(str);
+
+            short strength = settings.strength;
+            if (mBassBoost.getStrengthSupported()) {
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+            }
+            settings.strength = strength;
+            mBassBoost.setProperties(settings);
+            settings = mBassBoost.getProperties();
+
+            if (mBassBoost.getStrengthSupported()) {
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 1.2: test setStrength() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setStrength",
+            args = {short.class}
+        )
+    })
+    public void test1_2SetStrengthAfterRelease() throws Exception {
+        getBassBoost(0);
+        mBassBoost.release();
+        try {
+            mBassBoost.setStrength(TEST_STRENGTH);
+            fail("setStrength() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getBassBoost(0);
+        try {
+            mBassBoost.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mBassBoost.getEnabled());
+            mBassBoost.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mBassBoost.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        getBassBoost(0);
+        mBassBoost.release();
+        try {
+            mBassBoost.setEnabled(true);
+            fail("setEnabled() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test3_0ControlStatusListener() throws Exception {
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getBassBoost(0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseBassBoost();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        )
+    })
+    public void test3_1EnableStatusListener() throws Exception {
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mBassBoost2.setEnabled(true);
+        mIsEnabled = true;
+        getBassBoost(0);
+        synchronized(mLock) {
+            try {
+                mBassBoost.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseBassBoost();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {BassBoost.OnParameterChangeListener.class}
+        )
+    })
+    public void test3_2ParameterChangedListener() throws Exception {
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        getBassBoost(0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mBassBoost.setStrength(TEST_STRENGTH);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseBassBoost();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                BassBoost.PARAM_STRENGTH, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getBassBoost(int session) {
+         if (mBassBoost == null || session != mSession) {
+             if (session != mSession && mBassBoost != null) {
+                 mBassBoost.release();
+                 mBassBoost = null;
+             }
+             try {
+                mBassBoost = new BassBoost(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getBassBoost() BassBoost not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getBassBoost() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mBassBoost", mBassBoost);
+    }
+
+    private void releaseBassBoost() {
+        if (mBassBoost != null) {
+            mBassBoost.release();
+            mBassBoost = null;
+        }
+    }
+
+    // Initializes the bassboot listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mBassBoost2 = new BassBoost(0, 0);
+                assertNotNull("could not create bassboot2", mBassBoost2);
+
+                if (mControl) {
+                    mBassBoost2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mBassBoost2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mBassBoost2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mBassBoost2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mBassBoost2.setParameterListener(new BassBoost.OnParameterChangeListener() {
+                        public void onParameterChange(BassBoost effect, int status,
+                                int param, short value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mBassBoost2) {
+                                    mChangedParameter = param;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mBassBoost2 != null) {
+            mBassBoost2.release();
+            mBassBoost2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
new file mode 100644
index 0000000..b94da11
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.media.cts;
+
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+import android.hardware.Camera;
+import android.media.CamcorderProfile;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
+
+@TestTargetClass(CamcorderProfile.class)
+public class CamcorderProfileTest extends AndroidTestCase {
+
+    private static final String TAG = "CamcorderProfileTest";
+
+    private void checkProfile(CamcorderProfile profile) {
+        Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
+            "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
+            "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
+            "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
+            profile.duration,
+            profile.quality,
+            profile.fileFormat,
+            profile.videoCodec,
+            profile.videoBitRate,
+            profile.videoFrameRate,
+            profile.videoFrameWidth,
+            profile.videoFrameHeight,
+            profile.audioCodec,
+            profile.audioBitRate,
+            profile.audioSampleRate,
+            profile.audioChannels));
+        assertTrue(profile.duration > 0);
+        assertTrue(profile.quality == CamcorderProfile.QUALITY_LOW ||
+                   profile.quality == CamcorderProfile.QUALITY_HIGH);
+        assertTrue(profile.videoBitRate > 0);
+        assertTrue(profile.videoFrameRate > 0);
+        assertTrue(profile.videoFrameWidth > 0);
+        assertTrue(profile.videoFrameHeight > 0);
+        assertTrue(profile.audioBitRate > 0);
+        assertTrue(profile.audioSampleRate > 0);
+        assertTrue(profile.audioChannels > 0);
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "get",
+            args = {int.class}
+        )
+    })
+    public void testGet() {
+        CamcorderProfile lowProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW);
+        CamcorderProfile highProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
+        checkProfile(lowProfile);
+        checkProfile(highProfile);
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "get",
+            args = {int.class, int.class}
+        )
+    })
+    public void testGetWithId() {
+        int nCamera = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCamera; id++) {
+            CamcorderProfile lowProfile = CamcorderProfile.get(id,
+                    CamcorderProfile.QUALITY_LOW);
+            CamcorderProfile highProfile = CamcorderProfile.get(id,
+                    CamcorderProfile.QUALITY_HIGH);
+            checkProfile(lowProfile);
+            checkProfile(highProfile);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/CameraProfileTest.java b/tests/tests/media/src/android/media/cts/CameraProfileTest.java
new file mode 100644
index 0000000..72cd296a
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CameraProfileTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.media.cts;
+
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+import android.hardware.Camera;
+import android.media.CameraProfile;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
+
+@TestTargetClass(CameraProfile.class)
+public class CameraProfileTest extends AndroidTestCase {
+
+    private static final String TAG = "CameraProfileTest";
+
+    private void checkQuality(int low, int mid, int high) {
+        Log.v(TAG, "low = " + low + ", mid = " + mid + ", high = " + high);
+        assertTrue(low >= 0 && low <= 100);
+        assertTrue(mid >= 0 && mid <= 100);
+        assertTrue(high >= 0 && high <= 100);
+        assertTrue(low <= mid && mid <= high);
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getJpegEncodingQualityParameter",
+            args = {int.class}
+        )
+    })
+    public void testGetImageEncodingQualityParameter() {
+        int low = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_LOW);
+        int mid = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_MEDIUM);
+        int high = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH);
+        checkQuality(low, mid, high);
+    }
+
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getJpegEncodingQualityParameter",
+            args = {int.class, int.class}
+        )
+    })
+    public void testGetWithId() {
+        int nCamera = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCamera; id++) {
+            int low = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_LOW);
+            int mid = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_MEDIUM);
+            int high = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_HIGH);
+            checkQuality(low, mid, high);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/EnvReverbTest.java b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
new file mode 100644
index 0000000..fe7baf9
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
@@ -0,0 +1,698 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.EnvironmentalReverb;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(EnvironmentalReverb.class)
+public class EnvReverbTest extends AndroidTestCase {
+
+    private String TAG = "EnvReverbTest";
+    private final static int MILLIBEL_TOLERANCE = 100;            // +/-1dB
+    private final static float DELAY_TOLERANCE = 1.05f;           // 5%
+    private final static float RATIO_TOLERANCE = 1.05f;           // 5%
+
+    private EnvironmentalReverb mReverb = null;
+    private EnvironmentalReverb mReverb2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+
+
+    //-----------------------------------------------------------------
+    // ENVIRONMENTAL REVERB TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "EnvironmentalReverb",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        EnvironmentalReverb envReverb = null;
+         try {
+            envReverb = new EnvironmentalReverb(0, 0);
+            assertNotNull("could not create EnvironmentalReverb", envReverb);
+            try {
+                assertTrue("invalid effect ID", (envReverb.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("EnvironmentalReverb not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("EnvironmentalReverb not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (envReverb != null) {
+                envReverb.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test room level and room HF level
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setRoomLevel",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getRoomLevel",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setRoomHFLevel",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getRoomHFLevel",
+            args = {}
+        )
+    })
+    public void test1_0Room() throws Exception {
+        getReverb(0);
+        try {
+            short level = mReverb.getRoomLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setRoomLevel(level);
+            short level2 = mReverb.getRoomLevel();
+            assertTrue("got incorrect room level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+            level = mReverb.getRoomHFLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setRoomHFLevel(level);
+            level2 = mReverb.getRoomHFLevel();
+            assertTrue("got incorrect room HF level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.1: test decay time and ratio
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setDecayTime",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getDecayTime",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setDecayHFRatio",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getDecayHFRatio",
+            args = {}
+        )
+    })
+    public void test1_1Decay() throws Exception {
+        getReverb(0);
+        try {
+            int time = mReverb.getDecayTime();
+            time = (time == 500) ? 1000 : 500;
+            mReverb.setDecayTime(time);
+            int time2 = mReverb.getDecayTime();
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            short ratio = mReverb.getDecayHFRatio();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDecayHFRatio(ratio);
+            short ratio2 = mReverb.getDecayHFRatio();
+            assertTrue("got incorrect decay HF ratio",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+
+    //Test case 1.2: test reverb level and delay
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setReverbLevel",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getReverbLevel",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setReverbDelay",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getReverbDelay",
+            args = {}
+        )
+    })
+    public void test1_2Reverb() throws Exception {
+        getReverb(0);
+        try {
+            short level = mReverb.getReverbLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setReverbLevel(level);
+            short level2 = mReverb.getReverbLevel();
+            assertTrue("got incorrect reverb level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+// FIXME:uncomment actual test when early reflections are implemented in the reverb
+//            int time = mReverb.getReverbDelay();
+//             mReverb.setReverbDelay(time);
+//            int time2 = mReverb.getReverbDelay();
+//            assertTrue("got incorrect reverb delay",
+//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            mReverb.setReverbDelay(0);
+            int time2 = mReverb.getReverbDelay();
+            assertEquals("got incorrect reverb delay", mReverb.getReverbDelay(), 0);
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.3: test early reflections level and delay
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setReflectionsLevel",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getReflectionsLevel",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setReflectionsDelay",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getReflectionsDelay",
+            args = {}
+        )
+    })
+    public void test1_3Reflections() throws Exception {
+        getReverb(0);
+        try {
+// FIXME:uncomment actual test when early reflections are implemented in the reverb
+//            short level = mReverb.getReflectionsLevel();
+//            level = (short)((level == 0) ? -1000 : 0);
+//            mReverb.setReflectionsLevel(level);
+//            short level2 = mReverb.getReflectionsLevel();
+//            assertTrue("got incorrect reflections level",
+//                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+//                    (level2 < (level + MILLIBEL_TOLERANCE)));
+//
+//            int time = mReverb.getReflectionsDelay();
+//            time = (time == 20) ? 0 : 20;
+//            mReverb.setReflectionsDelay(time);
+//            int time2 = mReverb.getReflectionsDelay();
+//            assertTrue("got incorrect reflections delay",
+//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            mReverb.setReflectionsLevel((short) 0);
+            assertEquals("got incorrect reverb delay",
+                    mReverb.getReflectionsLevel(), (short) 0);
+            mReverb.setReflectionsDelay(0);
+            assertEquals("got incorrect reverb delay",
+                    mReverb.getReflectionsDelay(), 0);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.4: test diffusion and density
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setDiffusion",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getDiffusion",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setDensity",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getDensity",
+            args = {}
+        )
+    })
+    public void test1_4DiffusionAndDensity() throws Exception {
+        getReverb(0);
+        try {
+            short ratio = mReverb.getDiffusion();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDiffusion(ratio);
+            short ratio2 = mReverb.getDiffusion();
+            assertTrue("got incorrect diffusion",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+            ratio = mReverb.getDensity();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDensity(ratio);
+            ratio2 = mReverb.getDensity();
+            assertTrue("got incorrect density",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.5: test properties
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getProperties",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setProperties",
+            args = {EnvironmentalReverb.Settings.class}
+        )
+    })
+    public void test1_5Properties() throws Exception {
+        getReverb(0);
+        try {
+            EnvironmentalReverb.Settings settings = mReverb.getProperties();
+            String str = settings.toString();
+            settings = new EnvironmentalReverb.Settings(str);
+            short level = (short)((settings.roomLevel == 0) ? -1000 : 0);
+            settings.roomLevel = level;
+            mReverb.setProperties(settings);
+            settings = mReverb.getProperties();
+            assertTrue("setProperties failed",
+                    (settings.roomLevel >= (level - MILLIBEL_TOLERANCE)) &&
+                    (settings.roomLevel <= (level + MILLIBEL_TOLERANCE)));
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getReverb(0);
+        try {
+            mReverb.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
+            mReverb.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        getReverb(0);
+        mReverb.release();
+        try {
+            mReverb.setEnabled(true);
+            fail("setEnabled() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test3_0ControlStatusListener() throws Exception {
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getReverb(0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        )
+    })
+    public void test3_1EnableStatusListener() throws Exception {
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mReverb2.setEnabled(true);
+        mIsEnabled = true;
+        getReverb(0);
+        synchronized(mLock) {
+            try {
+                mReverb.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {EnvironmentalReverb.OnParameterChangeListener.class}
+        )
+    })
+    public void test3_2ParameterChangedListener() throws Exception {
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        getReverb(0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mReverb.setRoomLevel((short)0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                EnvironmentalReverb.PARAM_ROOM_LEVEL, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getReverb(int session) {
+         if (mReverb == null || session != mSession) {
+             if (session != mSession && mReverb != null) {
+                 mReverb.release();
+                 mReverb = null;
+             }
+             try {
+                mReverb = new EnvironmentalReverb(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getReverb() EnvironmentalReverb not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mReverb", mReverb);
+    }
+
+    private void releaseReverb() {
+        if (mReverb != null) {
+            mReverb.release();
+            mReverb = null;
+        }
+    }
+
+    // Initializes the reverb listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mReverb2 = new EnvironmentalReverb(0, 0);
+                assertNotNull("could not create reverb2", mReverb2);
+
+                if (mControl) {
+                    mReverb2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mReverb2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mReverb2.setParameterListener(new EnvironmentalReverb.OnParameterChangeListener() {
+                        public void onParameterChange(EnvironmentalReverb effect,
+                                int status, int param, int value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mChangedParameter = param;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mReverb2 != null) {
+            mReverb2.release();
+            mReverb2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/EqualizerTest.java b/tests/tests/media/src/android/media/cts/EqualizerTest.java
new file mode 100644
index 0000000..4b63828
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EqualizerTest.java
@@ -0,0 +1,600 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.Equalizer;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(Equalizer.class)
+public class EqualizerTest extends AndroidTestCase {
+
+    private String TAG = "EqualizerTest";
+    private final static int MIN_NUMBER_OF_BANDS = 4;
+    private final static int MAX_LEVEL_RANGE_LOW = -1200;         // -12dB
+    private final static int MIN_LEVEL_RANGE_HIGH = 1200;         // +12dB
+    private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000; // 1kHz
+    private final static int MIN_NUMBER_OF_PRESETS = 0;
+    private final static float TOLERANCE = 100;                   // +/-1dB
+
+    private Equalizer mEqualizer = null;
+    private Equalizer mEqualizer2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+
+
+    //-----------------------------------------------------------------
+    // EQUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "Equalizer",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        Equalizer eq = null;
+        try {
+            eq = new Equalizer(0, 0);
+            assertNotNull("could not create Equalizer", eq);
+            try {
+                assertTrue("invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("Equalizer not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test setBandLevel() and getBandLevel()
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getNumberOfBands",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getBandLevelRange",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setBandLevel",
+            args = {short.class, short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getBandLevel",
+            args = {short.class}
+        )
+    })
+    public void test1_0BandLevel() throws Exception {
+        getEqualizer(0);
+        try {
+            short numBands = mEqualizer.getNumberOfBands();
+            assertTrue("not enough bands", numBands >= MIN_NUMBER_OF_BANDS);
+
+            short[] levelRange = mEqualizer.getBandLevelRange();
+            assertTrue("min level too high", levelRange[0] <= MAX_LEVEL_RANGE_LOW);
+            assertTrue("max level too low", levelRange[1] >= MIN_LEVEL_RANGE_HIGH);
+
+            mEqualizer.setBandLevel((short)0, levelRange[1]);
+            short level = mEqualizer.getBandLevel((short)0);
+            // allow +/- TOLERANCE margin on actual level compared to requested level
+            assertTrue("setBandLevel failed",
+                    (level >= (levelRange[1] - TOLERANCE)) &&
+                    (level <= (levelRange[1] + TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.1: test band frequency
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getBand",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getBandFreqRange",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getCenterFreq",
+            args = {short.class}
+        )
+    })
+    public void test1_1BandFrequency() throws Exception {
+        getEqualizer(0);
+        try {
+            short band = mEqualizer.getBand(TEST_FREQUENCY_MILLIHERTZ);
+            assertTrue("getBand failed", band >= 0);
+            int[] freqRange = mEqualizer.getBandFreqRange(band);
+            assertTrue("getBandFreqRange failed",
+                    (freqRange[0] <= TEST_FREQUENCY_MILLIHERTZ) &&
+                    (freqRange[1] >= TEST_FREQUENCY_MILLIHERTZ));
+            int freq = mEqualizer.getCenterFreq(band);
+            assertTrue("getCenterFreq failed",
+                    (freqRange[0] <= freq) && (freqRange[1] >= freq));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.2: test presets
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getNumberOfPresets",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "usePreset",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getCurrentPreset",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getPresetName",
+            args = {short.class}
+        )
+    })
+    public void test1_2Presets() throws Exception {
+        getEqualizer(0);
+        try {
+            short numPresets = mEqualizer.getNumberOfPresets();
+            assertTrue("getNumberOfPresets failed", numPresets >= MIN_NUMBER_OF_PRESETS);
+            if (numPresets > 0) {
+                mEqualizer.usePreset((short)(numPresets - 1));
+                short preset = mEqualizer.getCurrentPreset();
+                assertEquals("usePreset failed", preset, (short)(numPresets - 1));
+                String name = mEqualizer.getPresetName(preset);
+                assertNotNull("getPresetName failed", name);
+            }
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.3: test properties
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getProperties",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setProperties",
+            args = {Equalizer.Settings.class}
+        )
+    })
+    public void test1_3Properties() throws Exception {
+        getEqualizer(0);
+        try {
+            Equalizer.Settings settings = mEqualizer.getProperties();
+            assertTrue("no enough bands", settings.numBands >= MIN_NUMBER_OF_BANDS);
+            short newLevel = 0;
+            if (settings.bandLevels[0] == 0) {
+                newLevel = -600;
+            }
+            String str = settings.toString();
+            settings = new Equalizer.Settings(str);
+            settings.curPreset = (short)-1;
+            settings.bandLevels[0] = newLevel;
+            mEqualizer.setProperties(settings);
+            settings = mEqualizer.getProperties();
+            assertTrue("setProperties failed",
+                    (settings.bandLevels[0] >= (newLevel - TOLERANCE)) &&
+                    (settings.bandLevels[0] <= (newLevel + TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.4: test setBandLevel() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setBandLevel",
+            args = {short.class, short.class}
+        )
+    })
+    public void test1_4SetBandLevelAfterRelease() throws Exception {
+
+        getEqualizer(0);
+        mEqualizer.release();
+        try {
+            mEqualizer.setBandLevel((short)0, (short)0);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getEqualizer(0);
+        try {
+            mEqualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mEqualizer.getEnabled());
+            mEqualizer.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mEqualizer.getEnabled());
+
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+
+        getEqualizer(0);
+        mEqualizer.release();
+        try {
+            mEqualizer.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test3_0ControlStatusListener() throws Exception {
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getEqualizer(0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseEqualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        )
+    })
+    public void test3_1EnableStatusListener() throws Exception {
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mEqualizer2.setEnabled(true);
+        mIsEnabled = true;
+        getEqualizer(0);
+        synchronized(mLock) {
+            try {
+                mEqualizer.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseEqualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {Equalizer.OnParameterChangeListener.class}
+        )
+    })
+    public void test3_2ParameterChangedListener() throws Exception {
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        getEqualizer(0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mEqualizer.setBandLevel((short)0, (short)0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseEqualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                Equalizer.PARAM_BAND_LEVEL, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getEqualizer(int session) {
+         if (mEqualizer == null || session != mSession) {
+             if (session != mSession && mEqualizer != null) {
+                 mEqualizer.release();
+                 mEqualizer = null;
+             }
+             try {
+                mEqualizer = new Equalizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getEqualizer() Equalizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getEqualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mEqualizer", mEqualizer);
+    }
+
+    private void releaseEqualizer() {
+        if (mEqualizer != null) {
+            mEqualizer.release();
+            mEqualizer = null;
+        }
+    }
+
+    // Initializes the equalizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mEqualizer2 = new Equalizer(0, 0);
+                assertNotNull("could not create Equalizer2", mEqualizer2);
+
+                if (mControl) {
+                    mEqualizer2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mEqualizer2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mEqualizer2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mEqualizer2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mEqualizer2.setParameterListener(new Equalizer.OnParameterChangeListener() {
+                        public void onParameterChange(Equalizer effect,
+                                int status, int param1, int param2, int value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mEqualizer2) {
+                                    mChangedParameter = param1;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEqualizer2 != null) {
+            mEqualizer2.release();
+            mEqualizer2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index e5dc1ff..db9c5be 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -38,8 +38,8 @@
 
     private final String OUTPUT_PATH;
     private static final int RECORD_TIME = 3000;
-    private static final int VIDEO_WIDTH = 320;
-    private static final int VIDEO_HEIGHT = 240;
+    private static final int VIDEO_WIDTH = 176;
+    private static final int VIDEO_HEIGHT = 144;
     private static final int FRAME_RATE = 15;
     private static final long MAX_FILE_SIZE = 5000;
     private static final int MAX_DURATION_MSEC = 200;
diff --git a/tests/tests/media/src/android/media/cts/PresetReverbTest.java b/tests/tests/media/src/android/media/cts/PresetReverbTest.java
new file mode 100644
index 0000000..a1b05bd
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/PresetReverbTest.java
@@ -0,0 +1,452 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.PresetReverb;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(PresetReverb.class)
+public class PresetReverbTest extends AndroidTestCase {
+
+    private String TAG = "PresetReverbTest";
+    private final static short FIRST_PRESET = PresetReverb.PRESET_NONE;
+    private final static short LAST_PRESET = PresetReverb.PRESET_PLATE;
+    private PresetReverb mReverb = null;
+    private PresetReverb mReverb2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+
+
+    //-----------------------------------------------------------------
+    // PRESET REVERB TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "PresetReverb",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        PresetReverb reverb = null;
+        try {
+            reverb = new PresetReverb(0, 0);
+            assertNotNull("could not create PresetReverb", reverb);
+            try {
+                assertTrue("invalid effect ID", (reverb.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("PresetReverb not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("PresetReverb not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (reverb != null) {
+                reverb.release();
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test presets
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setPreset",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getPreset",
+            args = {}
+        )
+    })
+    public void test1_0Presets() throws Exception {
+        getReverb(0);
+        try {
+            for (short preset = FIRST_PRESET;
+                 preset <= LAST_PRESET;
+                 preset++) {
+                mReverb.setPreset(preset);
+                assertEquals("got incorrect preset", preset, mReverb.getPreset());
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.1: test properties
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getProperties",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setProperties",
+            args = {PresetReverb.Settings.class}
+        )
+    })
+    public void test1_1Properties() throws Exception {
+        getReverb(0);
+        try {
+            PresetReverb.Settings settings = mReverb.getProperties();
+            String str = settings.toString();
+            settings = new PresetReverb.Settings(str);
+            short preset = (settings.preset == PresetReverb.PRESET_SMALLROOM) ?
+                            PresetReverb.PRESET_MEDIUMROOM : PresetReverb.PRESET_SMALLROOM;
+            settings.preset = preset;
+            mReverb.setProperties(settings);
+            settings = mReverb.getProperties();
+            assertEquals("setProperties failed", settings.preset, preset);
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getReverb(0);
+        try {
+            mReverb.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
+            mReverb.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        getReverb(0);
+        mReverb.release();
+        try {
+            mReverb.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test3_0ControlStatusListener() throws Exception {
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getReverb(0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        )
+    })
+    public void test3_1EnableStatusListener() throws Exception {
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mReverb2.setEnabled(true);
+        mIsEnabled = true;
+        getReverb(0);
+        synchronized(mLock) {
+            try {
+                mReverb.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {PresetReverb.OnParameterChangeListener.class}
+        )
+    })
+    public void test3_2ParameterChangedListener() throws Exception {
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        getReverb(0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mReverb.setPreset(PresetReverb.PRESET_SMALLROOM);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseReverb();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                PresetReverb.PARAM_PRESET, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getReverb(int session) {
+         if (mReverb == null || session != mSession) {
+             if (session != mSession && mReverb != null) {
+                 mReverb.release();
+                 mReverb = null;
+             }
+             try {
+                mReverb = new PresetReverb(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getReverb() PresetReverb not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mReverb", mReverb);
+    }
+
+    private void releaseReverb() {
+        if (mReverb != null) {
+            mReverb.release();
+            mReverb = null;
+        }
+    }
+
+    // Initializes the reverb listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mReverb2 = new PresetReverb(0, 0);
+                assertNotNull("could not create Reverb2", mReverb2);
+
+                if (mControl) {
+                    mReverb2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mReverb2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mReverb2.setParameterListener(new PresetReverb.OnParameterChangeListener() {
+                        public void onParameterChange(PresetReverb effect,
+                                int status, int param, short value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mReverb2) {
+                                    mChangedParameter = param;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mReverb2 != null) {
+            mReverb2.release();
+            mReverb2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/VideoEditorTest.java b/tests/tests/media/src/android/media/cts/VideoEditorTest.java
new file mode 100644
index 0000000..481f4d6
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoEditorTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.media.cts;
+
+import com.android.cts.stub.R;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+public class VideoEditorTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+
+    public VideoEditorTest() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        //setup for each test case.
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        //Test case clean up.
+        super.tearDown();
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/VirtualizerTest.java b/tests/tests/media/src/android/media/cts/VirtualizerTest.java
new file mode 100644
index 0000000..0a82f09
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VirtualizerTest.java
@@ -0,0 +1,500 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.Virtualizer;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(Virtualizer.class)
+public class VirtualizerTest extends AndroidTestCase {
+
+    private String TAG = "VirtualizerTest";
+    private final static short TEST_STRENGTH = 500;
+    private final static short TEST_STRENGTH2 = 1000;
+    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
+
+    private Virtualizer mVirtualizer = null;
+    private Virtualizer mVirtualizer2 = null;
+    private int mSession = -1;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mChangedParameter = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+
+    //-----------------------------------------------------------------
+    // VIRTUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "Virtualizer",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getId",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        Virtualizer eq = null;
+        try {
+            eq = new Virtualizer(0, 0);
+            assertNotNull(" could not create Virtualizer", eq);
+            try {
+                assertTrue(" invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("Virtualizer not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Virtualizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getStrengthSupported",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setStrength",
+            args = {short.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getRoundedStrength",
+            args = {}
+        )
+    })
+    public void test1_0Strength() throws Exception {
+        getVirtualizer(0);
+        try {
+            if (mVirtualizer.getStrengthSupported()) {
+                short strength = mVirtualizer.getRoundedStrength();
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+                mVirtualizer.setStrength((short)strength);
+                short strength2 = mVirtualizer.getRoundedStrength();
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
+            } else {
+                short strength = mVirtualizer.getRoundedStrength();
+                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 1.1: test properties
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getProperties",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setProperties",
+            args = {Virtualizer.Settings.class}
+        )
+    })
+    public void test1_1Properties() throws Exception {
+        getVirtualizer(0);
+        try {
+            Virtualizer.Settings settings = mVirtualizer.getProperties();
+            String str = settings.toString();
+            settings = new Virtualizer.Settings(str);
+
+            short strength = settings.strength;
+            if (mVirtualizer.getStrengthSupported()) {
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+            }
+            settings.strength = strength;
+            mVirtualizer.setProperties(settings);
+            settings = mVirtualizer.getProperties();
+
+            if (mVirtualizer.getStrengthSupported()) {
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 1.2: test setStrength() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setStrength",
+            args = {short.class}
+        )
+    })
+    public void test1_2SetStrengthAfterRelease() throws Exception {
+        getVirtualizer(0);
+        mVirtualizer.release();
+        try {
+            mVirtualizer.setStrength(TEST_STRENGTH);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        )
+    })
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getVirtualizer(0);
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue(" invalid state from getEnabled", mVirtualizer.getEnabled());
+            mVirtualizer.setEnabled(false);
+            assertFalse(" invalid state to getEnabled", mVirtualizer.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        )
+    })
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        getVirtualizer(0);
+        mVirtualizer.release();
+        try {
+            mVirtualizer.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setControlStatusListener",
+            args = {AudioEffect.OnControlStatusChangeListener.class}
+        )
+    })
+    public void test3_0ControlStatusListener() throws Exception {
+        mHasControl = true;
+        createListenerLooper(true, false, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        synchronized(mLock) {
+            try {
+                getVirtualizer(0);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseVirtualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnableStatusListener",
+            args = {AudioEffect.OnEnableStatusChangeListener.class}
+        )
+    })
+    public void test3_1EnableStatusListener() throws Exception {
+        createListenerLooper(false, true, false);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        mVirtualizer2.setEnabled(true);
+        mIsEnabled = true;
+        getVirtualizer(0);
+        synchronized(mLock) {
+            try {
+                mVirtualizer.setEnabled(false);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseVirtualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setParameterListener",
+            args = {Virtualizer.OnParameterChangeListener.class}
+        )
+    })
+    public void test3_2ParameterChangedListener() throws Exception {
+        createListenerLooper(false, false, true);
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Looper creation: wait was interrupted.");
+            }
+        }
+        assertTrue(mInitialized);
+        getVirtualizer(0);
+        synchronized(mLock) {
+            try {
+                mChangedParameter = -1;
+                mVirtualizer.setStrength(TEST_STRENGTH);
+                mLock.wait(1000);
+            } catch(Exception e) {
+                Log.e(TAG, "Create second effect: wait was interrupted.");
+            } finally {
+                releaseVirtualizer();
+                terminateListenerLooper();
+            }
+        }
+        assertEquals("parameter change not received",
+                Virtualizer.PARAM_STRENGTH, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getVirtualizer(int session) {
+         if (mVirtualizer == null || session != mSession) {
+             if (session != mSession && mVirtualizer != null) {
+                 mVirtualizer.release();
+                 mVirtualizer = null;
+             }
+             try {
+                mVirtualizer = new Virtualizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVirtualizer() Virtualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVirtualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVirtualizer", mVirtualizer);
+    }
+
+    private void releaseVirtualizer() {
+        if (mVirtualizer != null) {
+            mVirtualizer.release();
+            mVirtualizer = null;
+        }
+    }
+
+    // Initializes the virtualizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mInitialized = false;
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mVirtualizer2 = new Virtualizer(0, 0);
+                assertNotNull("could not create virtualizer2", mVirtualizer2);
+
+                if (mControl) {
+                    mVirtualizer2.setControlStatusListener(
+                            new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(
+                                AudioEffect effect, boolean controlGranted) {
+                            synchronized(mLock) {
+                                if (effect == mVirtualizer2) {
+                                    mHasControl = controlGranted;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mVirtualizer2.setEnableStatusListener(
+                            new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(mLock) {
+                                if (effect == mVirtualizer2) {
+                                    mIsEnabled = enabled;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mVirtualizer2.setParameterListener(new Virtualizer.OnParameterChangeListener() {
+                        public void onParameterChange(Virtualizer effect, int status,
+                                int param, short value)
+                        {
+                            synchronized(mLock) {
+                                if (effect == mVirtualizer2) {
+                                    mChangedParameter = param;
+                                    mLock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mVirtualizer2 != null) {
+            mVirtualizer2.release();
+            mVirtualizer2 = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/VisualizerTest.java b/tests/tests/media/src/android/media/cts/VisualizerTest.java
new file mode 100644
index 0000000..6b29948
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VisualizerTest.java
@@ -0,0 +1,411 @@
+/*
+ * 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.media.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.Visualizer;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import dalvik.annotation.TestLevel;
+import dalvik.annotation.TestTargetClass;
+import dalvik.annotation.TestTargetNew;
+import dalvik.annotation.TestTargets;
+
+@TestTargetClass(Visualizer.class)
+public class VisualizerTest extends AndroidTestCase {
+
+    private String TAG = "VisualizerTest";
+    private final static int MIN_CAPTURE_RATE_MAX = 10000; // 10Hz
+    private final static int MIN_CAPTURE_SIZE_MAX = 1024;
+    private final static int MAX_CAPTURE_SIZE_MIN = 512;
+
+    private Visualizer mVisualizer = null;
+    private int mSession = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object mLock = new Object();
+    private byte[] mWaveform = null;
+    private byte[] mFft = null;
+    private boolean mCaptureWaveform = false;
+    private boolean mCaptureFft = false;
+
+    //-----------------------------------------------------------------
+    // VISUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "Visualizer",
+            args = {int.class, int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "release",
+            args = {}
+        )
+    })
+    public void test0_0ConstructorAndRelease() throws Exception {
+        Visualizer visualizer = null;
+         try {
+            visualizer = new Visualizer(0);
+            assertNotNull("could not create Visualizer", visualizer);
+        } catch (IllegalArgumentException e) {
+            fail("Visualizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (visualizer != null) {
+                visualizer.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: capture rates
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getMaxCaptureRate",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getSamplingRate",
+            args = {}
+        )
+    })
+    public void test1_0CaptureRates() throws Exception {
+        getVisualizer(0);
+        try {
+            int captureRate = mVisualizer.getMaxCaptureRate();
+            assertTrue("insufficient max capture rate",
+                    captureRate >= MIN_CAPTURE_RATE_MAX);
+            int samplingRate = mVisualizer.getSamplingRate();
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 1.1: test capture size
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getCaptureSizeRange",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setCaptureSize",
+            args = {int.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getCaptureSize",
+            args = {}
+        )
+    })
+    public void test1_1CaptureSize() throws Exception {
+        getVisualizer(0);
+        try {
+            int[] range = mVisualizer.getCaptureSizeRange();
+            assertTrue("insufficient min capture size",
+                    range[0] <= MAX_CAPTURE_SIZE_MIN);
+            assertTrue("insufficient min capture size",
+                    range[1] >= MIN_CAPTURE_SIZE_MAX);
+            mVisualizer.setCaptureSize(range[0]);
+            assertEquals("insufficient min capture size",
+                    range[0], mVisualizer.getCaptureSize());
+            mVisualizer.setCaptureSize(range[1]);
+            assertEquals("insufficient min capture size",
+                    range[1], mVisualizer.getCaptureSize());
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - check capture
+    //----------------------------------
+
+    //Test case 2.0: test cature in polling mode
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getWaveForm",
+            args = {byte[].class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getFft",
+            args = {byte[].class}
+        )
+    })
+    public void test2_0PollingCapture() throws Exception {
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            // check capture on silence
+            byte[] data = new byte[mVisualizer.getCaptureSize()];
+            mVisualizer.getWaveForm(data);
+            int energy = computeEnergy(data, true);
+            assertEquals("getWaveForm reports energy for silence",
+                    0, energy);
+            mVisualizer.getFft(data);
+            energy = computeEnergy(data, false);
+            assertEquals("getFft reports energy for silence",
+                    0, energy);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        }
+        finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 2.1: test capture with listener
+    @TestTargets({
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setEnabled",
+            args = {boolean.class}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "getEnabled",
+            args = {}
+        ),
+        @TestTargetNew(
+            level = TestLevel.COMPLETE,
+            method = "setDataCaptureListener",
+            args = {Visualizer.OnDataCaptureListener.class,
+                    int.class, boolean.class, boolean.class}
+        )
+    })
+    public void test2_1ListenerCapture() throws Exception {
+        try {
+            getVisualizer(0);
+            createListenerLooper();
+            synchronized(mLock) {
+                try {
+                    mLock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);
+
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+
+            Thread.sleep(100);
+            // check capture on silence
+            synchronized(mLock) {
+                try {
+                    mCaptureWaveform = true;
+                    mLock.wait(1000);
+                    mCaptureWaveform = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture waveform: wait was interrupted.");
+                }
+            }
+            assertNotNull("waveform capture failed", mWaveform);
+            int energy = computeEnergy(mWaveform, true);
+            assertEquals("getWaveForm reports energy for silence",
+                    0, energy);
+
+            synchronized(mLock) {
+                try {
+                    mCaptureFft = true;
+                    mLock.wait(1000);
+                    mCaptureFft = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture FFT: wait was interrupted.");
+                }
+            }
+            assertNotNull("FFT capture failed", mFft);
+            energy = computeEnergy(mFft, false);
+            assertEquals("getFft reports energy for silence",
+                    0, energy);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            terminateListenerLooper();
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private int computeEnergy(byte[] data, boolean pcm) {
+        int energy = 0;
+        if (data.length != 0) {
+            if (pcm) {
+                for (int i = 0; i < data.length; i++) {
+                    int tmp = ((int)data[i] & 0xFF) - 128;
+                    energy += tmp*tmp;
+                }
+            } else {
+                energy = (int)data[0] * (int)data[0];
+                for (int i = 2; i < data.length; i += 2) {
+                    int real = (int)data[i];
+                    int img = (int)data[i + 1];
+                    energy += real * real + img * img;
+                }
+            }
+        }
+        return energy;
+    }
+
+    private void getVisualizer(int session) {
+         if (mVisualizer == null || session != mSession) {
+             if (session != mSession && mVisualizer != null) {
+                 mVisualizer.release();
+                 mVisualizer = null;
+             }
+             try {
+                mVisualizer = new Visualizer(session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVisualizer", mVisualizer);
+    }
+
+    private void releaseVisualizer() {
+        if (mVisualizer != null) {
+            mVisualizer.release();
+            mVisualizer = null;
+        }
+   }
+
+    private void createListenerLooper() {
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mEffect.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                if (mVisualizer != null) {
+                    mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
+                        public void onWaveFormDataCapture(
+                                Visualizer visualizer, byte[] waveform, int samplingRate) {
+                            synchronized(mLock) {
+                                if (visualizer == mVisualizer) {
+                                    if (mCaptureWaveform) {
+                                        mWaveform = waveform;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        }
+
+                        public void onFftDataCapture(
+                                Visualizer visualizer, byte[] fft, int samplingRate) {
+                            synchronized(mLock) {
+                                if (visualizer == mVisualizer) {
+                                    if (mCaptureFft) {
+                                        mFft = fft;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    10000,
+                    true,
+                    true);
+                }
+
+                synchronized(mLock) {
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the listener looper thread.
+     */
+    private void terminateListenerLooper() {
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index edcea9a2..cfe0872 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -97,67 +97,6 @@
         assertFalse(ConnectivityManager.isNetworkTypeValid(-1));
     }
 
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getNetworkPreference",
-            args = {}
-        ),
-        @TestTargetNew(
-            level = TestLevel.SUFFICIENT,
-            method = "setNetworkPreference",
-            args = {int.class}
-        )
-    })
-    public void testAccessNetworkPreference() {
-        int initialSetting = mCm.getNetworkPreference();
-
-        // Changing the network preference requires android.permission.WRITE_SECURE_SETTINGS,
-        // which is only available to signed or system applications.
-
-        // Setting the same preference that is already set is a no-op and does not throw
-        // a SecurityException.
-        mCm.setNetworkPreference(initialSetting);
-        assertEquals(initialSetting, mCm.getNetworkPreference());
-
-        // find a valid setting that is different from the initial setting
-        int validSetting = -1;
-        NetworkInfo[] ni = mCm.getAllNetworkInfo();
-        for (NetworkInfo n : ni) {
-            int type = n.getType();
-            if (type != initialSetting) {
-                validSetting = type;
-                break;
-            }
-        }
-        if (validSetting >= 0) {
-            try {
-                mCm.setNetworkPreference(validSetting);
-                fail("Trying to change the network preference should throw SecurityException");
-            } catch (SecurityException expected) {
-                // expected
-            }
-        }
-
-        // find an invalid setting
-        int invalidSetting = -1;
-        for (int i = 0; i < 10; i++) {
-            if (!ConnectivityManager.isNetworkTypeValid(i)) {
-                invalidSetting = i;
-                break;
-            }
-        }
-        if (invalidSetting >= 0) {
-            // illegal setting should be ignored
-            mCm.setNetworkPreference(invalidSetting);
-            assertEquals(initialSetting, mCm.getNetworkPreference());
-        }
-
-        // illegal setting should be ignored
-        mCm.setNetworkPreference(-1);
-        assertEquals(initialSetting, mCm.getNetworkPreference());
-    }
-
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         notes = "Test getAllNetworkInfo().",
diff --git a/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index 6cd5d6f..258ac4d 100644
--- a/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/tests/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -21,6 +21,7 @@
 import java.net.Socket;
 
 import javax.net.SocketFactory;
+import javax.net.ssl.SSLPeerUnverifiedException;
 
 import android.net.SSLCertificateSocketFactory;
 import android.test.AndroidTestCase;
@@ -141,4 +142,73 @@
         // The socket level is invalid.
     }
 
+    // a host and port that are expected to be available but have
+    // a cert with a different CN, in this case CN=mtalk.google.com
+    private static String TEST_CREATE_SOCKET_HOST = "mobile-gtalk.l.google.com";
+    private static int TEST_CREATE_SOCKET_PORT = 5228;
+
+    /**
+     * b/2807618 Make sure that hostname verifcation in cases were it
+     * is documented to be included by various
+     * SSLCertificateSocketFactory.createSocket messages.
+     *
+     * NOTE: Test will fail if external server is not available.
+     */
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        method = "createSocket",
+        args = {String.class, int.class}
+    )
+    public void test_createSocket_simple() throws Exception {
+        try {
+            mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
+            fail();
+        } catch (SSLPeerUnverifiedException expected) {
+            // expected
+        }
+    }
+
+    /**
+     * b/2807618 Make sure that hostname verifcation in cases were it
+     * is documented to be included by various
+     * SSLCertificateSocketFactory.createSocket messages.
+     *
+     * NOTE: Test will fail if external server is not available.
+     */
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        method = "createSocket",
+        args = {Socket.class, String.class, int.class, boolean.class}
+    )
+    public void test_createSocket_wrapping() throws Exception {
+        try {
+            Socket underlying = new Socket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
+            mFactory.createSocket(
+                    underlying, TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, true);
+            fail();
+        } catch (SSLPeerUnverifiedException expected) {
+            // expected
+        }
+    }
+
+    /**
+     * b/2807618 Make sure that hostname verifcation in cases were it
+     * is documented to be included by various
+     * SSLCertificateSocketFactory.createSocket messages.
+     *
+     * NOTE: Test will fail if external server is not available.
+     */
+    @TestTargetNew(
+        level = TestLevel.COMPLETE,
+        method = "createSocket",
+        args = {String.class, int.class, InetAddress.class, int.class}
+    )
+    public void test_createSocket_bind() throws Exception {
+        try {
+            mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, null, 0);
+            fail();
+        } catch (SSLPeerUnverifiedException expected) {
+            // expected
+        }
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index 62abc5a..a0b0459 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -108,9 +108,12 @@
 
     private static final Pattern DEVICE_PATTERN =
         Pattern.compile("^([0-9A-Za-z_-]+)$");
+    private static final Pattern SERIAL_NUMBER_PATTERN =
+        Pattern.compile("^([0-9A-Za-z]{0,20})$");
 
     /** Tests that check for valid values of constants in Build. */
     public void testBuildConstants() {
+        assertTrue(SERIAL_NUMBER_PATTERN.matcher(Build.SERIAL).matches());
         assertTrue(DEVICE_PATTERN.matcher(Build.DEVICE).matches());
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 61e1837..7225363 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -27,20 +27,21 @@
 public class BuildVersionTest extends TestCase {
 
     private static final String LOG_TAG = "BuildVersionTest";
-    private static final String EXPECTED_RELEASE = "2.2.1";
-    private static final String EXPECTED_SDK = "8";
+    private static final String EXPECTED_RELEASE = "2.3";
+    private static final int EXPECTED_SDK = 9;
 
     public void testReleaseVersion() {
         // Applications may rely on the exact release version
         assertEquals(EXPECTED_RELEASE, Build.VERSION.RELEASE);
-        assertEquals(EXPECTED_SDK, Build.VERSION.SDK);
+        assertEquals("" + EXPECTED_SDK, Build.VERSION.SDK);
+        assertEquals(EXPECTED_SDK, Build.VERSION.SDK_INT);
     }
 
     /**
      * Verifies {@link Build.FINGERPRINT} follows expected format:
      * <p/>
      * <code>
-     * (BRAND)/(PRODUCT)/(DEVICE)/(BOARD):(VERSION.RELEASE)/(BUILD_ID)/
+     * (BRAND)/(PRODUCT)/(DEVICE):(VERSION.RELEASE)/(BUILD_ID)/
      * (BUILD_NUMBER):(BUILD_VARIANT)/(TAGS)
      * </code>
      */
@@ -51,18 +52,19 @@
         assertEquals("Build fingerprint must not include whitespace", -1,
                 fingerprint.indexOf(' '));
         final String[] fingerprintSegs = fingerprint.split("/");
-        assertEquals("Build fingerprint does not match expected format", 7, fingerprintSegs.length);
+        assertEquals("Build fingerprint does not match expected format", 6, fingerprintSegs.length);
         assertEquals(Build.BRAND, fingerprintSegs[0]);
         assertEquals(Build.PRODUCT, fingerprintSegs[1]);
-        assertEquals(Build.DEVICE, fingerprintSegs[2]);
-        // parse BOARD:VERSION_RELEASE
-        String[] bootloaderPlat = fingerprintSegs[3].split(":");
-        assertEquals(Build.BOARD, bootloaderPlat[0]);
-        assertEquals(Build.VERSION.RELEASE, bootloaderPlat[1]);
-        assertEquals(Build.ID, fingerprintSegs[4]);
+
+        String[] devicePlatform = fingerprintSegs[2].split(":");
+        assertEquals(2, devicePlatform.length);
+        assertEquals(Build.DEVICE, devicePlatform[0]);
+        assertEquals(Build.VERSION.RELEASE, devicePlatform[1]);
+
+        assertEquals(Build.ID, fingerprintSegs[3]);
         // no requirements for BUILD_NUMBER and BUILD_VARIANT
-        assertTrue(fingerprintSegs[5].contains(":"));
+        assertTrue(fingerprintSegs[4].contains(":"));
         // no strict requirement for TAGS
-        //assertEquals(Build.TAGS, fingerprintSegs[6]);
+        //assertEquals(Build.TAGS, fingerprintSegs[5]);
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java b/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
index cb1f0ab..ffed104 100644
--- a/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
+++ b/tests/tests/os/src/android/os/cts/FileAccessPermissionTest.java
@@ -40,6 +40,8 @@
  *
  * mksdcard <size> <file>
  * emulator -sdcard <filepath>
+ *
+ * TODO: Combine this file with {@link android.permission.cts.FileSystemPermissionTest}
  */
 public class FileAccessPermissionTest extends AndroidTestCase {
 
diff --git a/tests/tests/permission/src/android/permission/cts/DebuggableTest.java b/tests/tests/permission/src/android/permission/cts/DebuggableTest.java
index 58751a5..fe4ed57 100644
--- a/tests/tests/permission/src/android/permission/cts/DebuggableTest.java
+++ b/tests/tests/permission/src/android/permission/cts/DebuggableTest.java
@@ -20,10 +20,7 @@
 import android.content.pm.PackageManager;
 import android.test.AndroidTestCase;
 
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Verify that pre-installed packages don't have the debuggable
@@ -32,21 +29,14 @@
  */
 public class DebuggableTest extends AndroidTestCase {
 
-    // Remove whitelist in future release.
-    private static final Set<String> WHITELISTED_APPS = new HashSet<String>(Arrays.asList(
-            "com.google.android.apps.uploader"
-            ));
-
     public void testNoDebuggable() {
         List<ApplicationInfo> apps = getContext()
                 .getPackageManager()
                 .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
         for (ApplicationInfo app : apps) {
             String appName = app.packageName;
-            if (!WHITELISTED_APPS.contains(appName)) {
-                assertTrue("Package " + appName + " is marked as debuggable.",
-                        (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
-            }
+            assertTrue("Package " + appName + " is marked as debuggable.",
+                    (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0);
         }
     }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
index 3fccfaf..c5f8ea5 100644
--- a/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/DevicePowerPermissionTest.java
@@ -48,21 +48,4 @@
             // expected
         }
     }
-
-    /**
-     * Verify that generating user activity requires Permission.
-     * <p>Requires Permission:
-     *   {@link android.Manifest.permission#DEVICE_POWER}.
-     *
-     * TODO: add back SmallTest annotation when test has been fixed
-     */
-    @KnownFailure("will be fixed in future release")
-    public void testUserActivity() {
-        try {
-            mPowerManager.userActivity(0, false);
-            fail("Was able to call PowerManager.userActivity without DEVICE_POWER Permission.");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
new file mode 100644
index 0000000..7b3a78e
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.permission.cts;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verify certain permissions on the filesystem
+ *
+ * TODO: Combine this file with {@link android.os.cts.FileAccessPermissionTest}
+ */
+@MediumTest
+public class FileSystemPermissionTest extends AndroidTestCase {
+
+    public void testCreateFileHasSanePermissions() throws Exception {
+        File myFile = new File(getContext().getFilesDir(), "hello");
+        FileOutputStream stream = new FileOutputStream(myFile);
+        stream.write("hello world".getBytes());
+        stream.close();
+        try {
+            FileUtils.FileStatus status = new FileUtils.FileStatus();
+            FileUtils.getFileStatus(myFile.getAbsolutePath(), status, false);
+            int expectedPerms = FileUtils.S_IFREG
+                    | FileUtils.S_IWUSR
+                    | FileUtils.S_IRUSR;
+            assertEquals(
+                    "Newly created files should have 0600 permissions",
+                    Integer.toOctalString(expectedPerms),
+                    Integer.toOctalString(status.mode));
+        } finally {
+            assertTrue(myFile.delete());
+        }
+    }
+
+    public void testCreateDirectoryHasSanePermissions() throws Exception {
+        File myDir = new File(getContext().getFilesDir(), "helloDirectory");
+        assertTrue(myDir.mkdir());
+        try {
+            FileUtils.FileStatus status = new FileUtils.FileStatus();
+            FileUtils.getFileStatus(myDir.getAbsolutePath(), status, false);
+            int expectedPerms = FileUtils.S_IFDIR
+                    | FileUtils.S_IWUSR
+                    | FileUtils.S_IRUSR
+                    | FileUtils.S_IXUSR;
+            assertEquals(
+                    "Newly created directories should have 0700 permissions",
+                    Integer.toOctalString(expectedPerms),
+                    Integer.toOctalString(status.mode));
+
+        } finally {
+            assertTrue(myDir.delete());
+        }
+    }
+
+    public void testOtherApplicationDirectoriesAreNotWritable() throws Exception {
+        List<ApplicationInfo> apps = getContext()
+                .getPackageManager()
+                .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
+        String myAppDirectory = getContext().getApplicationInfo().dataDir;
+        for (ApplicationInfo app : apps) {
+            if (!myAppDirectory.equals(app.dataDir)) {
+                assertDirectoryAndSubdirectoriesNotWritable(new File(app.dataDir));
+            }
+        }
+    }
+
+    public void testApplicationParentDirectoryNotWritable() throws Exception {
+        String myDataDir = getContext().getApplicationInfo().dataDir;
+        File parentDir = new File(myDataDir).getParentFile();
+        assertDirectoryNotWritable(parentDir);
+    }
+
+    public void testDataDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(Environment.getDataDirectory());
+    }
+
+    public void testAndroidRootDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(Environment.getRootDirectory());
+    }
+
+    public void testDownloadCacheDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(Environment.getDownloadCacheDirectory());
+    }
+
+    public void testRootDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(new File("/"));
+    }
+
+    public void testDevDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(new File("/dev"));
+    }
+
+    public void testProcDirectoryNotWritable() throws Exception {
+        assertDirectoryNotWritable(new File("/proc"));
+    }
+
+    public void testDevMemSane() throws Exception {
+        File f = new File("/dev/mem");
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    public void testDevkmemSane() throws Exception {
+        File f = new File("/dev/kmem");
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    public void testDevPortSane() throws Exception {
+        File f = new File("/dev/port");
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    private static void assertDirectoryNotWritable(File directory) throws Exception {
+        File toCreate = new File(directory, "hello");
+        try {
+            toCreate.createNewFile();
+            fail("Expected \"java.io.IOException: Permission denied\""
+                 + " when trying to create " + toCreate.getAbsolutePath());
+        } catch (IOException e) {
+            // It's expected we'll get a "Permission denied" exception.
+        } finally {
+            toCreate.delete();
+        }
+    }
+
+    /**
+     * Verify that any publicly readable directories reachable from
+     * the root directory are not writable.
+     *
+     * Note: Because not all directories are readable, this is a best-effort
+     * test only.  Writable directories within unreadable subdirectories
+     * will NOT be detected by this code.
+     */
+    public void testAllOtherDirectoriesNotWritable() throws Exception {
+        File start = new File("/");
+        assertDirectoryAndSubdirectoriesNotWritable(start);
+    }
+
+    private static final Set<String> OTHER_RANDOM_DIRECTORIES = new HashSet<String>(
+            Arrays.asList(
+                    "/data/backup",
+                    "/data/secure",
+                    "/data/system",
+                    "/data/dalvik-cache",
+                    "/data/property",
+                    "/data/app",
+                    "/data/app-private",
+                    "/data/local",
+                    "/data/misc",
+                    "/data/dontpanic",
+                    "/data/lost+found",
+                    "/data/drm",
+                    "/data/drm/rights",
+                    "/data/data/.drm",
+                    "/data/data/.drm/.wmdrm"
+            )
+    );
+
+    /**
+     * Because /data and /data/data are not readable, we blindly try to
+     * poke around in there looking for bad directories.  There has to be
+     * a better way...
+     */
+    public void testOtherRandomDirectoriesNotWritable() throws Exception {
+        for (String dir : OTHER_RANDOM_DIRECTORIES) {
+            File start = new File(dir);
+            assertDirectoryAndSubdirectoriesNotWritable(start);
+        }
+    }
+
+    public void testAllBlockDevicesAreNotReadableWritable() throws Exception {
+        assertBlockDevicesInDirAndSubDirAreNotWritable(new File("/dev"));
+    }
+
+    private static void
+    assertBlockDevicesInDirAndSubDirAreNotWritable(File dir) throws Exception {
+        assertTrue(dir.isDirectory());
+        File[] subDirectories = dir.listFiles(new FileFilter() {
+            @Override public boolean accept(File pathname) {
+                return pathname.isDirectory();
+            }
+        });
+
+
+        /* recurse into subdirectories */
+        if (subDirectories != null) {
+            for (File f : subDirectories) {
+                assertBlockDevicesInDirAndSubDirAreNotWritable(f);
+            }
+        }
+
+        File[] filesInThisDirectory = dir.listFiles();
+        if (filesInThisDirectory == null) {
+            return;
+        }
+
+        for (File f: filesInThisDirectory) {
+            FileUtils.FileStatus status = new FileUtils.FileStatus();
+            FileUtils.getFileStatus(f.getAbsolutePath(), status, false);
+            if (status.hasModeFlag(FileUtils.S_IFBLK)) {
+                assertFalse(f.getCanonicalPath(), f.canRead());
+                assertFalse(f.getCanonicalPath(), f.canWrite());
+                assertFalse(f.getCanonicalPath(), f.canExecute());
+            }
+        }
+    }
+
+    private void assertDirectoryAndSubdirectoriesNotWritable(File dir) throws Exception {
+        if (!dir.isDirectory()) {
+            return;
+        }
+
+        if (!dir.getAbsolutePath().equals(dir.getCanonicalPath())) {
+            // don't follow symbolic links.
+            return;
+        }
+
+        String myHome = getContext().getApplicationInfo().dataDir;
+        String thisDir = dir.getCanonicalPath();
+        if (thisDir.startsWith(myHome)) {
+            // Don't examine directories within our home directory.
+            // We expect these directories to be writable.
+            return;
+        }
+
+        assertDirectoryNotWritable(dir);
+
+        File[] subFiles = dir.listFiles();
+        if (subFiles == null) {
+            return;
+        }
+
+        for (File f : subFiles) {
+            assertDirectoryAndSubdirectoriesNotWritable(f);
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
index 39da4b6..c73d31a 100644
--- a/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
@@ -91,22 +91,6 @@
     }
 
     /**
-     * Verify that setting Activity's persistent attribute requires permissions.
-     * <p>Requires Permission:
-     *   {@link android.Manifest.permission#PERSISTENT_ACTIVITY}.
-     */
-    @UiThreadTest
-    @MediumTest
-    public void testSetPersistent() {
-        try {
-            mActivity.setPersistent(true);
-            fail("Activity.setPersistent() did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // Expected
-        }
-    }
-
-    /**
      * Verify that get task requires permissions.
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#GET_TASKS}
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
index 3a789d1..06fb4b4 100644
--- a/tests/tests/provider/src/android/provider/cts/SettingsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
@@ -27,10 +27,10 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 @TestTargetClass(android.provider.Settings.class)
 public class SettingsTest extends AndroidTestCase {
-
     public void testSystemTable() throws RemoteException {
         final String[] SYSTEM_PROJECTION = new String[] {
                 Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
index 3f75f94..a97aa96 100644
--- a/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsManagerTest.java
@@ -17,12 +17,15 @@
 package android.telephony.cts;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.SystemClock;
 import android.telephony.TelephonyManager;
 import android.telephony.SmsManager;
 import android.test.AndroidTestCase;
@@ -50,6 +53,12 @@
     private static final String SMS_SEND_ACTION = "CTS_SMS_SEND_ACTION";
     private static final String SMS_DELIVERY_ACTION = "CTS_SMS_DELIVERY_ACTION";
 
+    // List of network operators that don't support SMS delivery report
+    private static final List<String> NO_DELIVERY_REPORTS =
+            Arrays.asList(
+                    "310410"    // AT&T Mobility
+            );
+
     private TelephonyManager mTelephonyManager;
     private String mDestAddr;
     private String mText;
@@ -59,6 +68,7 @@
     private PendingIntent mDeliveredIntent;
     private Intent mSendIntent;
     private Intent mDeliveryIntent;
+    private boolean mDeliveryReportSupported;
 
     private static final int TIME_OUT = 1000 * 60 * 4;
 
@@ -69,6 +79,18 @@
             (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
         mDestAddr = mTelephonyManager.getLine1Number();
         mText = "This is a test message";
+
+        if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+            // CDMA supports SMS delivery report
+            mDeliveryReportSupported = true;
+        } else if (mTelephonyManager.getDeviceId().equals("000000000000000")) {
+            // emulator doesn't support SMS delivery report
+            mDeliveryReportSupported = false;
+        } else {
+            // is this a GSM network that doesn't support SMS delivery report?
+            String mccmnc = mTelephonyManager.getSimOperator();
+            mDeliveryReportSupported = !(NO_DELIVERY_REPORTS.contains(mccmnc));
+        }
     }
 
     @TestTargetNew(
@@ -120,8 +142,10 @@
         // send single text sms
         init();
         sendTextMessage(mDestAddr, mDestAddr, mSentIntent, mDeliveredIntent);
-        mSendReceiver.waitForCalls(1, TIME_OUT);
-        mDeliveryReceiver.waitForCalls(1, TIME_OUT);
+        assertTrue(mSendReceiver.waitForCalls(1, TIME_OUT));
+        if (mDeliveryReportSupported) {
+            assertTrue(mDeliveryReceiver.waitForCalls(1, TIME_OUT));
+        }
 
         if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
             // TODO: temp workaround, OCTET encoding for EMS not properly supported
@@ -134,8 +158,10 @@
 
         init();
         sendDataMessage(mDestAddr, port, data, mSentIntent, mDeliveredIntent);
-        mSendReceiver.waitForCalls(1, TIME_OUT);
-        mDeliveryReceiver.waitForCalls(1, TIME_OUT);
+        assertTrue(mSendReceiver.waitForCalls(1, TIME_OUT));
+        if (mDeliveryReportSupported) {
+            assertTrue(mDeliveryReceiver.waitForCalls(1, TIME_OUT));
+        }
 
         // send multi parts text sms
         init();
@@ -148,8 +174,10 @@
             deliveryIntents.add(PendingIntent.getBroadcast(getContext(), 0, mDeliveryIntent, 0));
         }
         sendMultiPartTextMessage(mDestAddr, parts, sentIntents, deliveryIntents);
-        mSendReceiver.waitForCalls(numParts, TIME_OUT);
-        mDeliveryReceiver.waitForCalls(numParts, TIME_OUT);
+        assertTrue(mSendReceiver.waitForCalls(numParts, TIME_OUT));
+        if (mDeliveryReportSupported) {
+            assertTrue(mDeliveryReceiver.waitForCalls(numParts, TIME_OUT));
+        }
     }
 
     private void init() {
@@ -220,12 +248,20 @@
             }
         }
 
-        public void waitForCalls(int expectedCalls, long timeout) throws InterruptedException {
+        public boolean waitForCalls(int expectedCalls, long timeout) throws InterruptedException {
             synchronized(mLock) {
                 mExpectedCalls = expectedCalls;
-                if (mCalls < mExpectedCalls) {
-                    mLock.wait(timeout);
+                long startTime = SystemClock.elapsedRealtime();
+
+                while (mCalls < mExpectedCalls) {
+                    long waitTime = timeout - (SystemClock.elapsedRealtime() - startTime);
+                    if (waitTime > 0) {
+                        mLock.wait(waitTime);
+                    } else {
+                        return false;  // timed out
+                    }
                 }
+                return true;  // success
             }
         }
     }
diff --git a/tests/tests/telephony/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/src/android/telephony/cts/SmsMessageTest.java
index 44b0871..515f8b5 100644
--- a/tests/tests/telephony/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/SmsMessageTest.java
@@ -212,7 +212,7 @@
         assertEquals(SCA2, sms.getServiceCenterAddress());
         assertEquals(OA2, sms.getOriginatingAddress());
         assertEquals(MESSAGE_BODY2, sms.getMessageBody());
-        CharSequence msgBody = (CharSequence) sms.getMessageBody();
+        CharSequence msgBody = sms.getMessageBody();
         result = SmsMessage.calculateLength(msgBody, false);
         assertEquals(SMS_NUMBER2, result[0]);
         assertEquals(sms.getMessageBody().length(), result[1]);
diff --git a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
index 6c13007..ae3d940 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -21,13 +21,12 @@
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 import android.os.Looper;
 import android.os.cts.TestThread;
-import android.provider.Settings;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
@@ -297,7 +296,7 @@
 
             case TelephonyManager.PHONE_TYPE_NONE:
                 assertNull(deviceId);
-                assertHardwareId();
+                assertSerialNumber();
                 assertMacAddressReported();
                 break;
 
@@ -401,15 +400,13 @@
         }
     }
 
-    private void assertHardwareId() {
-        ContentResolver resolver = mContext.getContentResolver();
-        String hardwareId = Settings.Secure.getString(resolver, "hardware_id");
-        assertNotNull("Non-telephony devices must define a Settings.Secure 'hardware_id' property.",
-                hardwareId);
+    private void assertSerialNumber() {
+        assertNotNull("Non-telephony devices must have a Build.SERIAL number.",
+                Build.SERIAL);
         assertTrue("Hardware id must be no longer than 20 characters.",
-                hardwareId.length() <= 20);
+                Build.SERIAL.length() <= 20);
         assertTrue("Hardware id must be alphanumeric.",
-                Pattern.matches("[0-9A-Za-z]+", hardwareId));
+                Pattern.matches("[0-9A-Za-z]+", Build.SERIAL));
     }
 
     private void assertMacAddressReported() {
diff --git a/tests/tests/telephony/src/android/telephony/gsm/cts/SmsManagerTest.java b/tests/tests/telephony/src/android/telephony/gsm/cts/SmsManagerTest.java
index 8dc6936..512e89d 100644
--- a/tests/tests/telephony/src/android/telephony/gsm/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/gsm/cts/SmsManagerTest.java
@@ -28,6 +28,7 @@
 @TestTargetClass(SmsManager.class)
 public class SmsManagerTest extends android.telephony.cts.SmsManagerTest {
 
+    @Override
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "getDefault",
diff --git a/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java b/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
index a7c55a1..1d6c109 100644
--- a/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
+++ b/tests/tests/text/src/android/text/method/cts/ArrowKeyMovementMethodTest.java
@@ -143,23 +143,6 @@
         assertSelection(-1);
         runTestOnUiThread(new Runnable() {
             public void run() {
-                mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_DOWN);
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        assertSelection(END_OF_1ST_LINE);
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                Selection.removeSelection(mEditable);
-                mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_RIGHT);
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        assertSelection(END_OF_1ST_LINE);
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
                 Selection.removeSelection(mEditable);
                 mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_UP);
             }
@@ -189,22 +172,6 @@
         runTestOnUiThread(new Runnable() {
             public void run() {
                 Selection.removeSelection(mEditable);
-                mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_DOWN);
-            }
-        });
-        assertSelection(END_OF_ALL_TEXT);
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                Selection.removeSelection(mEditable);
-                mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_RIGHT);
-            }
-        });
-        assertSelection(END_OF_ALL_TEXT);
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                Selection.removeSelection(mEditable);
                 mArrowKeyMovementMethod.onTakeFocus(mTextView, mEditable, View.FOCUS_UP);
             }
         });
diff --git a/tests/tests/util/src/android/util/cts/TimeUtilsTest.java b/tests/tests/util/src/android/util/cts/TimeUtilsTest.java
index 8a720ef..47e37a0 100644
--- a/tests/tests/util/src/android/util/cts/TimeUtilsTest.java
+++ b/tests/tests/util/src/android/util/cts/TimeUtilsTest.java
@@ -21,8 +21,6 @@
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
 
-import org.apache.harmony.luni.internal.util.ZoneInfoDB;
-
 import android.util.TimeUtils;
 
 import java.util.Calendar;
@@ -473,20 +471,6 @@
         }
     }
 
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            notes = "Test method: getTimeZoneDatabaseVersion",
-            method = "getTimeZoneDatabaseVersion",
-            args = {}
-        )
-    })
-    public void testGetTimeZoneDatabaseVersion(){
-        String version = TimeUtils.getTimeZoneDatabaseVersion();
-        assertTrue(5 == version.length());
-        assertEquals(ZoneInfoDB.getVersion(), version);
-    }
-
     private static TimeZone guessTimeZone(Calendar c, String country) {
         return TimeUtils.getTimeZone(c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET),
                                      c.get(Calendar.DST_OFFSET) != 0,
diff --git a/tests/tests/view/src/android/view/cts/MotionEventTest.java b/tests/tests/view/src/android/view/cts/MotionEventTest.java
index beece1e..e4df594 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventTest.java
@@ -488,5 +488,13 @@
         assertEquals(1, mMotionEvent2.getHistorySize());
 
         mMotionEvent2.recycle();
+        
+        try {
+            mMotionEvent2.recycle();
+            fail("recycle() should throw an exception when the event has already been recycled.");
+        } catch (RuntimeException ex) {
+        }
+        
+        mMotionEvent2 = null; // since it was recycled, don't try to recycle again in tear down
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 77624e8..e07e105 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -558,25 +558,6 @@
         assertEquals(-1, view.getBaseline());
     }
 
-    @TestTargetNew(
-        level = TestLevel.COMPLETE,
-        method = "computeScroll",
-        args = {}
-    )
-    public void testComputeScroll() throws Throwable {
-        final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
-        assertTrue(view.hasCalledComputeScroll());
-
-        view.reset();
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                view.requestLayout();
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        assertTrue(view.hasCalledComputeScroll());
-    }
-
     @TestTargets({
         @TestTargetNew(
             level = TestLevel.COMPLETE,
@@ -2723,8 +2704,7 @@
         });
 
         assertFalse(view.hasCalledOnKeyMultiple());
-        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_ENTER);
-        getInstrumentation().sendKeySync(keyEvent);
+        view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_ENTER));
         assertTrue(view.hasCalledOnKeyMultiple());
     }
 
@@ -4655,6 +4635,10 @@
             mHasChildDrawableStateChanged = false;
             mHasBroughtChildToFront = false;
         }
+
+        public void childOverlayStateChanged(View child) {
+
+        }
     }
 
     private final class OnCreateContextMenuListenerImpl implements OnCreateContextMenuListener {
diff --git a/tests/tests/view/src/android/view/cts/WindowTest.java b/tests/tests/view/src/android/view/cts/WindowTest.java
index 527f444..0e31888 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -39,11 +39,13 @@
 import android.util.DisplayMetrics;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.Gravity;
+import android.view.InputQueue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.SurfaceHolder;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -1243,6 +1245,14 @@
         public void setDefaultWindowFormat(int format) {
             super.setDefaultWindowFormat(format);
         }
+        
+        @Override
+        public void takeSurface(SurfaceHolder.Callback2 callback) {
+        }
+        
+        @Override
+        public void takeInputQueue(InputQueue.Callback callback) {
+        }
     }
 
     private class MockWindowCallback implements Window.Callback {
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
index 896e99f..bdee05a 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -97,6 +97,11 @@
             args = {int.class, int.class}
         ),
         @TestTargetNew(
+                level = TestLevel.COMPLETE,
+                method = "getSelectedText",
+                args = {int.class}
+            ),
+        @TestTargetNew(
             level = TestLevel.COMPLETE,
             method = "getCursorCapsMode",
             args = {int.class}
@@ -127,6 +132,11 @@
             args = {CharSequence.class, int.class}
         ),
         @TestTargetNew(
+                level = TestLevel.COMPLETE,
+                method = "setComposingRegion",
+                args = {int.class, int.class}
+            ),
+        @TestTargetNew(
             level = TestLevel.COMPLETE,
             method = "sendKeyEvent",
             args = {KeyEvent.class}
@@ -184,6 +194,10 @@
         assertTrue(inputConnection.isSetComposingTextCalled);
         wrapper.setSelection(0, 10);
         assertTrue(inputConnection.isSetSelectionCalled);
+        wrapper.getSelectedText(0);
+        assertTrue(inputConnection.isGetSelectedTextCalled);
+        wrapper.setComposingRegion(0, 3);
+        assertTrue(inputConnection.isSetComposingRegionCalled);
     }
 
     private class MockInputConnection implements InputConnection {
@@ -198,12 +212,14 @@
         public boolean isGetExtractedTextCalled;
         public boolean isGetTextAfterCursorCalled;
         public boolean isGetTextBeforeCursorCalled;
+        public boolean isGetSelectedTextCalled;
         public boolean isPerformContextMenuActionCalled;
         public boolean isPerformEditorActionCalled;
         public boolean isPerformPrivateCommandCalled;
         public boolean isReportFullscreenModeCalled;
         public boolean isSendKeyEventCalled;
         public boolean isSetComposingTextCalled;
+        public boolean isSetComposingRegionCalled;
         public boolean isSetSelectionCalled;
 
         public boolean beginBatchEdit() {
@@ -261,6 +277,11 @@
             return null;
         }
 
+        public CharSequence getSelectedText(int flags) {
+            isGetSelectedTextCalled = true;
+            return null;
+        }
+
         public boolean performContextMenuAction(int id) {
             isPerformContextMenuActionCalled = true;
             return false;
@@ -291,6 +312,11 @@
             return false;
         }
 
+        public boolean setComposingRegion(int start, int end) {
+            isSetComposingRegionCalled = true;
+            return false;
+        }
+
         public boolean setSelection(int start, int end) {
             isSetSelectionCalled = true;
             return false;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 554acd8..b9d054f 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -1732,22 +1732,6 @@
         });
     }
 
-    @TestTargetNew(
-        level = TestLevel.SUFFICIENT,
-        method = "performLongClick",
-        args = {}
-    )
-    public void testPerformLongClick() throws InterruptedException {
-        String form = "<p><form><input type=\"text\" name=\"Test\"><br>"
-                + "<input type=\"submit\" value=\"Submit\"></form></p>";
-        mWebView.loadDataWithBaseURL("fake://home", "<html><body>" + form
-                + "</body></html>", "text/html", "UTF-8", null);
-        waitForLoadComplete(mWebView, TEST_TIMEOUT);
-
-        mWebView.performLongClick();
-        // no simple way to check the effect of a long click
-    }
-
     @TestTargets({
         @TestTargetNew(
             level = TestLevel.COMPLETE,
diff --git a/tests/tests/widget/src/android/widget/cts/CursorTreeAdapterTest.java b/tests/tests/widget/src/android/widget/cts/CursorTreeAdapterTest.java
index 0927b61..e448a97 100644
--- a/tests/tests/widget/src/android/widget/cts/CursorTreeAdapterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CursorTreeAdapterTest.java
@@ -79,12 +79,20 @@
         mDatabase.execSQL("CREATE TABLE child1 (_id INTEGER PRIMARY KEY, value TEXT);");
         mDatabase.execSQL("INSERT INTO child1 (value) VALUES ('" + CHILD_VALUE_ONE + "');");
         mDatabase.execSQL("INSERT INTO child1 (value) VALUES ('" + CHILD_VALUE_TWO + "');");
+        return getChild1Cursor();
+    }
+
+    private Cursor getChild1Cursor() {
         return mDatabase.query("child1", VALUE_PROJECTION, null, null, null, null, null);
     }
 
     private Cursor createChild2Cursor() {
         mDatabase.execSQL("CREATE TABLE child2 (_id INTEGER PRIMARY KEY, value TEXT);");
         mDatabase.execSQL("INSERT INTO child2 (value) VALUES ('" + CHILD_VALUE_THREE + "');");
+        return getChild2Cursor();
+    }
+
+    private Cursor getChild2Cursor() {
         return mDatabase.query("child2", VALUE_PROJECTION, null, null, null, null, null);
     }
 
@@ -679,7 +687,7 @@
         level = TestLevel.COMPLETE,
         notes = "Test {@link CursorAdapter#getChildView(int, int, boolean, View, ViewGroup)}",
         method = "getChildView",
-        args = {int.class, int.class, boolean.class, android.view.View.class, 
+        args = {int.class, int.class, boolean.class, android.view.View.class,
                 android.view.ViewGroup.class}
     )
     public void testGetChildView() {
@@ -792,9 +800,15 @@
 
             if (0 == groupCursor.getPosition()) {
                 mHasAddedChild1IntoCache = true;
+                if (mChildCursor1.isClosed()) {
+                    mChildCursor1 = getChild1Cursor();
+                }
                 return mChildCursor1;
             } else if (1 == groupCursor.getPosition()) {
                 mHasAddedChild2IntoCache = true;
+                if (mChildCursor2.isClosed()) {
+                    mChildCursor2 = getChild2Cursor();
+                }
                 return mChildCursor2;
             }
             return null;
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 65045d8..40684c9 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -215,11 +215,11 @@
         mInstrumentation.waitForIdleSync();
 
         assertEquals(0, mGridView.getSelectedItemPosition());
-        KeyEvent event = new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_DPAD_RIGHT);
+        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
         mInstrumentation.sendKeySync(event);
         assertEquals(1, mGridView.getSelectedItemPosition());
 
-        event = new KeyEvent(KeyEvent.ACTION_MULTIPLE, KeyEvent.KEYCODE_DPAD_LEFT);
+        event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
         mInstrumentation.sendKeySync(event);
         assertEquals(0, mGridView.getSelectedItemPosition());
 
@@ -535,7 +535,7 @@
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "attachLayoutAnimationParameters",
-        args = {android.view.View.class, android.view.ViewGroup.LayoutParams.class, int.class, 
+        args = {android.view.View.class, android.view.ViewGroup.LayoutParams.class, int.class,
                 int.class}
     )
     public void testAttachLayoutAnimationParameters() {
diff --git a/tests/tests/widget/src/android/widget/cts/ImageSwitcherTest.java b/tests/tests/widget/src/android/widget/cts/ImageSwitcherTest.java
index 9e32748..a7667ba 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageSwitcherTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageSwitcherTest.java
@@ -136,18 +136,20 @@
         imageSwitcher.setImageURI(uri);
         assertSame(iv1, imageSwitcher.getCurrentView());
 
-        Bitmap testImageBitmap = WidgetTestUtils.getUnscaledBitmap(getContext().getResources(),
-                R.raw.testimage);
         BitmapDrawable currViewBitmap =
             (BitmapDrawable) ((ImageView) imageSwitcher.getCurrentView()).getDrawable();
+        Bitmap testImageBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(
+                getContext().getResources(), R.raw.testimage,
+                currViewBitmap.getBitmap().getConfig());
         WidgetTestUtils.assertEquals(testImageBitmap, currViewBitmap.getBitmap());
 
         createSampleImage(imagefile, R.raw.scenery);
         uri = Uri.parse(imagefile.getPath());
         imageSwitcher.setImageURI(uri);
         assertSame(iv, imageSwitcher.getCurrentView());
-        Bitmap sceneryImageBitmap = WidgetTestUtils.getUnscaledBitmap(getContext().getResources(),
-                R.raw.scenery);
+        Bitmap sceneryImageBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(
+                getContext().getResources(), R.raw.scenery,
+                currViewBitmap.getBitmap().getConfig());
         currViewBitmap =
             (BitmapDrawable) ((ImageView) imageSwitcher.getCurrentView()).getDrawable();
         WidgetTestUtils.assertEquals(sceneryImageBitmap, currViewBitmap.getBitmap());
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index 5214b50..0777259 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -276,10 +276,12 @@
         assertTrue(mImageView.isLayoutRequested());
         assertNotNull(mImageView.getDrawable());
 
-        Bitmap testimageBitmap = WidgetTestUtils.getUnscaledBitmap(mActivity.getResources(),
-                R.raw.testimage);
         Drawable imageViewDrawable = mImageView.getDrawable();
         BitmapDrawable imageViewBitmap = (BitmapDrawable) imageViewDrawable;
+        Bitmap.Config viewConfig = imageViewBitmap.getBitmap().getConfig();
+        Bitmap testimageBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(
+                mActivity.getResources(), R.raw.testimage, viewConfig);
+
         WidgetTestUtils.assertEquals(testimageBitmap, imageViewBitmap.getBitmap());
     }
 
diff --git a/tests/tests/widget/src/android/widget/cts/SimpleAdapterTest.java b/tests/tests/widget/src/android/widget/cts/SimpleAdapterTest.java
index 6fb7063..d20582b 100644
--- a/tests/tests/widget/src/android/widget/cts/SimpleAdapterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SimpleAdapterTest.java
@@ -410,10 +410,10 @@
             mSimpleAdapter.setViewImage(view, SimpleCursorAdapterTest.createTestImage(mContext,
                     "testimage", com.android.cts.stub.R.raw.testimage));
             assertNotNull(view.getDrawable());
-            Bitmap testBitmap = WidgetTestUtils.getUnscaledBitmap(mContext.getResources(),
-                    com.android.cts.stub.R.raw.testimage);
-            WidgetTestUtils.assertEquals(testBitmap,
-                    ((BitmapDrawable) view.getDrawable()).getBitmap());
+            Bitmap actualBitmap = ((BitmapDrawable) view.getDrawable()).getBitmap();
+            Bitmap testBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(mContext.getResources(),
+                    com.android.cts.stub.R.raw.testimage, actualBitmap.getConfig());
+            WidgetTestUtils.assertEquals(testBitmap, actualBitmap);
         } finally {
             SimpleCursorAdapterTest.destroyTestImage(mContext,"testimage");
         }
diff --git a/tests/tests/widget/src/android/widget/cts/SimpleCursorAdapterTest.java b/tests/tests/widget/src/android/widget/cts/SimpleCursorAdapterTest.java
index 8f32228..08e425a 100644
--- a/tests/tests/widget/src/android/widget/cts/SimpleCursorAdapterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SimpleCursorAdapterTest.java
@@ -256,10 +256,10 @@
             mSimpleCursorAdapter.setViewImage(view,
                     createTestImage(mContext, SAMPLE_IMAGE_NAME, testimgRawId));
             assertNotNull(view.getDrawable());
-            Bitmap testBitmap = WidgetTestUtils.getUnscaledBitmap(mContext.getResources(),
-                    testimgRawId);
-            WidgetTestUtils.assertEquals(testBitmap,
-                    ((BitmapDrawable) view.getDrawable()).getBitmap());
+            Bitmap actualBitmap = ((BitmapDrawable) view.getDrawable()).getBitmap();
+            Bitmap testBitmap = WidgetTestUtils.getUnscaledAndDitheredBitmap(mContext.getResources(),
+                    testimgRawId, actualBitmap.getConfig());
+            WidgetTestUtils.assertEquals(testBitmap, actualBitmap);
         } finally {
             destroyTestImage(mContext, SAMPLE_IMAGE_NAME);
         }
diff --git a/tests/tests/widget/src/android/widget/cts/SimpleCursorTreeAdapterTest.java b/tests/tests/widget/src/android/widget/cts/SimpleCursorTreeAdapterTest.java
index 7295e75..8e6f0f3 100644
--- a/tests/tests/widget/src/android/widget/cts/SimpleCursorTreeAdapterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SimpleCursorTreeAdapterTest.java
@@ -83,24 +83,24 @@
             level = TestLevel.COMPLETE,
             notes = "Test constructors",
             method = "SimpleCursorTreeAdapter",
-            args = {android.content.Context.class, android.database.Cursor.class, int.class, 
-                    int.class, java.lang.String[].class, int[].class, int.class, int.class, 
+            args = {android.content.Context.class, android.database.Cursor.class, int.class,
+                    int.class, java.lang.String[].class, int[].class, int.class, int.class,
                     java.lang.String[].class, int[].class}
         ),
         @TestTargetNew(
             level = TestLevel.COMPLETE,
             notes = "Test constructors",
             method = "SimpleCursorTreeAdapter",
-            args = {android.content.Context.class, android.database.Cursor.class, int.class, 
-                    int.class, java.lang.String[].class, int[].class, int.class, 
+            args = {android.content.Context.class, android.database.Cursor.class, int.class,
+                    int.class, java.lang.String[].class, int[].class, int.class,
                     java.lang.String[].class, int[].class}
         ),
         @TestTargetNew(
             level = TestLevel.COMPLETE,
             notes = "Test constructors",
             method = "SimpleCursorTreeAdapter",
-            args = {android.content.Context.class, android.database.Cursor.class, int.class, 
-                    java.lang.String[].class, int[].class, int.class, java.lang.String[].class, 
+            args = {android.content.Context.class, android.database.Cursor.class, int.class,
+                    java.lang.String[].class, int[].class, int.class, java.lang.String[].class,
                     int[].class}
         )
     })
@@ -122,7 +122,7 @@
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "bindChildView",
-        args = {android.view.View.class, android.content.Context.class, 
+        args = {android.view.View.class, android.content.Context.class,
                 android.database.Cursor.class, boolean.class}
     )
     public void testBindChildView() {
@@ -146,7 +146,7 @@
     @TestTargetNew(
         level = TestLevel.COMPLETE,
         method = "bindGroupView",
-        args = {android.view.View.class, android.content.Context.class, 
+        args = {android.view.View.class, android.content.Context.class,
                 android.database.Cursor.class, boolean.class}
     )
     // The param context and isExpanded is never readed.
@@ -183,7 +183,7 @@
         // color drawable
         ImageView view = new ImageView(mContext);
         assertNull(view.getDrawable());
-        mSimpleCursorTreeAdapter.setViewImage(view, 
+        mSimpleCursorTreeAdapter.setViewImage(view,
                 String.valueOf(com.android.cts.stub.R.drawable.scenery));
         BitmapDrawable d = (BitmapDrawable) mContext.getResources().getDrawable(
                 com.android.cts.stub.R.drawable.scenery);
@@ -213,10 +213,10 @@
             mSimpleCursorTreeAdapter.setViewImage(view,
                     SimpleCursorAdapterTest.createTestImage(mContext, SAMPLE_IMAGE_NAME,
                             com.android.cts.stub.R.raw.testimage));
-            Bitmap test = WidgetTestUtils.getUnscaledBitmap(mContext.getResources(),
-                    com.android.cts.stub.R.raw.testimage);
-            WidgetTestUtils.assertEquals(test,
-                    ((BitmapDrawable) view.getDrawable()).getBitmap());
+            Bitmap actualBitmap = ((BitmapDrawable) view.getDrawable()).getBitmap();
+            Bitmap test = WidgetTestUtils.getUnscaledAndDitheredBitmap(mContext.getResources(),
+                    com.android.cts.stub.R.raw.testimage, actualBitmap.getConfig());
+            WidgetTestUtils.assertEquals(test, actualBitmap);
         } finally {
             SimpleCursorAdapterTest.destroyTestImage(mContext, SAMPLE_IMAGE_NAME);
         }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 54d00b2..31168fd 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -41,7 +41,9 @@
 
     private static final String TAG = "DeviceInfoInstrument";
 
-    private static final String OPEN_GL_ES_VERSION = "opengles_version";
+    // constants for device info attributes to be sent as instrumentation keys
+    // these values should correspond to attributes defined in cts_result.xsd
+    private static final String OPEN_GL_ES_VERSION = "openGlEsVersion";
     private static final String PROCESSES = "processes";
     private static final String FEATURES = "features";
     private static final String PHONE_NUMBER = "phoneNumber";
@@ -51,29 +53,27 @@
     private static final String NETWORK = "network";
     public static final String KEYPAD = "keypad";
     public static final String NAVIGATION = "navigation";
-    public static final String TOUCH_SCREEN = "touch_screen";
+    public static final String TOUCH_SCREEN = "touch";
+    private static final String SCREEN_Y_DENSITY = "Ydpi";
+    private static final String SCREEN_X_DENSITY = "Xdpi";
     private static final String SCREEN_SIZE = "screen_size";
     private static final String SCREEN_DENSITY_BUCKET = "screen_density_bucket";
-    private static final String SCREEN_Y_DENSITY = "screen_Y_density";
-    private static final String SCREEN_X_DENSITY = "screen_X_density";
     private static final String SCREEN_DENSITY = "screen_density";
     private static final String SCREEN_HEIGHT = "screen_height";
     private static final String SCREEN_WIDTH = "screen_width";
-    private static final String VERSION_SDK = "version_sdk";
-    private static final String VERSION_RELEASE = "version_release";
-    private static final String VERSION_INCREMENTAL = "version_incremental";
+    private static final String VERSION_SDK = "androidPlatformVersion";
+    private static final String VERSION_RELEASE = "buildVersion";
     private static final String BUILD_ABI = "build_abi";
     private static final String BUILD_ABI2 = "build_abi2";
     private static final String BUILD_FINGERPRINT = "build_fingerprint";
-    private static final String BUILD_TAGS = "build_tags";
     private static final String BUILD_TYPE = "build_type";
     private static final String BUILD_MODEL = "build_model";
     private static final String BUILD_BRAND = "build_brand";
     private static final String BUILD_MANUFACTURER = "build_manufacturer";
     private static final String BUILD_BOARD = "build_board";
     private static final String BUILD_DEVICE = "build_device";
-    private static final String PRODUCT_NAME = "product_name";
-    private static final String BUILD_ID = "build_id";
+    private static final String PRODUCT_NAME = "buildName";
+    private static final String BUILD_ID = "buildID";
     private static Bundle mResults = new Bundle();
 
     public DeviceInfoInstrument() {
@@ -96,12 +96,10 @@
         addResult(BUILD_BRAND, Build.BRAND);
         addResult(BUILD_MODEL, Build.MODEL);
         addResult(BUILD_TYPE, Build.TYPE);
-        addResult(BUILD_TAGS, Build.TAGS);
         addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
         addResult(BUILD_ABI, Build.CPU_ABI);
         addResult(BUILD_ABI2, Build.CPU_ABI2);
 
-        addResult(VERSION_INCREMENTAL, Build.VERSION.INCREMENTAL);
         addResult(VERSION_RELEASE, Build.VERSION.RELEASE);
         addResult(VERSION_SDK, Build.VERSION.SDK);
 
diff --git a/tools/host/etc/cts b/tools/host/etc/cts
index c368db2..bb1fa2a 100755
--- a/tools/host/etc/cts
+++ b/tools/host/etc/cts
@@ -17,7 +17,7 @@
 
 CTS_SH=cts
 CTS_LIB=cts.jar
-DDMS_LIB=ddmlib.jar
+DDMS_LIB=ddmlib-prebuilt.jar
 JUNIT_LIB=junit.jar
 HOSTTEST_LIB=hosttestlib.jar
 CTS_TEST_ANNOTATIONS_HOST_LIB=CtsTestAnnotationsHostLib.jar
@@ -38,15 +38,13 @@
     BINARY_DIR=tools
     JAR_DIR=tools/lib
 else if [ ! -f ${ANDROID_ROOT}/bin/${ADB_TOOLS} ]; then
-    echo "Please set your Android Framework root path."
-    echo "Define \"ANDROID_ROOT\" variable"
+    echo "Missing ${ANDROID_ROOT}/bin/${ADB_TOOLS}"
     exit -1;
 fi;
 fi;
 
 if [ ! -f ${ANDROID_ROOT}/${JAR_DIR}/${DDMS_LIB} ]; then
-    echo "Please set your Android Framework root path."
-    echo "Define \"ANDROID_ROOT\" variable"
+    echo "Missing ${ANDROID_ROOT}/${JAR_DIR}/${DDMS_LIB}"
     exit -1;
 fi;
 
diff --git a/tools/host/src/Android.mk b/tools/host/src/Android.mk
index 1fd8159..5ad3998 100644
--- a/tools/host/src/Android.mk
+++ b/tools/host/src/Android.mk
@@ -21,7 +21,7 @@
 
 LOCAL_JAR_MANIFEST := ../etc/manifest.txt
 LOCAL_JAVA_LIBRARIES := \
-    ddmlib junit hosttestlib CtsTestAnnotationsHostLib
+    ddmlib-prebuilt junit hosttestlib CtsTestAnnotationsHostLib
 
 LOCAL_MODULE := cts
 
diff --git a/tools/host/src/com/android/cts/DeviceManager.java b/tools/host/src/com/android/cts/DeviceManager.java
index c164ac1..c794332 100644
--- a/tools/host/src/com/android/cts/DeviceManager.java
+++ b/tools/host/src/com/android/cts/DeviceManager.java
@@ -16,8 +16,10 @@
 
 package com.android.cts;
 
+import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.AndroidDebugBridge;
 import com.android.ddmlib.IDevice;
+import com.android.ddmlib.TimeoutException;
 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
 
 import java.io.IOException;
@@ -230,6 +232,10 @@
                 mSemaphore.release();
             } catch (IOException e) {
                 // FIXME: handle failed connection to device.
+            } catch (TimeoutException e) {
+                // FIXME: handle failed connection to device.
+            } catch (AdbCommandRejectedException e) {
+                // FIXME: handle failed connection to device.
             }
         }
     }
diff --git a/tools/host/src/com/android/cts/ReferenceAppTestPackage.java b/tools/host/src/com/android/cts/ReferenceAppTestPackage.java
index 1b32576..0b6aa506 100644
--- a/tools/host/src/com/android/cts/ReferenceAppTestPackage.java
+++ b/tools/host/src/com/android/cts/ReferenceAppTestPackage.java
@@ -16,8 +16,10 @@
 
 package com.android.cts;
 
+import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.MultiLineReceiver;
 import com.android.ddmlib.RawImage;
+import com.android.ddmlib.TimeoutException;
 import com.android.ddmlib.log.LogReceiver.ILogListener;
 import com.android.ddmlib.log.LogReceiver.LogEntry;
 
@@ -199,6 +201,10 @@
                     }
                 } catch (IOException e) {
                     Log.e("Error taking snapshot! " + cmdArgs, e);
+                } catch (TimeoutException e) {
+                    Log.e("Error taking snapshot! " + cmdArgs, e);
+                } catch (AdbCommandRejectedException e) {
+                    Log.e("Error taking snapshot! " + cmdArgs, e);
                 }
             }
         });
diff --git a/tools/host/src/com/android/cts/TestDevice.java b/tools/host/src/com/android/cts/TestDevice.java
index 987fd96..88bcd68 100644
--- a/tools/host/src/com/android/cts/TestDevice.java
+++ b/tools/host/src/com/android/cts/TestDevice.java
@@ -16,6 +16,7 @@
 
 package com.android.cts;
 
+import com.android.ddmlib.AdbCommandRejectedException;
 import com.android.ddmlib.Client;
 import com.android.ddmlib.ClientData;
 import com.android.ddmlib.IDevice;
@@ -23,13 +24,16 @@
 import com.android.ddmlib.MultiLineReceiver;
 import com.android.ddmlib.NullOutputReceiver;
 import com.android.ddmlib.RawImage;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.SyncException;
 import com.android.ddmlib.SyncService;
 import com.android.ddmlib.SyncService.ISyncProgressMonitor;
-import com.android.ddmlib.SyncService.SyncResult;
+import com.android.ddmlib.TimeoutException;
 import com.android.ddmlib.log.LogReceiver;
 import com.android.ddmlib.log.LogReceiver.ILogListener;
 
 import java.io.BufferedReader;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -101,6 +105,8 @@
             try {
                 mDevice.runLogService("main", logReceiver);
             } catch (IOException e) {
+            } catch (TimeoutException e) {
+            } catch (AdbCommandRejectedException e) {
             }
         }
 
@@ -136,6 +142,10 @@
             mSyncService = mDevice.getSyncService();
         } catch (IOException e) {
             // FIXME: handle failed connection.
+        } catch (TimeoutException e) {
+            // FIXME: handle failed connection.
+        } catch (AdbCommandRejectedException e) {
+            // FIXME: handle failed connection.
         }
         mBatchModeResultParser = null;
         mUninstallObserver = new PackageActionObserver(ACTION_UNINSTALL);
@@ -391,9 +401,11 @@
      * Store the build information of a device
      */
     public static final class DeviceParameterCollector{
-        public static final String PRODUCT_NAME = "product_name";
-        public static final String BUILD_VERSION = "version_release";
-        public static final String BUILD_ID = "build_id";
+        // the device info keys expected to be sent from device info instrumentation
+        // these constants should match exactly with those defined in DeviceInfoInstrument.jaa
+        public static final String PRODUCT_NAME = "buildName";
+        public static final String BUILD_VERSION = "buildVersion";
+        public static final String BUILD_ID = "buildID";
         public static final String BUILD_FINGERPRINT = "build_fingerprint";
         public static final String BUILD_TAGS = "build_tags";
         public static final String BUILD_TYPE = "build_type";
@@ -410,11 +422,11 @@
         public static final String SCREEN_DENSITY = "screen_density";
         public static final String SCREEN_DENSITY_BUCKET = "screen_density_bucket";
         public static final String SERIAL_NUMBER = "serialNumber";
-        public static final String VERSION_SDK = "version_sdk";
+        public static final String VERSION_SDK = "androidPlatformVersion";
         public static final String LOCALES = "locales";
-        public static final String SCREEN_Y_DENSITY = "screen_Y_density";
-        public static final String SCREEN_X_DENSITY = "screen_X_density";
-        public static final String TOUCH_SCREEN = "touch_screen";
+        public static final String SCREEN_Y_DENSITY = "Ydpi";
+        public static final String SCREEN_X_DENSITY = "Xdpi";
+        public static final String TOUCH_SCREEN = "touch";
         public static final String NAVIGATION = "navigation";
         public static final String KEYPAD = "keypad";
         public static final String NETWORK = "network";
@@ -423,7 +435,7 @@
         public static final String PHONE_NUMBER = "phoneNumber";
         public static final String FEATURES = "features";
         public static final String PROCESSES = "processes";
-        public static final String OPEN_GL_ES_VERSION = "opengles_version";
+        public static final String OPEN_GL_ES_VERSION = "openGlEsVersion";
 
         private HashMap<String, String> mInfoMap;
 
@@ -957,10 +969,16 @@
      * @param remotePath The remote path.
      */
     public void pushFile(String localPath, String remotePath) {
-        SyncResult result = mSyncService.pushFile(localPath, remotePath,
-                new PushMonitor());
-        if (result.getCode() != SyncService.RESULT_OK) {
-            Log.e("Uploading file failed: " + result.getMessage(), null);
+        try {
+            mSyncService.pushFile(localPath, remotePath, new PushMonitor());
+        } catch (TimeoutException e) {
+            Log.e("Uploading file failed: timeout", null);
+        } catch (SyncException e) {
+            Log.e("Uploading file failed: " + e.getMessage(), null);
+        } catch (FileNotFoundException e) {
+            Log.e("Uploading file failed: " + e.getMessage(), null);
+        } catch (IOException e) {
+            Log.e("Uploading file failed: " + e.getMessage(), null);
         }
     }
 
@@ -1262,7 +1280,7 @@
             mResultLines = new ArrayList<String>();
             mStackTrace = null;
             mFailedMsg = null;
-            mResultCode = CtsTestResult.CODE_PASS;
+            mResultCode = CtsTestResult.CODE_FAIL;
         }
 
         /** {@inheritDoc} */
@@ -1715,10 +1733,19 @@
             @Override
             public void run() {
                 try {
-                    mDevice.executeShellCommand(cmd, receiver);
+                    mDevice.executeShellCommand(cmd, receiver, 0);
                 } catch (IOException e) {
                     Log.e(String.format("Failed to execute shell command %s on device %s", cmd,
                             mDevice.getSerialNumber()), e);
+                } catch (TimeoutException e) {
+                    Log.e(String.format("Failed to execute shell command %s on device %s", cmd,
+                            mDevice.getSerialNumber()), e);
+                } catch (AdbCommandRejectedException e) {
+                    Log.e(String.format("Failed to execute shell command %s on device %s", cmd,
+                            mDevice.getSerialNumber()), e);
+                } catch (ShellCommandUnresponsiveException e) {
+                    Log.e(String.format("Failed to execute shell command %s on device %s", cmd,
+                            mDevice.getSerialNumber()), e);
                 }
             }
         }.start();
@@ -1936,8 +1963,11 @@
      *
      * @return the screenshot
      * @throws IOException
+     * @throws AdbCommandRejectedException
+     * @throws TimeoutException
      */
-    public RawImage getScreenshot() throws IOException {
+    public RawImage getScreenshot() throws IOException, TimeoutException,
+            AdbCommandRejectedException {
         return mDevice.getScreenshot();
     }
 }
diff --git a/tools/host/src/com/android/cts/Version.java b/tools/host/src/com/android/cts/Version.java
index c1b5d39..fead909 100644
--- a/tools/host/src/com/android/cts/Version.java
+++ b/tools/host/src/com/android/cts/Version.java
@@ -18,12 +18,12 @@
 
 public class Version {
     // The CTS version string
-    private static final String version = "2.2_r4";
-
+    private static final String version = "2.2_r1";
+    
     private Version() {
         // no instances allowed
     }
-
+    
     public static String asString() {
         return version;
     }
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
new file mode 100644
index 0000000..a61e37c
--- /dev/null
+++ b/tools/tradefed-host/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="res"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/hosttestlib"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/tradefed-host/.project b/tools/tradefed-host/.project
new file mode 100644
index 0000000..990c63e
--- /dev/null
+++ b/tools/tradefed-host/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>cts-tradefed-host</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
new file mode 100644
index 0000000..c1e5c54
--- /dev/null
+++ b/tools/tradefed-host/Android.mk
@@ -0,0 +1,31 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := res
+
+LOCAL_MODULE := cts-tradefed
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt hosttestlib
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build all sub-directories
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
new file mode 100644
index 0000000..d98c7fb
--- /dev/null
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration
+    description="Runs a CTS plan from a pre-existing CTS installation">
+
+    <build_provider class="com.android.cts.tradefed.targetsetup.CtsBuildProvider" />
+    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+    <target_preparer class="com.android.cts.tradefed.targetsetup.CtsSetup" />
+    <test class="com.android.cts.tradefed.testtype.PlanTest" />
+    <logger class="com.android.tradefed.log.FileLogger" />
+    <result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
+
+</configuration>
diff --git a/tools/tradefed-host/res/result/cts_result.css b/tools/tradefed-host/res/result/cts_result.css
new file mode 100644
index 0000000..d7ce510
--- /dev/null
+++ b/tools/tradefed-host/res/result/cts_result.css
@@ -0,0 +1,234 @@
+/* Copyright (C) 2008 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.
+*/
+
+body {
+    font-family:arial,sans-serif;
+    color:#000;
+    font-size:13px;
+    color:#333;
+    padding:10;
+    margin:10;
+}
+
+table {
+    font-size:1em;
+    margin:0 0 1em;
+    padding:0;
+    border-collapse:collapse;
+    border-width:0;
+    empty-cells:show;
+    width: 95%;
+}
+
+/* Report logo and device name */
+#title table {
+    padding:5px;
+    border-width: 0px;
+    margin-left:auto;
+    margin-right:auto;
+    vertical-align:middle;
+}
+
+/* Device and test plan summary below the title */
+#summary table {
+    background-color: rgb(212, 233, 169);
+    -moz-border-radius:5px;
+    border-radius:5px;
+    padding:5px;
+    border-color: #A5C639 #A5C639 #A5C639 #A5C639;
+    border-width: 0px 0px 0px 0px;
+    margin-left:auto;
+    margin-right:auto;
+    width:80%;
+}
+
+#summary th {
+    background-color: #A5C639;
+    font-size:1.2em;
+    height: 2em;
+    width: 50%;
+}
+
+#summary td {
+    border-width: 0px 0px 0px 0px;
+    border-color: gray;
+    border-style: inset;
+    font-size:1em;
+    vertical-align: top;
+}
+
+#summaryinfo table {
+    background-color: rgb(212, 233, 169);
+    padding:5px;
+    border-width:0;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+#summaryinfo td {
+    padding:1px;
+    border-width: 0px 0px 0px 0px;
+    vertical-align: top;
+}
+
+/* The test summary */
+#testsummary table {
+    background-color: rgb(212, 233, 169);
+    padding:5px;
+    border-width:1;
+    border-color: #A5C639;
+    margin-left:auto;
+    margin-right:auto;
+    width: 40%;
+}
+
+#testsummary th {
+    background-color: #A5C639;
+    border-width: 1px;
+    border-color: gray;
+    border-style: outset;
+    height: 2em;
+}
+
+#testsummary td {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: center;
+}
+
+/* The test details */
+#testdetail table {
+    background-color: rgb(212, 233, 169);
+    padding:5px;
+    border-width:1;
+    border-color: #A5C639;
+    margin-left:auto;
+    margin-right:auto;
+    width: 95%;
+    table-layout:fixed;
+    vertical-align: top;
+}
+
+#testdetail th {
+    background-color: #A5C639;
+    border-width: 1px;
+    border-color: gray;
+    border-style: outset;
+    height: 2em;
+}
+
+#testdetail td {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+}
+
+/* The test package name */
+#none table {
+    border-width:0;
+    border-color: white;
+    background-color: white;
+    text-align:left;
+    border-collapse:collapse;
+}
+
+#none td {
+    border-width:0;
+    border-color: white;
+    background-color: white;
+    text-align:left;
+    border-collapse:collapse;
+    font-weight:bold;
+}
+
+/* Test cell details */
+td.failed {
+    background-color: #FA5858;
+    font-weight:bold;
+    vertical-align: top;
+    text-align: center;
+}
+
+td.failuredetails {
+    text-align: left;
+}
+
+td.pass {
+    text-align: center;
+    margin-left:auto;
+    margin-right:auto;
+}
+
+td.timeout, td.omitted, td.notExecuted {
+    background-color: #A5C639;
+    vertical-align: top;
+    text-align: center;
+}
+
+td.testname {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+}
+
+td.testcase {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+    font-weight:bold;
+}
+
+td.testcasespacer {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+    font-weight:bold;
+}
+
+td.testsuite {
+    border-width: 1px;
+    border-color: #A5C639;
+    border-style: outset;
+    text-align: left;
+    vertical-align: top;
+    padding:1;
+    overflow:hidden;
+    font-weight:bold;
+}
+
+#details {
+    white-space: pre-wrap;       /* css-3 */
+    white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
+    white-space: -pre-wrap;      /* Opera 4-6 */
+    white-space: -o-pre-wrap;    /* Opera 7 */
+    word-wrap: break-word;       /* Internet Explorer 5.5+ */
+    overflow:auto;
+}
diff --git a/tools/tradefed-host/res/result/cts_result.xsd b/tools/tradefed-host/res/result/cts_result.xsd
new file mode 100644
index 0000000..f2fc3a8
--- /dev/null
+++ b/tools/tradefed-host/res/result/cts_result.xsd
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2009 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.
+ -->
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           targetNamespace="http://compatibility.android.com/cts_result/2.0"
+           xmlns="http://compatibility.android.com/cts_result/2.0"
+           elementFormDefault="qualified">
+
+<xs:element name="TestResult">
+  <xs:complexType>
+    <xs:sequence>
+      <xs:element name="DeviceInfo" type="deviceInfoType"/>
+      <xs:element name="HostInfo" type="hostInfoType"/>
+      <xs:element name="Summary" type="summaryType"/>
+      <xs:element name="TestPackage" type="testPackageType" maxOccurs="unbounded" minOccurs="1"/>
+    </xs:sequence>
+    <xs:attribute name="starttime" type="xs:string"/>
+    <xs:attribute name="endtime" type="xs:string"/>
+    <xs:attribute name="testPlan" type="xs:string"/>
+    <xs:attribute name="version" type="xs:string"/>
+    <xs:attribute name="profile" type="xs:string"/>
+  </xs:complexType>
+</xs:element>
+
+<xs:complexType name="deviceInfoType">
+  <xs:sequence>
+    <xs:element name="Screen">
+      <xs:complexType>
+        <xs:attribute name="resolution" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="PhoneSubInfo">
+      <xs:complexType>
+        <xs:attribute name="subscriberId" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="BuildInfo">
+      <xs:complexType>
+        <xs:attribute name="Xdpi" type="xs:decimal"/>
+        <xs:attribute name="Ydpi" type="xs:decimal"/>
+        <xs:attribute name="androidPlatformVersion" type="xs:integer"/>
+        <xs:attribute name="buildID" type="xs:string"/>
+        <xs:attribute name="buildName" type="xs:string"/>
+        <xs:attribute name="buildVersion" type="xs:string"/>
+        <xs:attribute name="build_board" type="xs:string"/>
+        <xs:attribute name="build_brand" type="xs:string"/>
+        <xs:attribute name="build_device" type="xs:string"/>
+        <xs:attribute name="build_fingerprint" type="xs:string"/>
+        <xs:attribute name="build_model" type="xs:string"/>
+        <xs:attribute name="build_type" type="xs:string"/>
+        <xs:attribute name="deviceID" type="xs:string"/>
+        <xs:attribute name="imei" type="xs:integer"/>
+        <xs:attribute name="imsi" type="xs:integer"/>
+        <xs:attribute name="keypad" type="xs:string"/>
+        <xs:attribute name="locales" type="xs:string"/>
+        <xs:attribute name="navigation" type="xs:string"/>
+        <xs:attribute name="network" type="xs:string"/>
+        <xs:attribute name="touch" type="xs:string"/>
+        <xs:attribute name="openGlEsVersion" type="xs:string"/>
+        <xs:attribute name="build_abi" type="xs:string"/>
+        <xs:attribute name="build_abi2" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="FeatureInfo" type="featureInfoType"/>
+    <xs:element name="ProcessInfo" type="processInfoType"/>
+  </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="hostInfoType">
+  <xs:sequence>
+    <xs:element name="Os">
+      <xs:complexType>
+        <xs:attribute name="arch" type="xs:string"/>
+        <xs:attribute name="name" type="xs:string"/>
+        <xs:attribute name="version" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="Java">
+      <xs:complexType>
+        <xs:attribute name="name" type="xs:string"/>
+        <xs:attribute name="version" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="Cts">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="IntValue" minOccurs="0" maxOccurs="unbounded">
+            <xs:complexType>
+              <xs:attribute name="name" type="xs:string"/>
+              <xs:attribute name="value" type="xs:integer"/>
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+        <xs:attribute name="version" type="xs:string"/>
+      </xs:complexType>
+    </xs:element>
+  </xs:sequence>
+  <xs:attribute name="name" type="xs:string"/>
+</xs:complexType>
+
+<xs:complexType name="featureInfoType">
+    <xs:sequence>
+        <xs:element name="Feature" minOccurs="0" maxOccurs="unbounded">
+            <xs:complexType>
+                <xs:attribute name="name" type="xs:string" />
+                <xs:attribute name="type" type="xs:string" />
+                <xs:attribute name="available" type="xs:string" />
+            </xs:complexType>
+        </xs:element>
+    </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="processInfoType">
+    <xs:sequence>
+        <xs:element name="Process" minOccurs="0" maxOccurs="unbounded">
+            <xs:complexType>
+                <xs:attribute name="name" type="xs:string" />
+                <xs:attribute name="uid" type="xs:integer" />
+            </xs:complexType>
+        </xs:element>
+    </xs:sequence>
+</xs:complexType>
+
+<xs:complexType name="summaryType">
+  <xs:attribute name="failed" type="xs:integer"/>
+  <xs:attribute name="notExecuted" type="xs:integer"/>
+  <xs:attribute name="pass" type="xs:integer"/>
+  <xs:attribute name="timeout" type="xs:integer"/>
+  <xs:attribute name="omitted" type="xs:integer"/>
+  <xs:attribute name="total" type="xs:integer"/>
+</xs:complexType>
+
+<xs:complexType name="testPackageType">
+    <xs:complexContent>
+        <xs:extension base="summaryType">
+            <xs:sequence>
+                <xs:element name="TestCase" type="testCaseType" />
+            </xs:sequence>
+            <xs:attribute name="digest" type="xs:hexBinary" />
+            <xs:attribute name="name" type="xs:string" use="required" />
+            <xs:attribute name="runtime" type="xs:string" />
+        </xs:extension>
+    </xs:complexContent>
+</xs:complexType>
+
+<xs:complexType name="testCaseType">
+  <xs:sequence>
+    <xs:element name="Test" type="testType" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+  <xs:attribute name="name" type="xs:string" use="required"/>
+</xs:complexType>
+
+<xs:complexType name="testType">
+  <xs:sequence>
+    <xs:element name="FailedScene" minOccurs="0" maxOccurs="1">
+      <xs:complexType>
+          <xs:simpleContent>
+              <xs:extension base="xs:string">
+                  <xs:attribute name="message" type="xs:string" />
+              </xs:extension>
+          </xs:simpleContent>
+      </xs:complexType>
+    </xs:element>
+  </xs:sequence>
+  <xs:attribute name="name" type="xs:string" use="required"/>
+  <xs:attribute name="result" type="resultType" use="required"/>
+</xs:complexType>
+
+<xs:simpleType name="resultType">
+  <xs:restriction base="xs:string">
+    <xs:enumeration value="pass"/>
+    <xs:enumeration value="fail"/>
+    <xs:enumeration value="timeout"/>
+    <xs:enumeration value="notExecuted"/>
+    <xs:enumeration value="omitted"/>
+  </xs:restriction>
+</xs:simpleType>
+</xs:schema>
diff --git a/tools/tradefed-host/res/result/cts_result.xsl b/tools/tradefed-host/res/result/cts_result.xsl
new file mode 100644
index 0000000..cb220e1
--- /dev/null
+++ b/tools/tradefed-host/res/result/cts_result.xsl
@@ -0,0 +1,513 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
+
+    <xsl:template match="/">
+
+        <html>
+            <STYLE type="text/css">
+                @import "cts_result.css";
+            </STYLE>
+
+            <body>
+                <!-- Title of the Report -->
+                <DIV id="title">
+                    <TABLE>
+                        <TR>
+                            <TD width="40%" align="left"><img src="logo.gif"></img></TD>
+                            <TD width="60%" align="left">
+                                <h1>Test Report for <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/> -
+                                <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/>
+                            </h1>
+                        </TD>
+                    </TR>
+                </TABLE>
+            </DIV>
+            <img src="newrule-green.png" align="left"></img>
+
+            <br></br>
+            <br></br>
+
+            <!-- Header with phone and plan information -->
+            <DIV id="summary">
+                <TABLE width="90%" frame="none">
+                    <TR>
+                        <TH>Device Information</TH>
+                        <TH>Test Summary</TH>
+                    </TR>
+
+                    <TR>
+                        <TD>
+                            <!-- Device information -->
+                            <div id="summaryinfo">
+                                <TABLE width="75%">
+                                    <TR>
+                                        <TD class="rowtitle">Build Model</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Build Name</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildName"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Device ID</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Firmware Version</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildVersion"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Firmware Build Number</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildID"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Build Fingerprint</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_fingerprint"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Build ABI</TD>
+                                        <TD>
+                                            <xsl:value-of
+                                              select="TestResult/DeviceInfo/BuildInfo/@build_abi"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Build ABI2</TD>
+                                        <TD>
+                                            <xsl:value-of
+                                              select="TestResult/DeviceInfo/BuildInfo/@build_abi2"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Android Platform Version</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@androidPlatformVersion"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Supported Locales</TD>
+                                        <TD>
+                                            <xsl:call-template name="formatDelimitedString">
+                                                <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@locales"/>
+                                            </xsl:call-template>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Screen size</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/Screen/@resolution"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Phone number</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/PhoneSubInfo/@subscriberId"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">x dpi</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Xdpi"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">y dpi</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Ydpi"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Touch</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@touch"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Navigation</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@navigation"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Keypad</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@keypad"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Network</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@network"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">IMEI</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imei"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">IMSI</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imsi"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Open GL ES Version</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@openGlEsVersion"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Features</TD>
+                                        <TD>
+                                            <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='sdk']">
+                                                <xsl:text>[</xsl:text>
+                                                <xsl:choose>
+                                                    <xsl:when test="@available = 'true'">
+                                                        <xsl:text>X</xsl:text>
+                                                    </xsl:when>
+                                                    <xsl:otherwise>
+                                                        <xsl:text>_</xsl:text>
+                                                    </xsl:otherwise>
+                                                </xsl:choose>
+                                                <xsl:text>] </xsl:text>
+
+                                                <xsl:value-of select="@name" />
+                                                <br />
+                                            </xsl:for-each>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Other Features</TD>
+                                        <TD>
+                                            <UL>
+                                                <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='other']">
+                                                    <LI><xsl:value-of select="@name" /></LI>
+                                                </xsl:for-each>
+                                            </UL>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Root Processes</TD>
+                                        <TD>
+                                            <UL>
+                                                <xsl:for-each select="TestResult/DeviceInfo/ProcessInfo/Process[@uid='0']">
+                                                    <LI><xsl:value-of select="@name" /></LI>
+                                                </xsl:for-each>
+                                            </UL>
+                                        </TD>
+                                    </TR>
+                                </TABLE>
+                            </div>
+                        </TD>
+
+                        <!-- plan information -->
+                        <TD>
+                            <div id="summaryinfo">
+                                <TABLE width="75%">
+                                    <TR>
+                                        <TD class="rowtitle">CTS version</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/HostInfo/Cts/@version"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Test timeout</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/HostInfo/Cts/IntValue[@name='testStatusTimeoutMs']/@value" /> ms
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Host Info</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/HostInfo/@name"/>
+                                            (<xsl:value-of select="TestResult/HostInfo/Os/@name"/> - 
+                                              <xsl:value-of select="TestResult/HostInfo/Os/@version"/>)
+                                        </TD>
+                                    </TR>
+                                    <TR><TD><BR></BR></TD><TD></TD></TR>
+                                    <TR>
+                                        <TD class="rowtitle">Plan name</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/@testPlan"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Profile</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/@profile"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Start time</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/@starttime"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">End time</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/@endtime"/>
+                                        </TD>
+                                    </TR>
+
+                                    <!-- Test Summary -->
+                                    <TR><TD><BR></BR></TD><TD></TD></TR>
+                                    <TR>
+                                        <TD class="rowtitle">Tests Passed</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/Summary/@pass"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Tests Failed</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/Summary/@failed"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Tests Timed out</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/Summary/@timeout"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Tests Omitted</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/Summary/@omitted"/>
+                                        </TD>
+                                    </TR>
+                                    <TR>
+                                        <TD class="rowtitle">Tests Not Executed</TD>
+                                        <TD>
+                                            <xsl:value-of select="TestResult/Summary/@notExecuted"/>
+                                        </TD>
+                                    </TR>
+                                </TABLE>
+                            </div>
+                        </TD>
+                    </TR>
+                </TABLE>
+            </DIV>
+
+            <!-- High level summary of test execution -->
+            <h2 align="center">Test Summary by Package</h2>
+            <DIV id="testsummary">
+                <TABLE>
+                    <TR>
+                        <TH>Test Package</TH>
+                        <TH>Passed</TH>
+                        <TH>Failed</TH>
+                        <TH>Timed Out</TH>
+                        <TH>Total Tests</TH>
+                    </TR>
+                    <xsl:for-each select="TestResult/TestPackage">
+                        <TR>
+                            <TD>
+                                <xsl:variable name="href"><xsl:value-of select="@name"/></xsl:variable>
+                                <a href="#{$href}"><xsl:value-of select="@name"/></a>
+                            </TD>
+                            <TD>
+                                <xsl:value-of select="@pass"/>
+                            </TD>
+                            <TD>
+                                <xsl:value-of select="@failed"/>
+                            </TD>
+                            <TD>
+                                <xsl:value-of select="@timeout"/>
+                            </TD>
+                            <TD>
+                                <xsl:value-of select="@total"/>
+                            </TD>
+                        </TR>
+                    </xsl:for-each> <!-- end package -->
+                </TABLE>
+            </DIV>
+
+            <!-- Details of all the executed tests -->
+            <h2 align="center">Detailed Test Report</h2>
+
+            <!-- test package -->
+            <DIV id="testdetail">
+                <xsl:for-each select="TestResult/TestPackage">
+                    <DIV id="none">
+                        <TABLE>
+                            <TR>
+                                <TD class="none" align="left">
+                                    <xsl:variable name="href"><xsl:value-of select="@name"/></xsl:variable>
+                                    <a name="{$href}">Compatibility Test Package: <xsl:value-of select="@name"/></a>
+                                </TD>
+                            </TR>
+                        </TABLE>
+                    </DIV>
+
+                    <TABLE>
+                        <TR>
+                            <TH width="25%">Test</TH>
+                            <TH width="7%">Result</TH>
+                            <TH width="68%">Failure Details</TH>
+                        </TR>
+
+                        <!-- test case -->
+                        <xsl:for-each select="TestCase">
+
+                            <!-- emit a blank row before every test suite name -->
+                            <xsl:if test="position()!=1">
+                                <TR> <TD class="testcasespacer" colspan="3"></TD> </TR>
+                            </xsl:if>
+
+
+                            <TR>
+                                <TD class="testcase" colspan="3">
+                                    <xsl:value-of select="@name"/>
+                                </TD>
+                            </TR>
+                            
+                            <!-- test -->
+                            <xsl:for-each select="Test">
+                                <TR>
+                                    <TD class="testname"> -- <xsl:value-of select="@name"/></TD>
+
+                                    <!-- test results -->
+                                    <xsl:choose>
+                                        <xsl:when test="string(@KnownFailure)">
+                                            <!-- "pass" indicates the that test actually passed (results have been inverted already) -->
+                                            <xsl:if test="@result='pass'">
+                                                <TD class="pass">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        known problem
+                                                    </div>
+                                                </TD>
+                                                <TD class="failuredetails"></TD>
+                                            </xsl:if>
+
+                                            <!-- "fail" indicates that a known failure actually passed (results have been inverted already) -->
+                                            <xsl:if test="@result='fail'">
+                                                <TD class="failed">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                </TD>
+                                               <TD class="failuredetails">
+                                                    <div id="details">
+                                                        A test that was a known failure actually passed. Please check.
+                                                    </div>
+                                               </TD>
+                                            </xsl:if>
+                                        </xsl:when>
+
+                                        <xsl:otherwise>
+                                            <xsl:if test="@result='pass'">
+                                                <TD class="pass">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                </TD>
+                                                <TD class="failuredetails"></TD>
+                                            </xsl:if>
+
+                                            <xsl:if test="@result='fail'">
+                                                <TD class="failed">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                </TD>
+                                                <TD class="failuredetails">
+                                                    <div id="details">
+                                                        <xsl:value-of select="FailedScene/@message"/>
+                                                    </div>
+                                                </TD>
+                                            </xsl:if>
+
+                                            <xsl:if test="@result='timeout'">
+                                                <TD class="timeout">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                <TD class="failuredetails"></TD>
+                                                </TD>
+                                            </xsl:if>
+
+                                            <xsl:if test="@result='omitted'">
+                                                <TD class="omitted">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                </TD>
+                                                <TD class="failuredetails"></TD>
+                                            </xsl:if>
+
+                                            <xsl:if test="@result='notExecuted'">
+                                                <TD class="notExecuted">
+                                                    <div style="text-align: center; margin-left:auto; margin-right:auto;">
+                                                        <xsl:value-of select="@result"/>
+                                                    </div>
+                                                </TD>
+                                                <TD class="failuredetails"></TD>
+                                            </xsl:if>
+                                        </xsl:otherwise>
+                                    </xsl:choose>
+                                </TR> <!-- finished with a row -->
+                            </xsl:for-each> <!-- end test -->
+                        </xsl:for-each> <!-- end test case -->
+                    </TABLE>
+                </xsl:for-each> <!-- end test package -->
+            </DIV>
+            </body>
+        </html>
+    </xsl:template>
+
+    <!-- Take a delimited string and insert line breaks after a some number of elements. --> 
+    <xsl:template name="formatDelimitedString">
+        <xsl:param name="string" />
+        <xsl:param name="numTokensPerRow" select="10" />
+        <xsl:param name="tokenIndex" select="1" />
+        <xsl:if test="$string">
+            <!-- Requires the last element to also have a delimiter after it. -->
+            <xsl:variable name="token" select="substring-before($string, ';')" />
+            <xsl:value-of select="$token" />
+            <xsl:text>&#160;</xsl:text>
+          
+            <xsl:if test="$tokenIndex mod $numTokensPerRow = 0">
+                <br />
+            </xsl:if>
+
+            <xsl:call-template name="formatDelimitedString">
+                <xsl:with-param name="string" select="substring-after($string, ';')" />
+                <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" />
+                <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" />
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/tradefed-host/res/result/logo.gif b/tools/tradefed-host/res/result/logo.gif
new file mode 100644
index 0000000..61970b3
--- /dev/null
+++ b/tools/tradefed-host/res/result/logo.gif
Binary files differ
diff --git a/tools/tradefed-host/res/result/newrule-green.png b/tools/tradefed-host/res/result/newrule-green.png
new file mode 100644
index 0000000..10a4194
--- /dev/null
+++ b/tools/tradefed-host/res/result/newrule-green.png
Binary files differ
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
new file mode 100644
index 0000000..b914f2e
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
@@ -0,0 +1,74 @@
+/*
+ * 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.device;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.InstrumentationTest;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Collects info from device under test.
+ * <p/>
+ * This class simply serves as a conduit for grabbing info from device using the device info
+ * collector apk, and forwarding that data directly to the {@link ITestInvocationListener} as run
+ * metrics.
+ */
+public class DeviceInfoCollector {
+
+    private static final String LOG_TAG = "DeviceInfoCollector";
+    private static final String APK_NAME = "TestDeviceSetup";
+    public static final String APP_PACKAGE_NAME = "android.tests.devicesetup";
+    private static final String INSTRUMENTATION_NAME = "android.tests.getinfo.DeviceInfoInstrument";
+    // metric constants
+    // this values must exactly match those defined in
+    // cts/tools/device-setup/.../DeviceInfoInstrument.java
+    public static final String SCREEN_WIDTH = "screen_width";
+    public static final String SCREEN_HEIGHT = "screen_height";
+    public static final String PHONE_NUMBER = "phoneNumber";
+    public static final String FEATURES = "features";
+    public static final String PROCESSES = "processes";
+
+    /**
+     * Installs and runs the device info collector instrumentation, and forwards results
+     * to the <var>listeners</var>
+     *
+     * @param device
+     * @param listeners
+     * @throws DeviceNotAvailableException
+     */
+    public static void collectDeviceInfo(ITestDevice device, File testApkDir,
+            List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+        File apkFile = new File(testApkDir, String.format("%s.apk", APK_NAME));
+        if (!apkFile.exists()) {
+            Log.e(LOG_TAG, String.format("Could not find %s", apkFile.getAbsolutePath()));
+        }
+        // collect the instrumentation bundle results using instrumentation test
+        // should work even though no tests will actually be run
+        InstrumentationTest instrTest = new InstrumentationTest();
+        instrTest.setDevice(device);
+        instrTest.setInstallFile(apkFile);
+        // no need to collect tests and re-run
+        instrTest.setRerunMode(false);
+        instrTest.setPackageName(APP_PACKAGE_NAME);
+        instrTest.setRunnerName(INSTRUMENTATION_NAME);
+        instrTest.run(listeners);
+    }
+}
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
new file mode 100644
index 0000000..4c29b01
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -0,0 +1,635 @@
+/*
+ * 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.result;
+
+import com.android.cts.tradefed.device.DeviceInfoCollector;
+import com.android.cts.tradefed.targetsetup.CtsBuildHelper;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.TestResult.TestStatus;
+import com.android.tradefed.targetsetup.IBuildInfo;
+import com.android.tradefed.targetsetup.IFolderBuildInfo;
+import com.android.tradefed.util.FileUtil;
+
+import org.kxml2.io.KXmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Writes results to an XML files in the CTS format.
+ * <p/>
+ * Collects all test info in memory, then dumps to file when invocation is complete.
+ * <p/>
+ * Outputs xml in format governed by the cts_result.xsd
+ */
+public class CtsXmlResultReporter extends CollectingTestListener {
+
+    private static final String LOG_TAG = "CtsXmlResultReporter";
+
+    private static final String TEST_RESULT_FILE_NAME = "testResult.xml";
+    private static final String CTS_RESULT_FILE_VERSION = "2.0";
+    private static final String CTS_VERSION = "99";
+
+
+    private static final String[] CTS_RESULT_RESOURCES = {"cts_result.xsl", "cts_result.css",
+        "logo.gif", "newrule-green.png"};
+
+    /** the XML namespace */
+    private static final String ns = null;
+
+    private static final String REPORT_DIR_NAME = "output-file-path";
+    @Option(name=REPORT_DIR_NAME, description="root file system path to directory to store xml " +
+            "test results and associated logs. If not specified, results will be stored at " +
+            "<cts root>/repository/results")
+    protected File mReportDir = null;
+
+    protected IBuildInfo mBuildInfo;
+
+    private String mStartTime;
+
+    public void setReportDir(File reportDir) {
+        mReportDir = reportDir;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @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());
+                mReportDir = buildHelper.getResultsDir();
+
+            } catch (FileNotFoundException e) {
+                throw new IllegalArgumentException("unrecognized cts structure", e);
+            }
+        }
+        // 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, getResultTimestamp());
+        mReportDir.mkdirs();
+        mStartTime = getTimestamp();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testLog(String dataName, LogDataType dataType, InputStream dataStream) {
+        // TODO: implement this
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+        super.testFailed(status, test, trace);
+        Log.i(LOG_TAG, String.format("Test %s#%s: %s\n%s", test.getClassName(), test.getTestName(),
+                status.toString(), trace));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+        super.testRunEnded(elapsedTime, runMetrics);
+        Log.i(LOG_TAG, String.format("Test run %s complete. Tests passed %d, failed %d, error %d",
+                getCurrentRunResults().getName(), getCurrentRunResults().getNumPassedTests(),
+                getCurrentRunResults().getNumFailedTests(),
+                getCurrentRunResults().getNumErrorTests()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invocationEnded(long elapsedTime) {
+        super.invocationEnded(elapsedTime);
+        createXmlResult(mReportDir, mStartTime, elapsedTime);
+        copyFormattingFiles(mReportDir);
+        zipResults(mReportDir);
+    }
+
+    /**
+     * Creates a report file and populates it with the report data from the completed tests.
+     */
+    private void createXmlResult(File reportDir, String startTimestamp, long elapsedTime) {
+        String endTime = getTimestamp();
+
+        OutputStream stream = null;
+        try {
+            stream = createOutputResultStream(reportDir);
+            KXmlSerializer serializer = new KXmlSerializer();
+            serializer.setOutput(stream, "UTF-8");
+            serializer.startDocument("UTF-8", false);
+            serializer.setFeature(
+                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.processingInstruction("xml-stylesheet type=\"text/xsl\"  " +
+                    "href=\"cts_result.xsl\"");
+            serializeResultsDoc(serializer, startTimestamp, endTime);
+            serializer.endDocument();
+            // TODO: output not executed timeout omitted counts
+            String msg = String.format("XML test result file generated at %s. Total tests %d, " +
+                    "Failed %d, Error %d", reportDir.getAbsolutePath(), getNumTotalTests(),
+                    getNumFailedTests(), getNumErrorTests());
+            Log.logAndDisplay(LogLevel.INFO, LOG_TAG, msg);
+            Log.logAndDisplay(LogLevel.INFO, LOG_TAG, String.format("Time: %s",
+                    formatElapsedTime(elapsedTime)));
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Failed to generate report data");
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Output the results XML.
+     *
+     * @param serializer the {@link KXmlSerializer} to use
+     * @param startTime the user-friendly starting time of the test invocation
+     * @param endTime the user-friendly ending time of the test invocation
+     * @throws IOException
+     */
+    private void serializeResultsDoc(KXmlSerializer serializer, String startTime, String endTime)
+            throws IOException {
+        serializer.startTag(ns, "TestResult");
+        // TODO: output test plan and profile values
+        serializer.attribute(ns, "testPlan", "unknown");
+        serializer.attribute(ns, "profile", "unknown");
+        serializer.attribute(ns, "starttime", startTime);
+        serializer.attribute(ns, "endtime", endTime);
+        serializer.attribute(ns, "version", CTS_RESULT_FILE_VERSION);
+
+        serializeDeviceInfo(serializer);
+        serializeHostInfo(serializer);
+        serializeTestSummary(serializer);
+        serializeTestResults(serializer);
+    }
+
+    /**
+     * Output the device info XML.
+     *
+     * @param serializer
+     */
+    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));
+            return;
+        }
+        // Extract metrics that need extra handling, and then dump the remainder into BuildInfo
+        Map<String, String> metricsCopy = new HashMap<String, String>(
+                deviceInfoResult.getRunMetrics());
+        serializer.startTag(ns, "Screen");
+        String screenWidth = metricsCopy.remove(DeviceInfoCollector.SCREEN_WIDTH);
+        String screenHeight = metricsCopy.remove(DeviceInfoCollector.SCREEN_HEIGHT);
+        serializer.attribute(ns, "resolution", String.format("%sx%s", screenWidth, screenHeight));
+        serializer.endTag(ns, "Screen");
+
+        serializer.startTag(ns, "PhoneSubInfo");
+        serializer.attribute(ns, "subscriberId", metricsCopy.remove(
+                DeviceInfoCollector.PHONE_NUMBER));
+        serializer.endTag(ns, "PhoneSubInfo");
+
+        String featureData = metricsCopy.remove(DeviceInfoCollector.FEATURES);
+        String processData = metricsCopy.remove(DeviceInfoCollector.PROCESSES);
+
+        // dump the remaining metrics without translation
+        serializer.startTag(ns, "BuildInfo");
+        for (Map.Entry<String, String> metricEntry : metricsCopy.entrySet()) {
+            serializer.attribute(ns, metricEntry.getKey(), metricEntry.getValue());
+        }
+        serializer.endTag(ns, "BuildInfo");
+
+        serializeFeatureInfo(serializer, featureData);
+        serializeProcessInfo(serializer, processData);
+
+        serializer.endTag(ns, "DeviceInfo");
+    }
+
+    /**
+     * Prints XML indicating what features are supported by the device. It parses a string from the
+     * featureData argument that is in the form of "feature1:true;feature2:false;featuer3;true;"
+     * with a trailing semi-colon.
+     *
+     * <pre>
+     *  <FeatureInfo>
+     *     <Feature name="android.name.of.feature" available="true" />
+     *     ...
+     *   </FeatureInfo>
+     * </pre>
+     *
+     * @param serializer used to create XML
+     * @param featureData raw unparsed feature data
+     */
+    private void serializeFeatureInfo(KXmlSerializer serializer, String featureData) throws IOException {
+        serializer.startTag(ns, "FeatureInfo");
+
+        if (featureData == null) {
+            featureData = "";
+        }
+
+        String[] featurePairs = featureData.split(";");
+        for (String featurePair : featurePairs) {
+            String[] nameTypeAvailability = featurePair.split(":");
+            if (nameTypeAvailability.length >= 3) {
+                serializer.startTag(ns, "Feature");
+                serializer.attribute(ns, "name", nameTypeAvailability[0]);
+                serializer.attribute(ns, "type", nameTypeAvailability[1]);
+                serializer.attribute(ns, "available", nameTypeAvailability[2]);
+                serializer.endTag(ns, "Feature");
+            }
+        }
+        serializer.endTag(ns, "FeatureInfo");
+    }
+
+    /**
+     * Prints XML data indicating what particular processes of interest were running on the device.
+     * It parses a string from the rootProcesses argument that is in the form of
+     * "processName1;processName2;..." with a trailing semi-colon.
+     *
+     * <pre>
+     *   <ProcessInfo>
+     *     <Process name="long_cat_viewer" uid="0" />
+     *     ...
+     *   </ProcessInfo>
+     * </pre>
+     *
+     * @param document
+     * @param parentNode
+     * @param deviceInfo
+     */
+    private void serializeProcessInfo(KXmlSerializer serializer, String rootProcesses)
+            throws IOException {
+        serializer.startTag(ns, "ProcessInfo");
+
+        if (rootProcesses == null) {
+            rootProcesses = "";
+        }
+
+        String[] processNames = rootProcesses.split(";");
+        for (String processName : processNames) {
+            processName = processName.trim();
+            if (processName.length() > 0) {
+                serializer.startTag(ns, "Process");
+                serializer.attribute(ns, "name", processName);
+                serializer.attribute(ns, "uid", "0");
+                serializer.endTag(ns, "Process");
+            }
+        }
+        serializer.endTag(ns, "ProcessInfo");
+    }
+
+    /**
+     * 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
+     */
+    private void serializeHostInfo(KXmlSerializer serializer) throws IOException {
+        serializer.startTag(ns, "HostInfo");
+
+        String hostName = "";
+        try {
+            hostName = InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException ignored) {}
+        serializer.attribute(ns, "name", hostName);
+
+        serializer.startTag(ns, "Os");
+        serializer.attribute(ns, "name", System.getProperty("os.name"));
+        serializer.attribute(ns, "version", System.getProperty("os.version"));
+        serializer.attribute(ns, "arch", System.getProperty("os.arch"));
+        serializer.endTag(ns, "Os");
+
+        serializer.startTag(ns, "Java");
+        serializer.attribute(ns, "name", System.getProperty("java.vendor"));
+        serializer.attribute(ns, "version", System.getProperty("java.version"));
+        serializer.endTag(ns, "Java");
+
+        serializer.startTag(ns, "Cts");
+        serializer.attribute(ns, "version", CTS_VERSION);
+        // TODO: consider outputting tradefed options here
+        serializer.endTag(ns, "Cts");
+
+        serializer.endTag(ns, "HostInfo");
+    }
+
+    /**
+     * Output the test summary XML containing summary totals for all tests.
+     *
+     * @param serializer
+     * @throws IOException
+     */
+    private void serializeTestSummary(KXmlSerializer serializer) throws IOException {
+        serializer.startTag(ns, "Summary");
+        serializer.attribute(ns, "failed", Integer.toString(getNumErrorTests() +
+                getNumFailedTests()));
+        // TODO: output notExecuted, timeout, and omitted count
+        serializer.attribute(ns, "notExecuted", "0");
+        serializer.attribute(ns, "timeout", "0");
+        serializer.attribute(ns, "omitted", "0");
+        serializer.attribute(ns, "pass", Integer.toString(getNumPassedTests()));
+        serializer.attribute(ns, "total", Integer.toString(getNumTotalTests()));
+        serializer.endTag(ns, "Summary");
+    }
+
+    /**
+     * 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;
+        }
+        serializer.startTag(ns, "TestPackage");
+        serializer.attribute(ns, "name", runResult.getName());
+        serializer.attribute(ns, "runTime", formatElapsedTime(runResult.getElapsedTime()));
+        // TODO: generate digest
+        serializer.attribute(ns, "digest", "");
+        serializer.attribute(ns, "failed", Integer.toString(runResult.getNumErrorTests() +
+                runResult.getNumFailedTests()));
+        // TODO: output notExecuted, timeout, and omitted count
+        serializer.attribute(ns, "notExecuted", "0");
+        serializer.attribute(ns, "timeout", "0");
+        serializer.attribute(ns, "omitted", "0");
+        serializer.attribute(ns, "pass", Integer.toString(runResult.getNumPassedTests()));
+        serializer.attribute(ns, "total", Integer.toString(runResult.getNumTests()));
+
+        // the results XML needs to organize test's by class. Build a nested data structure that
+        // group's the results by class name
+        Map<String, Map<TestIdentifier, TestResult>> classResultsMap = buildClassNameMap(
+                runResult.getTestResults());
+
+        for (Map.Entry<String, Map<TestIdentifier, TestResult>> resultsEntry :
+                classResultsMap.entrySet()) {
+            serializer.startTag(ns, "TestCase");
+            serializer.attribute(ns, "name", resultsEntry.getKey());
+            serializeTests(serializer, resultsEntry.getValue());
+            serializer.endTag(ns, "TestCase");
+        }
+        serializer.endTag(ns, "TestPackage");
+    }
+
+    /**
+     * Organizes the test run results into a format organized by class name.
+     */
+    private Map<String, Map<TestIdentifier, TestResult>> buildClassNameMap(
+            Map<TestIdentifier, TestResult> results) {
+        // use a linked hashmap to have predictable iteration order
+        Map<String, Map<TestIdentifier, TestResult>> classResultMap =
+            new LinkedHashMap<String, Map<TestIdentifier, TestResult>>();
+        for (Map.Entry<TestIdentifier, TestResult> resultEntry : results.entrySet()) {
+            String className = resultEntry.getKey().getClassName();
+            Map<TestIdentifier, TestResult> resultsForClass = classResultMap.get(className);
+            if (resultsForClass == null) {
+                resultsForClass = new LinkedHashMap<TestIdentifier, TestResult>();
+                classResultMap.put(className, resultsForClass);
+            }
+            resultsForClass.put(resultEntry.getKey(), resultEntry.getValue());
+        }
+        return classResultMap;
+    }
+
+    /**
+     * Output XML for given map of tests their results
+     *
+     * @param serializer
+     * @param results
+     * @throws IOException
+     */
+    private void serializeTests(KXmlSerializer serializer, Map<TestIdentifier, TestResult> results)
+            throws IOException {
+        for (Map.Entry<TestIdentifier, TestResult> resultEntry : results.entrySet()) {
+            serializeTest(serializer, resultEntry.getKey(), resultEntry.getValue());
+        }
+    }
+
+    /**
+     * Output the XML for given test and result.
+     *
+     * @param serializer
+     * @param testId
+     * @param result
+     * @throws IOException
+     */
+    private void serializeTest(KXmlSerializer serializer, TestIdentifier testId, TestResult result)
+            throws IOException {
+        serializer.startTag(ns, "Test");
+        serializer.attribute(ns, "name", testId.getTestName());
+        serializer.attribute(ns, "result", convertStatus(result.getStatus()));
+
+        if (result.getStackTrace() != null) {
+            String sanitizedStack = sanitizeStackTrace(result.getStackTrace());
+            serializer.startTag(ns, "FailedScene");
+            serializer.attribute(ns, "message", getFailureMessageFromStackTrace(sanitizedStack));
+            serializer.text(sanitizedStack);
+            serializer.endTag(ns, "FailedScene");
+        }
+        serializer.endTag(ns, "Test");
+    }
+
+    /**
+     * 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 "fail";
+            case FAILURE:
+                return "fail";
+            case PASSED:
+                return "pass";
+            // TODO add notExecuted, omitted timeout
+        }
+        return "omitted";
+    }
+
+    /**
+     * Strip out any invalid XML characters that might cause the report to be unviewable.
+     * http://www.w3.org/TR/REC-xml/#dt-character
+     */
+    private static String sanitizeStackTrace(String trace) {
+        if (trace != null) {
+            return trace.replaceAll("[^\\u0009\\u000A\\u000D\\u0020-\\uD7FF\\uE000-\\uFFFD]", "");
+        } else {
+            return null;
+        }
+    }
+
+    private static String getFailureMessageFromStackTrace(String stack) {
+        // This is probably too simplistic to work in all cases, but for now, just return first
+        // line of stack as failure message
+        int firstNewLine = stack.indexOf('\n');
+        if (firstNewLine != -1) {
+            return stack.substring(0, firstNewLine);
+        }
+        return stack;
+    }
+
+    /**
+     * Return the current timestamp as a {@link String} suitable for displaying.
+     * <p/>
+     * Example: Fri Aug 20 15:13:03 PDT 2010
+     */
+    String getTimestamp() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
+        return dateFormat.format(new Date());
+    }
+
+    /**
+     * Return the current timestamp in a compressed format, used to uniquely identify results.
+     * <p/>
+     * Example: 2010.08.16_11.42.12
+     */
+    private String getResultTimestamp() {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss");
+        return dateFormat.format(new Date());
+    }
+
+    /**
+     * Return a prettified version of the given elapsed time
+     * @return
+     */
+    private String formatElapsedTime(long elapsedTimeMs) {
+        long seconds = TimeUnit.MILLISECONDS.toSeconds(elapsedTimeMs) % 60;
+        long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTimeMs) % 60;
+        long hours = TimeUnit.MILLISECONDS.toHours(elapsedTimeMs);
+        StringBuilder time = new StringBuilder();
+        if (hours > 0) {
+            time.append(hours);
+            time.append("h ");
+        }
+        if (minutes > 0) {
+            time.append(minutes);
+            time.append("m ");
+        }
+        time.append(seconds);
+        time.append("s");
+
+        return time.toString();
+    }
+
+    /**
+     * Creates the output stream to use for test results. Exposed for mocking.
+     */
+    OutputStream createOutputResultStream(File reportDir) throws IOException {
+        File reportFile = new File(reportDir, TEST_RESULT_FILE_NAME);
+        Log.i(LOG_TAG, String.format("Created xml report file at %s",
+                reportFile.getAbsolutePath()));
+        return new FileOutputStream(reportFile);
+    }
+
+    /**
+     * Copy the xml formatting files stored in this jar to the results directory
+     *
+     * @param resultsDir
+     */
+    private void copyFormattingFiles(File resultsDir) {
+        for (String resultFileName : CTS_RESULT_RESOURCES) {
+            InputStream configStream = getClass().getResourceAsStream(
+                    String.format("/result/%s", resultFileName));
+            if (configStream != null) {
+                File resultFile = new File(resultsDir, resultFileName);
+                try {
+                    FileUtil.writeToFile(configStream, resultFile);
+                } catch (IOException e) {
+                    Log.w(LOG_TAG, String.format("Failed to write %s to file", resultFileName));
+                }
+            } else {
+                Log.w(LOG_TAG, String.format("Failed to load %s from jar", resultFileName));
+            }
+        }
+    }
+
+    /**
+     * Zip the contents of the given results directory.
+     *
+     * @param resultsDir
+     */
+    private void zipResults(File resultsDir) {
+        // TODO: implement this
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java
new file mode 100644
index 0000000..9597109
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.targetsetup;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Helper class for retrieving files from the CTS install.
+ * <p/>
+ * Encapsulates the filesystem layout of the CTS installation.
+ */
+public class CtsBuildHelper {
+
+    static final String CTS_DIR_NAME = "android-cts";
+    /** The root location of the extracted CTS package */
+    private final File mRootDir;
+    /** the {@link CTS_DIR_NAME} directory */
+    private final File mCtsDir;
+
+    /**
+     * Creates a {@link CtsBuildHelper}.
+     *
+     * @param rootDir the parent folder that contains the "android-cts" directory and all its
+     *            contents.
+     * @throws FileNotFoundException if file does not exist
+     */
+    public CtsBuildHelper(File rootDir) throws FileNotFoundException {
+        mRootDir = rootDir;
+        mCtsDir = new File(mRootDir, CTS_DIR_NAME);
+        if (!mCtsDir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "CTS install folder %s does not exist", mCtsDir.getAbsolutePath()));
+        }
+    }
+
+    /**
+     * @return a {@link File} representing the parent folder of the CTS installation
+     */
+    public File getRootDir() {
+        return mRootDir;
+    }
+
+    /**
+     * @return a {@link File} representing the "android-cts" folder of the CTS installation
+     */
+    public File getCtsDir() {
+        return mCtsDir;
+    }
+
+    /**
+     * @return a {@link File} representing the test application file with given name
+     * @throws FileNotFoundException if file does not exist
+     */
+    public File getTestApp(String appFileName) throws FileNotFoundException {
+        File apkFile = new File(getTestCasesDir(), appFileName);
+        if (!apkFile.exists()) {
+            throw new FileNotFoundException(String.format("CTS test app file %s does not exist",
+                    apkFile.getAbsolutePath()));
+        }
+        return apkFile;
+    }
+
+    private File getRepositoryDir() {
+        return new File(getCtsDir(), "repository");
+    }
+
+    /**
+     * @return a {@link File} representing the results directory.
+     */
+    public File getResultsDir() {
+        return new File(getRepositoryDir(), "results");
+    }
+
+    /**
+     * @return a {@link File} representing the test cases directory
+     * @throws FileNotFoundException if dir does not exist
+     */
+    public File getTestCasesDir() throws FileNotFoundException {
+        File dir = new File(getRepositoryDir(), "testcases");
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "CTS test cases directory %s does not exist", dir.getAbsolutePath()));
+        }
+        return dir;
+    }
+
+    /**
+     * @return a {@link File} representing the test plan directory
+     * @throws FileNotFoundException if dir does not exist
+     */
+    public File getTestPlansDir() throws FileNotFoundException {
+        File dir = new File(getRepositoryDir(), "plans");
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "CTS test plans directory %s does not exist", dir.getAbsolutePath()));
+        }
+        return dir;
+    }
+}
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
new file mode 100644
index 0000000..90fb8ba
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.targetsetup;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.targetsetup.FolderBuildInfo;
+import com.android.tradefed.targetsetup.IBuildInfo;
+import com.android.tradefed.targetsetup.IBuildProvider;
+import com.android.tradefed.targetsetup.IFolderBuildInfo;
+import com.android.tradefed.targetsetup.TargetSetupError;
+
+import java.io.File;
+
+/**
+ * A simple {@link IBuildProvider} that uses a pre-existing CTS install.
+ */
+public class CtsBuildProvider implements IBuildProvider {
+
+    @Option(name="cts-install-path", description="the path to the cts installation to use")
+    private File mCtsRootDir;
+
+    /**
+     * {@inheritDoc}
+     */
+    public IBuildInfo getBuild() throws TargetSetupError {
+        if (mCtsRootDir == null) {
+            throw new IllegalArgumentException("Missing --cts-install-path");
+        }
+        IFolderBuildInfo ctsBuild = new FolderBuildInfo(0, "cts", "cts");
+        ctsBuild.setRootDir(mCtsRootDir);
+        return ctsBuild;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void buildNotTested(IBuildInfo info) {
+        // ignore
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsRootDeviceSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsRootDeviceSetup.java
new file mode 100644
index 0000000..589bed3
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsRootDeviceSetup.java
@@ -0,0 +1,95 @@
+/*
+ * 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.targetsetup;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetsetup.BuildError;
+import com.android.tradefed.targetsetup.DeviceSetup;
+import com.android.tradefed.targetsetup.IBuildInfo;
+import com.android.tradefed.targetsetup.IFolderBuildInfo;
+import com.android.tradefed.targetsetup.ITargetPreparer;
+import com.android.tradefed.targetsetup.TargetSetupError;
+
+import java.io.FileNotFoundException;
+
+/**
+ * A {@link ITargetPreparer} that attempts to automatically perform the CTS-specific manual steps
+ * for setting up a device for CTS testing.
+ * <p/>
+ * This class is NOT intended for 'official' CTS runs against a production device as the steps
+ * performed by this class require a debug build (aka 'adb root' must succeed).
+ * <p/>
+ * This class currently performs the 'Allow mock locations' and 'accessibililty setup' steps
+ * documented in the CTS user manual. It is intended to be used in conjunction with
+ * a {@link DeviceSetup} which will enable the 'Stay Awake' setting and verify that external
+ * storage is present.
+ */
+public class CtsRootDeviceSetup implements ITargetPreparer {
+
+    private static final String LOG_TAG = "CtsRootDeviceSetup";
+
+    // TODO: read this from a configuration file rather than hard-coding
+    private static final String ACCESSIBILITY_SERVICE_APK_FILE_NAME =
+        "CtsDelegatingAccessibilityService.apk";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+            DeviceNotAvailableException, BuildError {
+        if (!(buildInfo instanceof IFolderBuildInfo)) {
+            throw new IllegalArgumentException("Provided buildInfo is not a IFolderBuildInfo");
+        }
+        Log.i(LOG_TAG, String.format("Setting up %s to run CTS tests", device.getSerialNumber()));
+
+        IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
+        try {
+            CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
+
+            if (!device.enableAdbRoot()) {
+                throw new TargetSetupError(String.format(
+                        "Failed to set root on device %s.", device.getSerialNumber()));
+            }
+
+            // perform CTS setup steps that only work if adb is root
+
+            // TODO: turn on mock locations
+            enableAccessibilityService(device, buildHelper);
+
+            // end root setup steps
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError("Invalid CTS installation", e);
+        }
+    }
+
+    private void enableAccessibilityService(ITestDevice device, CtsBuildHelper ctsBuild)
+            throws DeviceNotAvailableException, TargetSetupError,
+            FileNotFoundException {
+        String errorCode = device.installPackage(
+                ctsBuild.getTestApp(ACCESSIBILITY_SERVICE_APK_FILE_NAME), true);
+        if (errorCode != null) {
+            // TODO: retry ?
+            throw new TargetSetupError(String.format(
+                    "Failed to install %s on device %s. Reason: %s",
+                    ACCESSIBILITY_SERVICE_APK_FILE_NAME, device.getSerialNumber(), errorCode));
+        }
+        // TODO: enable Settings > Accessibility > Accessibility > Delegating Accessibility
+        // Service
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java
new file mode 100644
index 0000000..77b4656
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java
@@ -0,0 +1,120 @@
+/*
+ * 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.targetsetup;
+
+import com.android.cts.tradefed.testtype.PlanTest;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetsetup.BuildError;
+import com.android.tradefed.targetsetup.IBuildInfo;
+import com.android.tradefed.targetsetup.IFolderBuildInfo;
+import com.android.tradefed.targetsetup.ITargetPreparer;
+import com.android.tradefed.targetsetup.TargetSetupError;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * A {@link ITargetPreparer} that sets up a device for CTS testing.
+ * <p/>
+ * All the actions performed in this class must work on a production device.
+ */
+public class CtsSetup implements ITargetPreparer, IConfigurationReceiver {
+
+    private static final String RUNNER_APK_NAME = "android.core.tests.runner.apk";
+    // TODO: read this from configuration file rather than hardcoding
+    private static final String TEST_STUBS_APK = "CtsTestStubs.apk";
+
+    private IConfiguration mConfiguration = null;
+
+    /**
+     * Factory method to create a {@link CtsBuildHelper}.
+     * <p/>
+     * Exposed for unit testing.
+     */
+    CtsBuildHelper createBuildHelper(File rootDir) throws FileNotFoundException {
+        return new CtsBuildHelper(rootDir);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setConfiguration(IConfiguration configuration) {
+        mConfiguration = configuration;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
+            BuildError, DeviceNotAvailableException {
+        if (!(buildInfo instanceof IFolderBuildInfo)) {
+            throw new IllegalArgumentException("Provided buildInfo is not a IFolderBuildInfo");
+        }
+        if (mConfiguration == null) {
+            throw new IllegalStateException("setConfiguration() was not called before setUp");
+        }
+        IFolderBuildInfo ctsBuildInfo = (IFolderBuildInfo)buildInfo;
+        try {
+            CtsBuildHelper buildHelper = createBuildHelper(ctsBuildInfo.getRootDir());
+            // pass necessary build information to the other config objects
+            mConfiguration.injectOptionValue(PlanTest.TEST_CASES_DIR_OPTION,
+                    buildHelper.getTestCasesDir().getAbsolutePath());
+            mConfiguration.injectOptionValue(PlanTest.TEST_PLANS_DIR_OPTION,
+                    buildHelper.getTestPlansDir().getAbsolutePath());
+            installCtsPrereqs(device, buildHelper);
+        } catch (FileNotFoundException e) {
+            throw new TargetSetupError("Invalid CTS installation", e);
+        } catch (ConfigurationException e) {
+            throw new TargetSetupError("Failed to set repository directory options", e);
+        }
+    }
+
+    /**
+     * Installs an apkFile on device.
+     *
+     * @param device the {@link ITestDevice}
+     * @param apkFile the apk {@link File}
+     * @throws DeviceNotAvailableException
+     * @throws TargetSetupError if apk cannot be installed successfully
+     */
+    void installApk(ITestDevice device, File apkFile)
+            throws DeviceNotAvailableException, TargetSetupError {
+        String errorCode = device.installPackage(apkFile, true);
+        if (errorCode != null) {
+            // TODO: retry ?
+            throw new TargetSetupError(String.format(
+                    "Failed to install %s on device %s. Reason: %s", apkFile.getName(),
+                    device.getSerialNumber(), errorCode));
+        }
+    }
+
+    /**
+     * Install pre-requisite apks for running tests
+     *
+     * @throws TargetSetupError if the pre-requisite apks fail to install
+     * @throws DeviceNotAvailableException
+     * @throws FileNotFoundException
+     */
+    private void installCtsPrereqs(ITestDevice device, CtsBuildHelper ctsBuild)
+            throws DeviceNotAvailableException, TargetSetupError, FileNotFoundException {
+        installApk(device, ctsBuild.getTestApp(TEST_STUBS_APK));
+        installApk(device, ctsBuild.getTestApp(RUNNER_APK_NAME));
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java
new file mode 100644
index 0000000..f7d4ab1
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java
@@ -0,0 +1,42 @@
+/*
+ * 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.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * Interface for accessing test plan data.
+ */
+interface IPlanXmlParser {
+
+    /**
+     * Parse the test plan data from given stream.
+     *
+     * @param xmlStream the {@link InputStream} that contains the test plan xml.
+     */
+    public void parse(InputStream xmlStream) throws ParseException;
+
+    /**
+     * Gets the list of test uris parsed from the plan.
+     * <p/>
+     * Must be called after {@link IPlanXmlParser#parse(InputStream)}.
+     */
+    public Collection<String> getTestUris();
+}
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
new file mode 100644
index 0000000..18ea7cf
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
@@ -0,0 +1,36 @@
+/*
+ * 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.tradefed.testtype.IRemoteTest;
+
+import java.util.Collection;
+
+/**
+ * Interface for accessing tests from the CTS repository.
+ */
+interface ITestCaseRepo {
+
+    /**
+     * Gets a list of tests identified by given list of uris
+     *
+     * @param testUris the string uris
+     * @return a {@link Collection} of {@link IRemoteTest}
+     */
+    public Collection<IRemoteTest> getTests(Collection<String> testUris);
+
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java
new file mode 100644
index 0000000..70b8432
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/JarHostTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.JUnitToInvocationResultForwarder;
+import com.android.tradefed.testtype.AbstractRemoteTest;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.IRunUtil.IRunnableResult;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+/**
+ * A {@link IRemoteTest} that can run a set of JUnit tests from a jar.
+ */
+public class JarHostTest extends AbstractRemoteTest implements IDeviceTest {
+
+    private static final String LOG_TAG = "JarHostTest";
+
+    private ITestDevice mDevice;
+    private File mJarFile;
+    private Collection<TestIdentifier> mTests;
+    private long mTimeoutMs = 10 * 60 * 1000;
+    private String mRunName;
+    private String mTestAppPath;
+
+    /**
+     * Set the jar file to load tests from.
+     * @param jarFile
+     */
+    void setJarFile(File jarFile) {
+        mJarFile = jarFile;
+    }
+
+    /**
+     * Sets the collection of tests to run
+     * @param tests
+     */
+    void setTests(Collection<TestIdentifier> tests) {
+        mTests = tests;
+    }
+
+    /**
+     * Set the maximum time in ms each test should run.
+     * <p/>
+     * Tests that take longer than this amount will be failed with a {@link TestTimeoutException}
+     * as the cause.
+     *
+     * @param testTimeout
+     */
+    void setTimeout(long testTimeoutMs) {
+        mTimeoutMs = testTimeoutMs;
+    }
+
+    /**
+     * Set the run name to report to {@link ITestInvocationListener#testRunStarted(String, int)}
+     *
+     * @param runName
+     */
+    void setRunName(String runName) {
+        mRunName = runName;
+    }
+
+    /**
+     * Set the filesystem path to test app artifacts needed to run tests.
+     *
+     * @see {@link com.android.hosttest.DeviceTest#setTestAppPath(String)}
+     *
+     * @param testAppPath
+     */
+    void setTestAppPath(String testAppPath) {
+        mTestAppPath = testAppPath;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int countTestCases() {
+        if (mTests == null) {
+            throw new IllegalStateException();
+        }
+        return mTests.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+        checkFields();
+        Log.i(LOG_TAG, String.format("Running %s test package from jar, contains %d tests.",
+                mRunName, countTestCases()));
+        // create a junit listener to forward the JUnit test results to the
+        // {@link ITestInvocationListener}s
+        JUnitToInvocationResultForwarder resultForwarder =
+                new JUnitToInvocationResultForwarder(listeners);
+        TestResult junitResult = new TestResult();
+        junitResult.addListener(resultForwarder);
+        long startTime = System.currentTimeMillis();
+        reportRunStarted(listeners);
+        for (TestIdentifier testId : mTests) {
+            Test junitTest = loadTest(testId.getClassName(), testId.getTestName());
+            if (junitTest != null) {
+                runTest(testId, junitTest, junitResult);
+            }
+        }
+        reportRunEnded(System.currentTimeMillis() - startTime, listeners);
+    }
+
+    /**
+     * Report the start of the test run.
+     *
+     * @param listeners
+     */
+    private void reportRunStarted(List<ITestInvocationListener> listeners) {
+        for (ITestInvocationListener listener : listeners) {
+            listener.testRunStarted(mRunName, countTestCases());
+        }
+    }
+
+    /**
+     * Run test with timeout support.
+     */
+    private void runTest(TestIdentifier testId, final Test junitTest, final TestResult junitResult) {
+        if (junitTest instanceof IDeviceTest) {
+            ((IDeviceTest)junitTest).setDevice(getDevice());
+        } else if (junitTest instanceof com.android.hosttest.DeviceTest) {
+            // legacy check - see if test uses hosttestlib. This check should go away once
+            // all host tests are converted to use tradefed
+            com.android.hosttest.DeviceTest deviceTest = (com.android.hosttest.DeviceTest)junitTest;
+            deviceTest.setDevice(getDevice().getIDevice());
+            deviceTest.setTestAppPath(mTestAppPath);
+        }
+        CommandStatus status = RunUtil.getInstance().runTimed(mTimeoutMs, new IRunnableResult() {
+
+            @Override
+            public boolean run() throws Exception {
+                junitTest.run(junitResult);
+                return true;
+            }
+
+            @Override
+            public void cancel() {
+                // ignore
+            }
+        });
+        if (status.equals(CommandStatus.TIMED_OUT)) {
+            junitResult.addError(junitTest, new TestTimeoutException());
+            junitResult.endTest(junitTest);
+        }
+    }
+
+    /**
+     * Report the end of the test run.
+     *
+     * @param elapsedTime
+     * @param listeners
+     */
+    @SuppressWarnings("unchecked")
+    private void reportRunEnded(long elapsedTime, List<ITestInvocationListener> listeners) {
+        for (ITestInvocationListener listener : listeners) {
+            listener.testRunEnded(elapsedTime, Collections.EMPTY_MAP);
+        }
+    }
+
+    /**
+     * Load the test with given names from the jar.
+     *
+     * @param className
+     * @param testName
+     * @return the loaded {@link Test} or <code>null</code> if test could not be loaded.
+     */
+    private Test loadTest(String className, String testName) {
+        try {
+            URL urls[] = {mJarFile.getCanonicalFile().toURI().toURL()};
+            Class<?> testClass = loadClass(className, urls);
+
+            if (TestCase.class.isAssignableFrom(testClass)) {
+                TestCase testCase = (TestCase)testClass.newInstance();
+                testCase.setName(testName);
+                return testCase;
+            } else if (Test.class.isAssignableFrom(testClass)) {
+                Test test = (Test)testClass.newInstance();
+                return test;
+            } else {
+                Log.e(LOG_TAG, String.format("Class '%s' from jar '%s' is not a Test",
+                        className, mJarFile.getAbsolutePath()));
+            }
+        } catch (ClassNotFoundException e) {
+            reportLoadError(mJarFile, className, e);
+        } catch (IllegalAccessException e) {
+            reportLoadError(mJarFile, className, e);
+        } catch (IOException e) {
+            reportLoadError(mJarFile, className, e);
+        } catch (InstantiationException e) {
+            reportLoadError(mJarFile, className, e);
+        }
+        return null;
+    }
+
+    /**
+     * Loads a class from given URLs.
+     * <p/>
+     * Exposed so unit tests can mock
+     *
+     * @param className
+     * @param urls
+     * @return
+     * @throws ClassNotFoundException
+     */
+    Class<?> loadClass(String className, URL[] urls) throws ClassNotFoundException {
+        URLClassLoader cl = new URLClassLoader(urls);
+        Class<?> testClass = cl.loadClass(className);
+        return testClass;
+    }
+
+    private void reportLoadError(File jarFile, String className, Exception e) {
+        Log.e(LOG_TAG, String.format("Failed to load test class '%s' from jar '%s'",
+                className, jarFile.getAbsolutePath()));
+        Log.e(LOG_TAG, e);
+    }
+
+    /**
+     * Checks that all mandatory member fields has been set.
+     */
+    private void checkFields() {
+        if (mRunName == null) {
+            throw new IllegalArgumentException("run name has not been set");
+        }
+        if (mDevice == null) {
+            throw new IllegalArgumentException("Device has not been set");
+        }
+        if (mJarFile == null) {
+            throw new IllegalArgumentException("jar file has not been set");
+        }
+        if (mTests == null) {
+            throw new IllegalArgumentException("tests has not been set");
+        }
+    }
+}
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
new file mode 100644
index 0000000..212eb66
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.cts.tradefed.device.DeviceInfoCollector;
+import com.android.ddmlib.Log;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.AbstractRemoteTest;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.Test;
+
+/**
+ * A {@link Test} that runs all the tests in the CTS test plan with given name
+ */
+public class PlanTest extends AbstractRemoteTest implements IDeviceTest, IRemoteTest {
+
+    private static final String LOG_TAG = "PlanTest";
+
+    public static final String TEST_CASES_DIR_OPTION = "test-cases-path";
+    public static final String TEST_PLANS_DIR_OPTION = "test-plans-path";
+
+    private ITestDevice mDevice;
+
+    @Option(name = "plan", description = "the test plan to run")
+    private String mPlanName = null;
+
+    @Option(name = TEST_CASES_DIR_OPTION, description =
+        "file path to directory containing CTS test cases")
+    private File mTestCaseDir = null;
+
+    @Option(name = TEST_PLANS_DIR_OPTION, description =
+        "file path to directory containing CTS test plans")
+    private File mTestPlanDir = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * Set the test plan directory.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setTestPlanDir(File planDir) {
+        mTestPlanDir = planDir;
+    }
+
+    /**
+     * Set the test case directory.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setTestCaseDir(File testCaseDir) {
+        mTestCaseDir = testCaseDir;
+    }
+
+    /**
+     * Set the plan name to run.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setPlanName(String planName) {
+        mPlanName = planName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+        if (mPlanName == null) {
+            throw new IllegalArgumentException("missing --plan option");
+        }
+        if (getDevice() == null) {
+            throw new IllegalArgumentException("missing device");
+        }
+        if (mTestCaseDir == null) {
+            throw new IllegalArgumentException(String.format("missing %s option",
+                    TEST_CASES_DIR_OPTION));
+        }
+        if (mTestPlanDir == null) {
+            throw new IllegalArgumentException(String.format("missing %s", TEST_PLANS_DIR_OPTION));
+        }
+
+        Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
+
+        try {
+            String ctsPlanRelativePath = String.format("%s.xml", mPlanName);
+            File ctsPlanFile = new File(mTestPlanDir, ctsPlanRelativePath);
+            IPlanXmlParser parser = createXmlParser();
+            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());
+                }
+                test.run(listeners);
+            }
+        } catch (FileNotFoundException e) {
+            throw new IllegalArgumentException("failed to find CTS plan file", e);
+        } catch (ParseException e) {
+            throw new IllegalArgumentException("failed to parse CTS plan file", e);
+        }
+    }
+
+    /**
+     * Runs the device info collector instrumentation on device, and forwards it to test listeners
+     * as run metrics.
+     *
+     * @param listeners
+     * @throws DeviceNotAvailableException
+     */
+    private void collectDeviceInfo(ITestDevice device, File testApkDir,
+            List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+        DeviceInfoCollector.collectDeviceInfo(device, testApkDir, listeners);
+    }
+
+    /**
+     * Factory method for creating a {@link ITestCaseRepo}.
+     * <p/>
+     * Exposed for unit testing
+     */
+    ITestCaseRepo createTestCaseRepo() {
+        return new TestCaseRepo(mTestCaseDir);
+    }
+
+    /**
+     * Factory method for creating a {@link PlanXmlParser}.
+     * <p/>
+     * Exposed for unit testing
+     */
+    IPlanXmlParser createXmlParser() {
+        return new PlanXmlParser();
+    }
+
+    /**
+     * Factory method for creating a {@link InputStream} from a plan xml file.
+     * <p/>
+     * Exposed for unit testing
+     */
+    InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
+        return new BufferedInputStream(new FileInputStream(xmlFile));
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java
new file mode 100644
index 0000000..fd3dcb8
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java
@@ -0,0 +1,72 @@
+/*
+ * 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.tradefed.util.xml.AbstractXmlParser;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Parses a test plan xml file.
+ */
+class PlanXmlParser extends AbstractXmlParser implements IPlanXmlParser {
+
+    private Set<String> mUris;
+
+    /**
+     * SAX callback object. Handles parsing data from the xml tags.
+     */
+    private class EntryHandler extends DefaultHandler {
+
+        private static final String ENTRY_TAG = "Entry";
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            if (ENTRY_TAG.equals(localName)) {
+                final String entryUriValue = attributes.getValue("uri");
+                mUris.add(entryUriValue);
+            }
+        }
+    }
+
+    PlanXmlParser() {
+        // Uses a LinkedHashSet to have predictable iteration order
+        mUris = new LinkedHashSet<String>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<String> getTestUris() {
+        return mUris;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected DefaultHandler createXmlHandler() {
+        return new EntryHandler();
+    }
+}
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
new file mode 100644
index 0000000..8392762
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
@@ -0,0 +1,135 @@
+/*
+ * 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.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+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;
+
+/**
+ * Retrieves CTS test case definitions from the repository.
+ */
+class TestCaseRepo implements ITestCaseRepo {
+
+    private static final String LOG_TAG = "TestCaseRepo";
+
+    private File mTestCaseDir;
+
+    /** mapping of uri to test definition */
+    private Map<String, TestPackageDef> mTestMap;
+
+    /**
+     * Creates a {@link TestCaseRepo}, initialized from provided repo files
+     *
+     * @param testCaseDir directory containing all test case definition xml and build files
+     */
+    public TestCaseRepo(File testCaseDir) {
+        mTestCaseDir = testCaseDir;
+        mTestMap = new Hashtable<String, TestPackageDef>();
+        parse(mTestCaseDir);
+    }
+
+    /**
+     * Builds mTestMap based on directory contents
+     */
+    private void parse(File dir) {
+        File[] xmlFiles = dir.listFiles(new XmlFilter());
+        for (File xmlFile : xmlFiles) {
+            parseTestFromXml(xmlFile);
+        }
+    }
+
+    /**
+     * @param xmlFile
+     * @throws ParseException
+     */
+    private void parseTestFromXml(File xmlFile)  {
+        TestPackageXmlParser parser = new TestPackageXmlParser();
+        try {
+            parser.parse(createStreamFromFile(xmlFile));
+            TestPackageDef def = parser.getTestPackageDef();
+            if (def != null) {
+                mTestMap.put(def.getUri(), def);
+            } else {
+                Log.w(LOG_TAG, String.format("Could not find test package info in xml file %s",
+                        xmlFile.getAbsolutePath()));
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(LOG_TAG, String.format("Could not find test case xml file %s",
+                    xmlFile.getAbsolutePath()));
+            Log.e(LOG_TAG, e);
+        } catch (ParseException e) {
+            Log.e(LOG_TAG, String.format("Failed to parse test case xml file %s",
+                    xmlFile.getAbsolutePath()));
+            Log.e(LOG_TAG, e);
+        }
+    }
+
+    /**
+     * Helper method to create a stream to read data from given file
+     * <p/>
+     * Exposed for unit testing
+     *
+     * @param xmlFile
+     * @return
+     *
+     */
+    InputStream createStreamFromFile(File xmlFile) throws FileNotFoundException {
+        return new BufferedInputStream(new FileInputStream(xmlFile));
+    }
+
+    private static class XmlFilter implements FilenameFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean accept(File dir, String name) {
+            return name.endsWith(".xml");
+        }
+    }
+
+    /**
+     * {@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;
+    }
+}
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
new file mode 100644
index 0000000..a75295e
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -0,0 +1,176 @@
+/*
+ * 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.testtype.IRemoteTest;
+import com.android.tradefed.testtype.InstrumentationTest;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Container for CTS test info.
+ * <p/>
+ * Knows how to translate this info into a runnable {@link IRemoteTest}.
+ */
+public class TestPackageDef {
+
+    private static final String LOG_TAG = "TestPackageDef";
+
+    private String mUri = null;
+    private String mAppNameSpace = null;
+    private String mName = null;
+    private String mRunner = null;
+    private boolean mIsHostSideTest = false;
+    private String mJarPath = null;
+    private boolean mIsSignatureTest = false;
+    private boolean mIsReferenceAppTest = false;
+
+    private Collection<TestIdentifier> mTests = new ArrayList<TestIdentifier>();
+
+    void setUri(String uri) {
+        mUri = uri;
+    }
+
+    /**
+     * Get the unique URI of the test package.
+     * @return the {@link String} uri
+     */
+    public String getUri() {
+        return mUri;
+    }
+
+    void setAppNameSpace(String appNameSpace) {
+        mAppNameSpace = appNameSpace;
+    }
+
+    String getAppNameSpace() {
+        return mAppNameSpace;
+    }
+
+    void setName(String name) {
+        mName = name;
+    }
+
+    String getName() {
+        return mName;
+    }
+
+    void setRunner(String runnerName) {
+        mRunner = runnerName;
+    }
+
+    String getRunner() {
+        return mRunner;
+    }
+
+    void setIsHostSideTest(boolean hostSideTest) {
+        mIsHostSideTest = hostSideTest;
+
+    }
+
+    boolean isHostSideTest() {
+        return mIsHostSideTest;
+    }
+
+    void setJarPath(String jarPath) {
+        mJarPath = jarPath;
+    }
+
+    String getJarPath() {
+        return mJarPath;
+    }
+
+    void setIsSignatureCheck(boolean isSignatureCheckTest) {
+        mIsSignatureTest = isSignatureCheckTest;
+    }
+
+    boolean isSignatureCheck() {
+        return mIsSignatureTest;
+    }
+
+    void setIsReferenceApp(boolean isReferenceApp) {
+        mIsReferenceAppTest = isReferenceApp;
+    }
+
+    boolean isReferenceApp() {
+        return mIsReferenceAppTest;
+    }
+
+    /**
+     * 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) {
+        if (mIsHostSideTest) {
+            Log.d(LOG_TAG, String.format("Creating host test for %s", mName));
+            JarHostTest hostTest = new JarHostTest();
+            hostTest.setRunName(mName);
+            hostTest.setJarFile(new File(testCaseDir, mJarPath));
+            hostTest.setTestAppPath(testCaseDir.getAbsolutePath());
+            hostTest.setTests(mTests);
+            return hostTest;
+        } else if (mIsSignatureTest) {
+            // TODO: implement this
+            Log.w(LOG_TAG, String.format("Skipping currently unsupported signature test %s",
+                    mName));
+            return null;
+        } else if (mIsReferenceAppTest) {
+            // TODO: implement this
+            Log.w(LOG_TAG, String.format("Skipping currently unsupported reference app test %s",
+                    mName));
+            return null;
+        } else {
+            Log.d(LOG_TAG, String.format("Creating instrumentation test for %s", mName));
+            InstrumentationTest instrTest = new InstrumentationTest();
+            instrTest.setPackageName(mAppNameSpace);
+            instrTest.setRunnerName(mRunner);
+            // mName means 'apk file name' for instrumentation tests
+            File apkFile = new File(testCaseDir, String.format("%s.apk", mName));
+            if (!apkFile.exists()) {
+                Log.w(LOG_TAG, String.format("Could not find apk file %s",
+                        apkFile.getAbsolutePath()));
+                return null;
+            }
+            instrTest.setInstallFile(apkFile);
+            return instrTest;
+        }
+    }
+
+    /**
+     * Add a {@link TestDef} to the list of tests in this package.
+     *
+     * @param testdef
+     */
+    void addTest(TestIdentifier testDef) {
+        mTests.add(testDef);
+    }
+
+    /**
+     * Get the collection of tests in this test package.
+     * <p/>
+     * Exposed for unit testing.
+     */
+    Collection<TestIdentifier> getTests() {
+        return mTests;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
new file mode 100644
index 0000000..9bf6968
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
@@ -0,0 +1,159 @@
+/*
+ * 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.util.xml.AbstractXmlParser;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.Iterator;
+import java.util.Stack;
+
+/**
+ * Parser for CTS test case XML.
+ * <p/>
+ * Dumb parser that just retrieves data from in the test case xml and stuff it into a
+ * {@link TestPackageDef}. Currently performs limited error checking.
+ */
+public class TestPackageXmlParser extends AbstractXmlParser {
+
+    private static final String LOG_TAG = "TestPackageXmlParser";
+
+    private TestPackageDef mPackageDef;
+
+    /**
+     * SAX callback object. Handles parsing data from the xml tags.
+     * <p/>
+     * Expected structure:
+     * <TestPackage>
+     *     <TestSuite ...>
+     *        <TestCase>
+     *           <Test>
+     */
+    private class TestPackageHandler extends DefaultHandler {
+
+        private static final String TEST_PACKAGE_TAG = "TestPackage";
+        private static final String TEST_SUITE_TAG = "TestSuite";
+        private static final String TEST_CASE_TAG = "TestCase";
+        private static final String TEST_TAG = "Test";
+
+        // holds current class name segments
+        private Stack<String> mClassNameStack = new Stack<String>();
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            if (TEST_PACKAGE_TAG.equals(localName)) {
+                // appPackageName is used as the uri
+                final String entryUriValue = attributes.getValue("appPackageName");
+                final String testPackageNameSpace = attributes.getValue("appNameSpace");
+                final String packageName = attributes.getValue("name");
+                final String runnerName = attributes.getValue("runner");
+                final String hostSideTest = attributes.getValue("hostSideOnly");
+                final String jarPath = attributes.getValue("jarPath");
+                final String signatureCheck = attributes.getValue("signatureCheck");
+                final String referenceApp = attributes.getValue("referenceAppTest");
+
+                mPackageDef = new TestPackageDef();
+                mPackageDef.setUri(entryUriValue);
+                mPackageDef.setAppNameSpace(testPackageNameSpace);
+                mPackageDef.setName(packageName);
+                mPackageDef.setRunner(runnerName);
+                mPackageDef.setIsHostSideTest(parseBoolean(hostSideTest));
+                mPackageDef.setJarPath(jarPath);
+                mPackageDef.setIsSignatureCheck(parseBoolean(signatureCheck));
+                mPackageDef.setIsReferenceApp(parseBoolean(referenceApp));
+
+                // reset the class name
+                mClassNameStack = new Stack<String>();
+            } else if (TEST_SUITE_TAG.equals(localName)) {
+                String packageSegment = attributes.getValue("name");
+                if (packageSegment != null) {
+                    mClassNameStack.push(packageSegment);
+                } else {
+                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
+                            TEST_SUITE_TAG));
+                }
+            } else if (TEST_CASE_TAG.equals(localName)) {
+                String classSegment = attributes.getValue("name");
+                if (classSegment != null) {
+                    mClassNameStack.push(classSegment);
+                } else {
+                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
+                            TEST_CASE_TAG));
+                }
+            } else if (TEST_TAG.equals(localName)) {
+                String methodName = attributes.getValue("name");
+                if (mPackageDef == null) {
+                    Log.e(LOG_TAG, String.format(
+                            "Invalid XML: encountered a '%s' tag not enclosed within a '%s' tag",
+                            TEST_TAG, TEST_PACKAGE_TAG));
+                } else if (methodName == null) {
+                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
+                            TEST_TAG));
+                } else {
+                    // build class name from package segments
+                    StringBuilder classNameBuilder = new StringBuilder();
+                    for (Iterator<String> iter = mClassNameStack.iterator(); iter.hasNext(); ) {
+                        classNameBuilder.append(iter.next());
+                        if (iter.hasNext()) {
+                            classNameBuilder.append(".");
+                        }
+                    }
+                    TestIdentifier testdef = new TestIdentifier(classNameBuilder.toString(),
+                            methodName);
+                    mPackageDef.addTest(testdef);
+                }
+            }
+
+        }
+
+        @Override
+        public void endElement (String uri, String localName, String qName) throws SAXException {
+            if (TEST_SUITE_TAG.equals(localName) || TEST_CASE_TAG.equals(localName)) {
+                mClassNameStack.pop();
+            }
+        }
+
+        /**
+         * Parse a boolean attribute value
+         */
+        private boolean parseBoolean(final String stringValue) {
+            return stringValue != null &&
+                    Boolean.parseBoolean(stringValue);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected DefaultHandler createXmlHandler() {
+        return new TestPackageHandler();
+    }
+
+    /**
+     * @returns the {@link TestPackageDef} containing data parsed from xml or <code>null</code> if
+     *          xml did not contain the correct information.
+     */
+    public TestPackageDef getTestPackageDef() {
+        return mPackageDef;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestTimeoutException.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestTimeoutException.java
new file mode 100644
index 0000000..c436658
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestTimeoutException.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * An exception that indicates a test has timed out.
+ * TODO: consider moving this to tradefed proper
+ */
+public class TestTimeoutException extends Exception {
+
+    private static final long serialVersionUID = 941691916057121118L;
+
+}
diff --git a/tools/tradefed-host/tests/.classpath b/tools/tradefed-host/tests/.classpath
new file mode 100644
index 0000000..b130096
--- /dev/null
+++ b/tools/tradefed-host/tests/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/easymock"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/cts-tradefed-host"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/tradefed-host/tests/.project b/tools/tradefed-host/tests/.project
new file mode 100644
index 0000000..1c385d8
--- /dev/null
+++ b/tools/tradefed-host/tests/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>cts-tradefed-host-tests</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tools/tradefed-host/tests/Android.mk b/tools/tradefed-host/tests/Android.mk
new file mode 100644
index 0000000..d3b94cd
--- /dev/null
+++ b/tools/tradefed-host/tests/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this lib.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := cts-tradefed-tests
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := ddmlib-prebuilt tradefed-prebuilt cts-tradefed
+LOCAL_STATIC_JAVA_LIBRARIES := easymock
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java
new file mode 100644
index 0000000..54f8f98
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.device;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.targetsetup.BuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Functional test for {@link DeviceInfoCollector}.
+ * <p/>
+ * TODO: this test assumes the TestDeviceSetup apk is located in the "java.io.tmpdir"
+ */
+public class DeviceInfoCollectorFuncTest extends DeviceTestCase {
+
+    public void testCollectDeviceInfo() throws DeviceNotAvailableException {
+        CollectingTestListener testListener = new CollectingTestListener();
+
+        testListener.invocationStarted(new BuildInfo());
+        List<ITestInvocationListener> listeners = new ArrayList<ITestInvocationListener>(1);
+        listeners.add(testListener);
+        DeviceInfoCollector.collectDeviceInfo(getDevice(), new File(
+                System.getProperty("java.io.tmpdir")),
+                listeners);
+        assertNotNull(testListener.getCurrentRunResults());
+        assertTrue(testListener.getCurrentRunResults().getRunMetrics().size() > 0);
+        for (Map.Entry<String, String> metricEntry : testListener.getCurrentRunResults().getRunMetrics().entrySet()) {
+            System.out.println(String.format("%s=%s", metricEntry.getKey(), metricEntry.getValue()));
+        }
+        testListener.invocationEnded(0);
+    }
+}
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
new file mode 100644
index 0000000..113496c
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.result;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.ITestRunListener.TestFailure;
+import com.android.tradefed.result.XmlResultReporter;
+import com.android.tradefed.targetsetup.BuildInfo;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link XmlResultReporter}.
+ */
+public class CtsXmlResultReporterTest extends TestCase {
+
+    private CtsXmlResultReporter mResultReporter;
+    private ByteArrayOutputStream mOutputStream;
+    private File mReportDir;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mOutputStream = new ByteArrayOutputStream();
+        mResultReporter = new CtsXmlResultReporter() {
+            @Override
+            OutputStream createOutputResultStream(File reportDir) throws IOException {
+                return mOutputStream;
+            }
+
+            @Override
+            String getTimestamp() {
+                return "ignore";
+            }
+        };
+        // TODO: use mock file dir instead
+        mReportDir = FileUtil.createTempDir("foo");
+        mResultReporter.setReportDir(mReportDir);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mReportDir != null) {
+            FileUtil.recursiveDelete(mReportDir);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * A simple test to ensure expected output is generated for test run with no tests.
+     */
+    public void testEmptyGeneration() {
+        final String expectedOutput = "<?xml version='1.0' encoding='UTF-8' standalone='no' ?>" +
+            "<?xml-stylesheet type=\"text/xsl\" href=\"cts_result.xsl\"?>" +
+            "<TestResult testPlan=\"unknown\" profile=\"unknown\" starttime=\"ignore\" endtime=\"ignore\" version=\"2.0\"> " +
+            "<Summary failed=\"0\" notExecuted=\"0\" timeout=\"0\" omitted=\"0\" pass=\"0\" total=\"0\" />" +
+            "</TestResult>";
+        mResultReporter.invocationStarted(new BuildInfo(1, "test", "test"));
+        mResultReporter.invocationEnded(1);
+        assertEquals(expectedOutput, getOutput());
+    }
+
+    /**
+     * A simple test to ensure expected output is generated for test run with a single passed test.
+     */
+    public void testSinglePass() {
+        Map<String, String> emptyMap = Collections.emptyMap();
+        final TestIdentifier testId = new TestIdentifier("com.foo.FooTest", "testFoo");
+        mResultReporter.invocationStarted(new BuildInfo());
+        mResultReporter.testRunStarted("run", 1);
+        mResultReporter.testStarted(testId);
+        mResultReporter.testEnded(testId, emptyMap);
+        mResultReporter.testRunEnded(3000, emptyMap);
+        mResultReporter.invocationEnded(1);
+        String output =  getOutput();
+        // TODO: consider doing xml based compare
+        assertTrue(output.contains(
+                "<Summary failed=\"0\" notExecuted=\"0\" timeout=\"0\" omitted=\"0\" pass=\"1\" total=\"1\" />"));
+        assertTrue(output.contains("<TestPackage name=\"run\" runTime=\"3s\" digest=\"\" " +
+                "failed=\"0\" notExecuted=\"0\" timeout=\"0\" omitted=\"0\" pass=\"1\" total=\"1\">"));
+        assertTrue(output.contains(String.format("<TestCase name=\"%s\">", testId.getClassName())));
+
+        final String testCaseTag = String.format(
+                "<Test name=\"%s\" result=\"pass\" />", testId.getTestName());
+        assertTrue(output.contains(testCaseTag));
+    }
+
+    /**
+     * A simple test to ensure expected output is generated for test run with a single failed test.
+     */
+    public void testSingleFail() {
+        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.testRunStarted("run", 1);
+        mResultReporter.testStarted(testId);
+        mResultReporter.testFailed(TestFailure.FAILURE, testId, trace);
+        mResultReporter.testEnded(testId, emptyMap);
+        mResultReporter.testRunEnded(3, emptyMap);
+        mResultReporter.invocationEnded(1);
+        String output =  getOutput();
+        System.out.print(getOutput());
+        // TODO: consider doing xml based compare
+        assertTrue(output.contains(
+                "<Summary failed=\"1\" notExecuted=\"0\" timeout=\"0\" omitted=\"0\" pass=\"0\" total=\"1\" />"));
+        final String failureTag =
+                "<FailedScene message=\"this is a trace\">this is a tracemore trace";
+        assertTrue(output.contains(failureTag));
+    }
+
+    /**
+     * Gets the output produced, stripping it of extraneous whitespace characters.
+     */
+    private String getOutput() {
+        String output = mOutputStream.toString();
+        // ignore newlines and tabs whitespace
+        output = output.replaceAll("[\\r\\n\\t]", "");
+        // replace two ws chars with one
+        return output.replaceAll("  ", " ");
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/CtsSetupTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/CtsSetupTest.java
new file mode 100644
index 0000000..932f3dc
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/CtsSetupTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.targetsetup;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.targetsetup.BuildError;
+import com.android.tradefed.targetsetup.IBuildInfo;
+import com.android.tradefed.targetsetup.IFolderBuildInfo;
+import com.android.tradefed.targetsetup.TargetSetupError;
+
+import org.easymock.EasyMock;
+
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CtsSetup}.
+ */
+public class CtsSetupTest extends TestCase {
+
+    private static final String LOG_TAG = "CtsSetupTest";
+
+    private CtsSetup mSetup;
+    private ITestDevice mMockDevice;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSetup = new CtsSetup() {
+            @Override
+            CtsBuildHelper createBuildHelper(File rootDir) {
+                try {
+                    return StubCtsBuildHelper.createStubHelper();
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, e);
+                    fail("failed to create stub helper");
+                    return null;
+                }
+            }
+        };
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+    }
+
+    /**
+     * Test {@link CtsSetup#setUp(ITestDevice, IBuildInfo)} when provided buildInfo is the incorrect
+     * type
+     */
+    public void testSetUp_wrongBuildInfo() throws TargetSetupError, BuildError,
+            DeviceNotAvailableException {
+        try {
+            mSetup.setUp(mMockDevice, EasyMock.createMock(IBuildInfo.class));
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test normal case for {@link CtsSetup#setUp(ITestDevice, IBuildInfo)}
+     */
+    public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException {
+        IFolderBuildInfo ctsBuild = EasyMock.createMock(IFolderBuildInfo.class);
+        EasyMock.expect(ctsBuild.getRootDir()).andReturn(
+                new File("tmp")).anyTimes();
+        EasyMock.expect(ctsBuild.getBuildId()).andStubReturn(0);
+        EasyMock.expect(
+                mMockDevice.installPackage((File)EasyMock.anyObject(), EasyMock.anyBoolean()))
+                .andReturn(null)
+                .anyTimes();
+        EasyMock.replay(ctsBuild, mMockDevice);
+        mSetup.setUp(mMockDevice, ctsBuild);
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java
new file mode 100644
index 0000000..53a79be
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.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.targetsetup;
+
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Stub implementation of CtsBuildHelper that returns empty files for all methods
+ */
+public class StubCtsBuildHelper extends CtsBuildHelper {
+
+    public static StubCtsBuildHelper createStubHelper() throws IOException {
+        File tmpFolder= FileUtil.createTempDir("ctstmp");
+        File ctsinstall = new File(tmpFolder, CtsBuildHelper.CTS_DIR_NAME);
+        ctsinstall.mkdirs();
+        return new StubCtsBuildHelper(tmpFolder);
+    }
+
+    private StubCtsBuildHelper(File rootDir) throws FileNotFoundException {
+        super(rootDir);
+    }
+
+    @Override
+    public File getTestApp(String appFileName) throws FileNotFoundException {
+        return new File("tmp");
+    }
+
+    @Override
+    public File getTestPlansDir() throws FileNotFoundException {
+        return new File("tmp");
+    }
+
+    @Override
+    public File getTestCasesDir() {
+        return new File("tmp");
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/JarHostTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/JarHostTestTest.java
new file mode 100644
index 0000000..fcd9563
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/JarHostTestTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.device.DeviceNotAvailableException;
+import com.android.tradefed.result.ITestInvocationListener;
+
+import org.easymock.EasyMock;
+
+import java.net.URL;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link JarHostTest}.
+ */
+public class JarHostTestTest extends TestCase {
+
+    private static final String RUN_NAME = "run";
+    private JarHostTest mJarTest;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mJarTest = new JarHostTest() {
+            // mock out the loading from jar
+            @Override
+            Class<?> loadClass(String className, URL[] urls) throws ClassNotFoundException {
+                return MockTest.class;
+            }
+        };
+    }
+
+    public static class MockTest extends TestCase {
+        public MockTest(String name) {
+            super(name);
+        }
+
+        public void testFoo() {
+        }
+    }
+
+    /**
+     * Test normal case for
+     * {@link JarHostTest#run(com.android.tradefed.result.ITestInvocationListener)}.
+     */
+    @SuppressWarnings("unchecked")
+    public void testRun() throws DeviceNotAvailableException {
+        ITestInvocationListener listener = EasyMock.createMock(ITestInvocationListener.class);
+        TestIdentifier expectedTest = new TestIdentifier(MockTest.class.getName(), "testFoo");
+
+        listener.testRunStarted(RUN_NAME, 1);
+        listener.testStarted(expectedTest);
+        listener.testEnded(expectedTest, Collections.EMPTY_MAP);
+        listener.testRunEnded(EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
+
+        EasyMock.replay(listener);
+
+        mJarTest.setRunName(RUN_NAME);
+        mJarTest.run(listener);
+    }
+
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java
new file mode 100644
index 0000000..1c2b4b6
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import org.easymock.EasyMock;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link PlanTest}.
+ */
+public class PlanTestTest extends TestCase {
+
+    /** the test fixture under test, with all external dependencies mocked out */
+    private PlanTest mPlanTest;
+    private ITestCaseRepo mMockRepo;
+    private IPlanXmlParser mMockPlanParser;
+    private ITestDevice mMockDevice;
+    private ITestInvocationListener mMockListener;
+
+    private static final String PLAN_NAME = "CTS";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mMockRepo = EasyMock.createMock(ITestCaseRepo.class);
+        mMockPlanParser = EasyMock.createMock(IPlanXmlParser.class);
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockListener = EasyMock.createNiceMock(ITestInvocationListener.class);
+
+        mPlanTest = new PlanTest() {
+            @Override
+            ITestCaseRepo createTestCaseRepo() {
+                return mMockRepo;
+            }
+
+            @Override
+            IPlanXmlParser createXmlParser() {
+                return mMockPlanParser;
+            }
+
+            @Override
+            InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
+                // return empty stream, not used
+                return new ByteArrayInputStream(new byte[0]);
+            }
+        };
+        mPlanTest.setDevice(mMockDevice);
+        // not used, but needs to be non-null
+        mPlanTest.setTestCaseDir(new File("tmp"));
+        mPlanTest.setTestPlanDir(new File("tmp"));
+        mPlanTest.setPlanName(PLAN_NAME);
+    }
+
+    /**
+     * Test normal case {@link PlanTest#run(java.util.List)}.
+     * <p/>
+     * Not that interesting of a test in its current form, but sets the stage for testing more
+     * complicated scenarios.
+     */
+    @SuppressWarnings("unchecked")
+    public void testRun() throws DeviceNotAvailableException, ParseException {
+        // expect
+        mMockPlanParser.parse((InputStream)EasyMock.anyObject());
+        Collection<String> uris = new ArrayList<String>(1);
+        uris.add("test-uri");
+        EasyMock.expect(mMockPlanParser.getTestUris()).andReturn(uris);
+
+        IRemoteTest mockTest = EasyMock.createMock(IRemoteTest.class);
+        Collection<IRemoteTest> tests = new ArrayList<IRemoteTest>(1);
+        tests.add(mockTest);
+        EasyMock.expect(mMockRepo.getTests(uris)).andReturn(tests);
+
+        // expect
+        mockTest.run((List<ITestInvocationListener>)EasyMock.anyObject());
+
+        replayMocks();
+        EasyMock.replay(mockTest);
+        mPlanTest.run(mMockListener);
+        verifyMocks();
+    }
+
+    private void replayMocks() {
+        EasyMock.replay(mMockRepo, mMockPlanParser, mMockDevice, mMockListener);
+    }
+
+    private void verifyMocks() {
+        EasyMock.verify(mMockRepo, mMockPlanParser, mMockDevice, mMockListener);
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java
new file mode 100644
index 0000000..803b230
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link PlanXmlParser}.
+ */
+public class PlanXmlParserTest extends TestCase {
+
+    private static final String TEST_URI1 = "foo";
+    private static final String TEST_URI2 = "foo2";
+
+    static final String TEST_DATA =
+        "<TestPlan version=\"1.0\">" +
+            String.format("<Entry uri=\"%s\" />", TEST_URI1) +
+            String.format("<Entry uri=\"%s\" />", TEST_URI2) +
+        "</TestPlan>";
+
+    /**
+     * Simple test for parsing a plan containing two uris
+     */
+    public void testParse() throws ParseException  {
+        PlanXmlParser parser = new PlanXmlParser();
+        parser.parse(getStringAsStream(TEST_DATA));
+        assertEquals(2, parser.getTestUris().size());
+        Iterator<String> iter = parser.getTestUris().iterator();
+        // assert uris in order
+        assertEquals(TEST_URI1, iter.next());
+        assertEquals(TEST_URI2, iter.next());
+    }
+
+    private InputStream getStringAsStream(String input) {
+        return new ByteArrayInputStream(input.getBytes());
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java
new file mode 100644
index 0000000..b24c0e3
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageXmlParserTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestPackageXmlParser}.
+ */
+public class TestPackageXmlParserTest extends TestCase {
+
+    private static String INSTR_TEST_DATA =
+        "<TestPackage AndroidFramework=\"Android 1.0\" appNameSpace=\"com.example\" " +
+        "appPackageName=\"android.example\" name=\"CtsExampleTestCases\" " +
+        "runner=\"android.test.InstrumentationTestRunner\" version=\"1.0\">" +
+        "</TestPackage>";
+
+    private static String HOST_TEST_DATA =
+        "<TestPackage hostSideOnly=\"true\" >\n" +
+        "    <TestSuite name=\"com\" >\n" +
+        "        <TestSuite name=\"example\" >\n" +
+        "            <TestCase name=\"ExampleTest\" >\n" +
+        "                <Test name=\"testFoo\" />\n" +
+        "                <Test name=\"testFoo2\" />\n" +
+        "            </TestCase>\n" +
+        "        </TestSuite>\n" +
+        "        <TestSuite name=\"example2\" >\n" +
+        "            <TestCase name=\"Example2Test\" >\n" +
+        "                <Test name=\"testFoo\" />\n" +
+        "            </TestCase>\n" +
+        "        </TestSuite>\n" +
+        "    </TestSuite>\n" +
+        "</TestPackage>";
+
+    private static String BAD_HOST_TEST_DATA =
+        "<TestPackage hostSideOnly=\"blah\" >" +
+        "</TestPackage>";
+
+    private static String NO_TEST_DATA =
+        "<invalid />";
+
+    /**
+     * Test parsing test case xml containing an instrumentation test definition.
+     */
+    public void testParse_instrPackage() throws ParseException  {
+        TestPackageXmlParser parser = new TestPackageXmlParser();
+        parser.parse(getStringAsStream(INSTR_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertEquals("com.example", def.getAppNameSpace());
+        assertEquals("android.example", def.getUri());
+        assertEquals("android.test.InstrumentationTestRunner", def.getRunner());
+    }
+
+    /**
+     * Test parsing test case xml containing an host test attribute and test data.
+     */
+    public void testParse_hostTest() throws ParseException  {
+        TestPackageXmlParser parser = new TestPackageXmlParser();
+        parser.parse(getStringAsStream(HOST_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertTrue(def.isHostSideTest());
+        assertEquals(3, def.getTests().size());
+        Iterator<TestIdentifier> iterator = def.getTests().iterator();
+
+        TestIdentifier firstTest = iterator.next();
+        assertEquals("com.example.ExampleTest", firstTest.getClassName());
+        assertEquals("testFoo", firstTest.getTestName());
+
+        TestIdentifier secondTest = iterator.next();
+        assertEquals("com.example.ExampleTest", secondTest.getClassName());
+        assertEquals("testFoo2", secondTest.getTestName());
+
+        TestIdentifier thirdTest = iterator.next();
+        assertEquals("com.example2.Example2Test", thirdTest.getClassName());
+        assertEquals("testFoo", thirdTest.getTestName());
+    }
+
+    /**
+     * Test parsing test case xml containing an invalid host test attribute.
+     */
+    public void testParse_badHostTest() throws ParseException  {
+        TestPackageXmlParser parser = new TestPackageXmlParser();
+        parser.parse(getStringAsStream(BAD_HOST_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertFalse(def.isHostSideTest());
+    }
+
+    /**
+     * Test parsing a test case xml with no test package data.
+     */
+    public void testParse_noData() throws ParseException  {
+        TestPackageXmlParser parser = new TestPackageXmlParser();
+        parser.parse(getStringAsStream(NO_TEST_DATA));
+        assertNull(parser.getTestPackageDef());
+    }
+
+    private InputStream getStringAsStream(String input) {
+        return new ByteArrayInputStream(input.getBytes());
+    }
+}
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index 86d870e..7d4b6e0 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -94,9 +94,9 @@
     source_path = [
         'frameworks/base/core/java',            # android test classes
         'frameworks/base/test-runner/src',      # test runner
-        'dalvik/libcore/junit/src/main/java',   # junit classes
+        'libcore/junit/src/main/java',          # junit classes
         'development/tools/hosttestlib/src',    # hosttestlib TestCase extensions
-        'dalvik/libcore/dalvik/src/main/java',  # test annotations
+        'libcore/dalvik/src/main/java',         # test annotations
         'cts/libs/annotation/src',              # cts annotations
         'cts/tests/src',                        # cts test stubs
         source_root                             # the source for this package
diff --git a/tools/utils/startcts b/tools/utils/startcts
index af9d503..c648125 100755
--- a/tools/utils/startcts
+++ b/tools/utils/startcts
@@ -49,7 +49,7 @@
 checkDir ${CTS_ROOT} "Error: Cannot locate CTS in \"${CTS_DIR}\". Please check your configuration in $0"
 checkDir ${SDK_ROOT} "Error: Cannot locate SDK installation in \"${SDK_ROOT}\". Please check your configuration in $0"
 
-DDM_LIB=${SDK_ROOT}/tools/lib/ddmlib.jar
+DDM_LIB=${CTS_ROOT}/tools/ddmlib-prebuilt.jar
 CTS_LIB=${CTS_ROOT}/tools/cts.jar
 JUNIT_LIB=${CTS_ROOT}/tools/junit.jar
 HOSTTEST_LIB=${CTS_ROOT}/tools/hosttestlib.jar
diff --git a/tools/vm-tests/src/dot/junit/opcodes/invoke_super/Test_invoke_super.java b/tools/vm-tests/src/dot/junit/opcodes/invoke_super/Test_invoke_super.java
index a2fd2d8..6efac8b 100644
--- a/tools/vm-tests/src/dot/junit/opcodes/invoke_super/Test_invoke_super.java
+++ b/tools/vm-tests/src/dot/junit/opcodes/invoke_super/Test_invoke_super.java
@@ -292,6 +292,21 @@
     }
 
     /**
+     * @constraint n/a
+     * @title invoke-super shall be used to invoke private methods
+     */
+    public void testVFE16() {
+        //@uses dot.junit.opcodes.invoke_super.d.T_invoke_super_13
+        //@uses dot.junit.opcodes.invoke_super.d.TSuper
+         try {
+             Class.forName("dot.junit.opcodes.invoke_super.d.T_invoke_super_13");
+             fail("expected a verification exception");
+         } catch (Throwable t) {
+             DxUtil.checkVerifyException(t);
+         }
+    }
+
+    /**
      * @constraint A23
      * @title number of registers
      */
diff --git a/tools/vm-tests/src/dot/junit/opcodes/invoke_super_range/Test_invoke_super_range.java b/tools/vm-tests/src/dot/junit/opcodes/invoke_super_range/Test_invoke_super_range.java
index 7bee936..d65b8d4 100644
--- a/tools/vm-tests/src/dot/junit/opcodes/invoke_super_range/Test_invoke_super_range.java
+++ b/tools/vm-tests/src/dot/junit/opcodes/invoke_super_range/Test_invoke_super_range.java
@@ -292,6 +292,21 @@
     }
 
     /**
+     * @constraint n/a
+     * @title invoke-super/range shall be used to invoke private methods
+     */
+    public void testVFE16() {
+        //@uses dot.junit.opcodes.invoke_super_range.d.T_invoke_super_range_13
+        //@uses dot.junit.opcodes.invoke_super_range.d.TSuper
+         try {
+             Class.forName("dot.junit.opcodes.invoke_super_range.d.T_invoke_super_range_13");
+             fail("expected a verification exception");
+         } catch (Throwable t) {
+             DxUtil.checkVerifyException(t);
+         }
+    }
+
+    /**
      * @constraint A23
      * @title number of registers
      */
diff --git a/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual/Test_invoke_virtual.java b/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual/Test_invoke_virtual.java
index 70e9f9e..6e41ec1 100644
--- a/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual/Test_invoke_virtual.java
+++ b/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual/Test_invoke_virtual.java
@@ -286,6 +286,19 @@
     }
 
     /**
+     * @constraint n/a
+     * @title invoke-virtual shall be used to invoke private methods
+     */
+    public void testVFE16() {
+         try {
+             Class.forName("dot.junit.opcodes.invoke_virtual.d.T_invoke_virtual_13");
+             fail("expected a verification exception");
+         } catch (Throwable t) {
+             DxUtil.checkVerifyException(t);
+         }
+    }
+
+    /**
      * @constraint A23
      * @title number of registers
      */
diff --git a/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual_range/Test_invoke_virtual_range.java b/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual_range/Test_invoke_virtual_range.java
index 4f91ba8..2368dc3 100644
--- a/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual_range/Test_invoke_virtual_range.java
+++ b/tools/vm-tests/src/dot/junit/opcodes/invoke_virtual_range/Test_invoke_virtual_range.java
@@ -294,6 +294,19 @@
     }
 
     /**
+     * @constraint n/a
+     * @title invoke-virtual/range shall be used to invoke private methods
+     */
+    public void testVFE16() {
+         try {
+             Class.forName("dot.junit.opcodes.invoke_virtual_range.d.T_invoke_virtual_range_13");
+             fail("expected a verification exception");
+         } catch (Throwable t) {
+             DxUtil.checkVerifyException(t);
+         }
+    }
+
+    /**
      * @constraint A23
      * @title number of registers
      */