Merge "Updating the behaviour of accessibility text iterators - CTS" into jb-dev
diff --git a/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java b/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java
index 3670bd9..32cc3ed 100644
--- a/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java
+++ b/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java
@@ -117,6 +117,10 @@
             Log.e(TAG, "ignore exception", e);
         } finally {
             track.stop();
+            track.flush();
+            track.release();
+            mPlaybackThread.quitLoop(false);
+            mPlaybackThread = null;
         }
     }
 
@@ -195,18 +199,19 @@
             mRecord = null;
         }
         if (mRecordThread != null) {
-            mRecordThread.quitLoop();
+            mRecordThread.quitLoop(true);
             mRecordThread = null;
         }
         if (mPlayback != null) {
             if (mPlayback.getState() != AudioTrack.STATE_UNINITIALIZED) {
                 mPlayback.stop();
+                mPlayback.flush();
             }
             mPlayback.release();
             mPlayback = null;
         }
         if (mPlaybackThread != null) {
-            mPlaybackThread.quitLoop();
+            mPlaybackThread.quitLoop(true);
             mPlaybackThread = null;
         }
         mDataMap.clear();
@@ -241,12 +246,17 @@
             if (samplingRate != 44100) {
                 throw new ProtocolError("wrong rate");
             }
+            //FIXME cannot start playback again
             //TODO repeat
             //FIXME in MODE_STATIC, setNotificationMarkerPosition does not work with full length
             mPlaybackThread = new LoopThread(new Runnable() {
 
                 @Override
                 public void run() {
+                    if (mPlayback != null) {
+                        mPlayback.release();
+                        mPlayback = null;
+                    }
                     int type = (mode == 0) ? AudioManager.STREAM_VOICE_CALL :
                         AudioManager.STREAM_MUSIC;
                     mPlayback = new AudioTrack(type, samplingRate,
@@ -282,12 +292,14 @@
         Log.d(TAG, "stopPlayback");
         assertProtocol(len == 0, "wrong payload len");
         if (mPlayback != null) {
+            Log.d(TAG, "release AudioTrack");
             mPlayback.stop();
+            mPlayback.flush();
             mPlayback.release();
             mPlayback = null;
         }
         if (mPlaybackThread != null) {
-            mPlaybackThread.quitLoop();
+            mPlaybackThread.quitLoop(true);
             mPlaybackThread = null;
         }
         sendSimpleReplyHeader(CMD_STOP_PLAYBACK, PROTOCOL_OK);
@@ -381,7 +393,7 @@
             mRecord = null;
         }
         if (mRecordThread != null) {
-            mRecordThread.quitLoop();
+            mRecordThread.quitLoop(true);
             mRecordThread = null;
         }
         sendSimpleReplyHeader(CMD_STOP_RECORDING, PROTOCOL_OK);
@@ -421,17 +433,20 @@
             mLooper = Looper.myLooper();
             Log.d(TAG, "run runnable");
             super.run();
-            Log.d(TAG, "loop");
+            //Log.d(TAG, "loop");
             Looper.loop();
         }
         // should be called outside this thread
-        public void quitLoop() {
+        public void quitLoop(boolean wait) {
             mLooper.quit();
             try {
-                join();
+                if (wait) {
+                    join();
+                }
             } catch (InterruptedException e) {
                 // ignore
             }
+            Log.d(TAG, "quit thread");
         }
     }
 
diff --git a/suite/audio_quality/lib/include/task/TaskAll.h b/suite/audio_quality/lib/include/task/TaskAll.h
index 2f04fc6..3729ef8 100644
--- a/suite/audio_quality/lib/include/task/TaskAll.h
+++ b/suite/audio_quality/lib/include/task/TaskAll.h
@@ -30,5 +30,6 @@
 #include "TaskSound.h"
 #include "TaskSave.h"
 #include "TaskMessage.h"
+#include "TaskDownload.h"
 
 #endif // CTSAUDIO_TASKALL_H
diff --git a/suite/audio_quality/lib/include/task/TaskDownload.h b/suite/audio_quality/lib/include/task/TaskDownload.h
new file mode 100644
index 0000000..64aae45
--- /dev/null
+++ b/suite/audio_quality/lib/include/task/TaskDownload.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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 CTSAUDIO_TASKDOWNLOAD_H
+#define CTSAUDIO_TASKDOWNLOAD_H
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include "audio/Buffer.h"
+#include "TaskGeneric.h"
+
+
+class TaskDownload: public TaskGeneric {
+public:
+    TaskDownload();
+    virtual ~TaskDownload();
+    virtual TaskGeneric::ExecutionResult run();
+};
+
+
+#endif // CTSAUDIO_TASKDOWNLOAD_H
diff --git a/suite/audio_quality/lib/include/task/TaskGeneric.h b/suite/audio_quality/lib/include/task/TaskGeneric.h
index 6dc269e..fe5215d 100644
--- a/suite/audio_quality/lib/include/task/TaskGeneric.h
+++ b/suite/audio_quality/lib/include/task/TaskGeneric.h
@@ -43,7 +43,8 @@
         ETaskSound          = 9,
         ETaskSave           = 10,
         ETaskMessage        = 11,
-        ETaskInvalidLast    = 12,
+        ETaskDownload       = 12,
+        ETaskInvalidLast    = 13,
         //no ETaskInclude include does not involve any action.
     };
 
diff --git a/suite/audio_quality/lib/src/GenericFactory.cpp b/suite/audio_quality/lib/src/GenericFactory.cpp
index f615876..2402055 100644
--- a/suite/audio_quality/lib/src/GenericFactory.cpp
+++ b/suite/audio_quality/lib/src/GenericFactory.cpp
@@ -59,6 +59,9 @@
     case TaskGeneric::ETaskMessage:
         task = new TaskMessage();
         break;
+    case TaskGeneric::ETaskDownload:
+        task = new TaskDownload();
+        break;
     default:
         LOGE("GenericFactory::createTask unsupported type %d", type);
         return NULL;
diff --git a/suite/audio_quality/lib/src/audio/AudioProtocol.cpp b/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
index a046432..bf1ca22 100644
--- a/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
+++ b/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
@@ -111,7 +111,7 @@
     mBuffer[1] = htonl(20);
     mBuffer[2] = htonl(param.mId);
     mBuffer[3] = htonl(param.mSamplingF);
-    uint32_t mode = param.mStereo ? 1<<31 : 0;
+    uint32_t mode = param.mStereo ? 0x80000000 : 0;
     mode |= param.mMode;
     mBuffer[4] = htonl(mode);
     mBuffer[5] = htonl(param.mVolume);
@@ -132,7 +132,7 @@
     mBuffer[0] = htonl(ECmdStartRecording);
     mBuffer[1] = htonl(16);
     mBuffer[2] = htonl(param.mSamplingF);
-    uint32_t mode = param.mStereo ? 1<<31 : 0;
+    uint32_t mode = param.mStereo ? 0x80000000 : 0;
     mode |= param.mMode;
     mBuffer[3] = htonl(mode);
     mBuffer[4] = htonl(param.mVolume);
diff --git a/suite/audio_quality/lib/src/audio/RemoteAudio.cpp b/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
index 10ae14c..bb9474b 100644
--- a/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
+++ b/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
@@ -235,6 +235,7 @@
         LOGE("Buffer id %d not registered", id);
         return false;
     }
+    LOGD("RemoteAudio::startPlayback stereo %d mode %d", stereo, mode);
     handler->mActive = true;
     handler->getParam().mStereo = stereo;
     handler->getParam().mSamplingF = samplingF;
diff --git a/suite/audio_quality/lib/src/task/ModelBuilder.cpp b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
index 87373ca..6f34f5c 100644
--- a/suite/audio_quality/lib/src/task/ModelBuilder.cpp
+++ b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
@@ -30,7 +30,8 @@
 };
 static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
     { TaskGeneric::ETaskSound, false },
-    { TaskGeneric::ETaskProcess, false }
+    { TaskGeneric::ETaskProcess, false },
+    { TaskGeneric::ETaskDownload, false }
 };
 static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
     { TaskGeneric::ETaskSequential, true }
@@ -58,7 +59,8 @@
     { "output", TaskGeneric::ETaskOutput, NULL, 0 },
     { "sound", TaskGeneric::ETaskSound, NULL, 0 },
     { "save", TaskGeneric::ETaskSave, NULL, 0 },
-    { "message", TaskGeneric::ETaskMessage, NULL, 0 }
+    { "message", TaskGeneric::ETaskMessage, NULL, 0 },
+    { "download", TaskGeneric::ETaskDownload, NULL, 0 }
 };
 
 
diff --git a/suite/audio_quality/lib/src/task/TaskDownload.cpp b/suite/audio_quality/lib/src/task/TaskDownload.cpp
new file mode 100644
index 0000000..dacefc5
--- /dev/null
+++ b/suite/audio_quality/lib/src/task/TaskDownload.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 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 <UniquePtr.h>
+#include "Log.h"
+#include "audio/AudioSignalFactory.h"
+#include "audio/RemoteAudio.h"
+#include "StringUtil.h"
+#include "task/TaskCase.h"
+#include "task/TaskDownload.h"
+
+static const android::String8 STR_ID("id");
+
+TaskDownload::TaskDownload()
+    : TaskGeneric(TaskGeneric::ETaskDownload)
+{
+    const android::String8* list[] = {&STR_ID, NULL};
+    registerSupportedStringAttributes(list);
+}
+
+TaskDownload::~TaskDownload()
+{
+
+}
+
+TaskGeneric::ExecutionResult TaskDownload::run()
+{
+    android::String8 id;
+    if (!findStringAttribute(STR_ID, id)) {
+        LOGE("TaskDownload::run %s string not found", STR_ID.string());
+        return TaskGeneric::EResultError;
+    }
+
+    android::sp<Buffer> buffer = getTestCase()->findBuffer(id);
+    if (buffer.get() == NULL) {
+        LOGE("TaskDownload::run cannot find buffer %s", id.string());
+        return TaskGeneric::EResultError;
+    }
+    int downloadId;
+    if (!getTestCase()->getRemoteAudio()->downloadData(id, buffer, downloadId)) {
+        return TaskGeneric::EResultError;
+    }
+    LOGI("Downloaded buffer %s to DUT with id %d", id.string(), downloadId);
+    return TaskGeneric::EResultOK;
+}
+
+
+
diff --git a/suite/audio_quality/lib/src/task/TaskOutput.cpp b/suite/audio_quality/lib/src/task/TaskOutput.cpp
index ce3a7d8..be6f4fd 100644
--- a/suite/audio_quality/lib/src/task/TaskOutput.cpp
+++ b/suite/audio_quality/lib/src/task/TaskOutput.cpp
@@ -54,15 +54,13 @@
         LOGE("prepare failed");
         return TaskGeneric::EResultError;
     }
+    android::sp<Buffer> buffer = getTestCase()->findBuffer(mId);
+    if (buffer.get() == NULL) {
+        LOGE("cannot find buffer %s", mId.string());
+        return TaskGeneric::EResultError;
+    }
+    buffer->restart(); // reset to play from beginning
     if (localDevice) {
-        android::sp<Buffer> buffer;
-        buffer = getTestCase()->findBuffer(mId);
-        if (buffer.get() == NULL) {
-            LOGE("cannot find buffer %s", mId.string());
-            return TaskGeneric::EResultError;
-        }
-        buffer->restart(); // reset to play from beginning
-
         if (!hw->startPlaybackOrRecord(buffer)) {
             LOGE("play failed");
             return TaskGeneric::EResultError;
@@ -73,7 +71,7 @@
             return TaskGeneric::EResultError;
         }
         AudioRemotePlayback* remote = reinterpret_cast<AudioRemotePlayback*>(hw.get());
-        if (!remote->startPlaybackForRemoteData(id, false)) { // mono always
+        if (!remote->startPlaybackForRemoteData(id, buffer->isStereo())) {
             return TaskGeneric::EResultError;
         }
     }
diff --git a/suite/audio_quality/lib/src/task/TaskSound.cpp b/suite/audio_quality/lib/src/task/TaskSound.cpp
index f530203..8dd9b4a 100644
--- a/suite/audio_quality/lib/src/task/TaskSound.cpp
+++ b/suite/audio_quality/lib/src/task/TaskSound.cpp
@@ -51,7 +51,6 @@
 
 TaskGeneric::ExecutionResult TaskSound::run()
 {
-    //TODO : needs to support data generated from process
     android::String8 id;
     if (!findStringAttribute(STR_ID, id)) {
         LOGE("TaskSound::run %s string not found", STR_ID.string());
@@ -84,6 +83,7 @@
         buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS, amplitude,
                 AudioHardware::ESampleRate_44100, freq, samples, true);
     } else if (StringUtil::compare(tokens->at(0), "random") == 0) {
+        // TODO FIXME it does not seem to work well.
         if (tokens->size() != 3) {
             LOGE("Wrong number of parameters %d", tokens->size());
         }
diff --git a/suite/audio_quality/test_description/all.xml b/suite/audio_quality/test_description/all_playback.xml
similarity index 75%
copy from suite/audio_quality/test_description/all.xml
copy to suite/audio_quality/test_description/all_playback.xml
index d022230..cf5af6f 100644
--- a/suite/audio_quality/test_description/all.xml
+++ b/suite/audio_quality/test_description/all_playback.xml
@@ -15,8 +15,8 @@
      limitations under the License.
 -->
 
-<batch name="cts_audio_all" version="1.0" description="All tests">
-	<include file="host_speaker_calibration.xml" />
-	<include file="dut_recording_thd.xml" />
-	<include file="dut_recording_spectrum.xml" />
+<batch name="cts_audio_all_playback" version="1.0" description="All playback tests">
+	<include file="dut_speaker_calibration.xml" />
+	<include file="dut_playback_thd.xml" />
+	<include file="dut_playback_spectrum.xml" />
 </batch>
diff --git a/suite/audio_quality/test_description/all.xml b/suite/audio_quality/test_description/all_recording.xml
similarity index 90%
rename from suite/audio_quality/test_description/all.xml
rename to suite/audio_quality/test_description/all_recording.xml
index d022230..f1e78b9 100644
--- a/suite/audio_quality/test_description/all.xml
+++ b/suite/audio_quality/test_description/all_recording.xml
@@ -15,7 +15,7 @@
      limitations under the License.
 -->
 
-<batch name="cts_audio_all" version="1.0" description="All tests">
+<batch name="cts_audio_all_recording" version="1.0" description="All recording tests">
 	<include file="host_speaker_calibration.xml" />
 	<include file="dut_recording_thd.xml" />
 	<include file="dut_recording_spectrum.xml" />
diff --git a/suite/audio_quality/test_description/conf/detect_usb_audio.py b/suite/audio_quality/test_description/conf/detect_usb_audio.py
old mode 100755
new mode 100644
diff --git a/suite/audio_quality/test_description/dut_playback_spectrum.xml b/suite/audio_quality/test_description/dut_playback_spectrum.xml
new file mode 100644
index 0000000..d945ff6
--- /dev/null
+++ b/suite/audio_quality/test_description/dut_playback_spectrum.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_playback_spectrum" version="1.0" description="Check frequency spectrum for playback">
+	<setup>
+		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
+		<process method="script:gen_random" input="consti:10000,consti:4000,consti:44100,consti:20000" output="id:sound1" />
+		<download id="sound1" />
+	</setup>
+	<action>
+		<sequential repeat="1" index="i">
+			<input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
+			<output device="DUT" id="sound1" gain="100" mode="voice" sync="start" waitforcompletion="1" />
+		</sequential>
+		<sequential repeat="1" index="k">
+			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
+			<process method="script:check_spectrum" input="id:sound1,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:85.0,constf:150.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
+		</sequential>
+	</action>
+	<save file="sound1,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/dut_playback_thd.xml b/suite/audio_quality/test_description/dut_playback_thd.xml
new file mode 100644
index 0000000..819e17f
--- /dev/null
+++ b/suite/audio_quality/test_description/dut_playback_thd.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_playback_thd" version="1.0" description="Check THD in DUT's playback side">
+	<setup>
+		<!-- prepare sound source id: to be used in output, sine 1000Hz, 4000ms long -->
+		<sound id="sound1" type="sin:32000:1000:4000" preload="1" />
+	</setup>
+	<action>
+		<sequential repeat="1" index="i">
+			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="0" />
+			<sequential repeat="1" index="j">
+				<!-- dummy recording to compensate for possible playback latency -->
+				<input device="host" id="dummy" gain="100" time="1000" sync="complete" />
+				<input device="host" id="host_in_$j" gain="100" time="2000" sync="complete" />
+			</sequential>
+		</sequential>
+		<sequential repeat="1" index="k">
+			<!-- input: host record, signal frequency in Hz, THD for pass in percentile, output: THD calculated -->
+			<process method="script:playback_thd" input="id:host_in_$k,consti:1000,constf:1.0" output="val:thd_device_$k" />
+		</sequential>
+	</action>
+	<save file="host_in_.*" report="thd_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/dut_recording_thd.xml b/suite/audio_quality/test_description/dut_recording_thd.xml
index 49db0d2..4567ee2 100644
--- a/suite/audio_quality/test_description/dut_recording_thd.xml
+++ b/suite/audio_quality/test_description/dut_recording_thd.xml
@@ -25,12 +25,12 @@
 	<action>
 		<sequential repeat="1" index="i">
 			<output device="host" id="sound1" gain="100" sync="start" waitforcompletion="0" />
-			<sequential repeat="5" index="j">
+			<sequential repeat="2" index="j">
 				<input device="host" id="host_in_$j" gain="100" time="4000" sync="start" />
 				<input device="DUT" id="dut_in_$j" gain="100" time="2000" sync="start" />
 			</sequential>
 		</sequential>
-		<sequential repeat="5" index="k">
+		<sequential repeat="2" index="k">
 			<!-- input: host record, device record, signal frequency in Hz, THD for pass in percentile, output: THD calculated -->
 			<process method="script:recording_thd" input="id:host_in_$k,id:dut_in_$k,consti:1000,constf:1.0" output="val:thd_host_$k,val:thd_device_$k" />
 		</sequential>
diff --git a/suite/audio_quality/test_description/dut_speaker_calibration.xml b/suite/audio_quality/test_description/dut_speaker_calibration.xml
new file mode 100644
index 0000000..9cc655b
--- /dev/null
+++ b/suite/audio_quality/test_description/dut_speaker_calibration.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_speaker_calibration" version="1.0" description="Calibrate host recording gain">
+	<setup> <!-- 1 setup -->
+		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
+		<sound id="sound1" type="sin:32000:1000:5000" preload="1" />
+	</setup>
+
+	<action> <!-- 1 action -->
+		<!--  equivalent of for loop. all children will be completed before moving to the next 
+		      stage.repeat up to 100 times unless stopped by some condition -->
+		<sequential repeat="20" index="i">
+			<!--  sync start : execute only sync complete : execute + complete
+			      For sync start, complete will be called when the parent completes -->
+			<output device="DUT" id="sound1" gain="100" sync="start"/>
+			<sequential repeat="8" index="j">
+				<input device="host" id="host_in" gain="100" time="500" sync="complete" />
+				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
+				<process method="builtin:rms_mva" input="id:host_in,consti:3000,consti:8000" output="val:rms_$i_$j" />
+				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
+			</sequential>
+		</sequential>
+	</action>
+	<save file="host_in" report="rms_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml b/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml
new file mode 100644
index 0000000..6c97ddc
--- /dev/null
+++ b/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_speaker_calibration" version="1.0" description="Calibrate host recording gain">
+	<setup> <!-- 1 setup -->
+		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
+		<sound id="sound1" type="sin:32000:1000:5000" preload="1" />
+	</setup>
+
+	<action> <!-- 1 action -->
+		<!--  equivalent of for loop. all children will be completed before moving to the next 
+		      stage.repeat up to 100 times unless stopped by some condition -->
+		<sequential repeat="20" index="i">
+			<!--  sync start : execute only sync complete : execute + complete
+			      For sync start, complete will be called when the parent completes -->
+			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="1" />
+			<sequential repeat="8" index="j">
+				<input device="host" id="host_in" gain="100" time="500" sync="complete" />
+				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
+				<process method="builtin:rms_mva" input="id:host_in,consti:2,consti:1" output="val:rms_$i_$j" />
+				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
+			</sequential>
+		</sequential>
+	</action>
+	<save file="host_in" report="rms_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s b/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s
new file mode 100644
index 0000000..46ba654
--- /dev/null
+++ b/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s
Binary files differ
diff --git a/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml b/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml
new file mode 100644
index 0000000..63374eb
--- /dev/null
+++ b/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_playback_spectrum_chirp" version="1.0" description="Check frequency spectrum for playback">
+	<setup>
+		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
+		<sound id="chirp" type="file:test_description/experimental/chirp_400_20000.r2s" preload="1" />
+	</setup>
+	<action>
+		<sequential repeat="1" index="i">
+                        <input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
+			<output device="DUT" id="chirp" gain="100" mode="voice" sync="start" waitforcompletion="0" />
+		</sequential>
+		<sequential repeat="1" index="k">
+			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
+			<process method="script:check_spectrum" input="id:chirp,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
+		</sequential>
+	</action>
+	<save file="chirp,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml b/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml
new file mode 100644
index 0000000..7653208
--- /dev/null
+++ b/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="dut_playback_spectrum" version="1.0" description="Check frequency spectrum for playback">
+	<setup>
+		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
+		<process method="script:gen_random" input="consti:10000,consti:5000,consti:44100,consti:20000" output="id:sound1" />
+	</setup>
+	<action>
+		<sequential repeat="1" index="i">
+			<input device="host" id="host_in_$i" gain="100" time="4000" sync="start" />
+			<output device="host" id="sound1" gain="100" sync="start" waitforcompletion="1" />
+		</sequential>
+		<sequential repeat="1" index="k">
+			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
+			<process method="script:check_spectrum" input="id:sound1,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
+		</sequential>
+	</action>
+	<save file="sound1,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml b/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml
new file mode 100644
index 0000000..9e689f2
--- /dev/null
+++ b/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 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.
+-->
+
+<case name="ref_playback_spectrum_chirp" version="1.0" description="Check frequency spectrum of reference speaker for playback">
+	<setup>
+		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
+		<sound id="chirp" type="file:test_description/experimental/chirp_400_20000.r2s" />
+	</setup>
+	<action>
+		<sequential repeat="1" index="i">
+                        <input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
+			<output device="host" id="chirp" gain="100" sync="start" waitforcompletion="0" />
+		</sequential>
+		<sequential repeat="1" index="k">
+			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
+			<process method="script:check_spectrum" input="id:chirp,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
+		</sequential>
+	</action>
+	<save file="chirp,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
+</case>
diff --git a/suite/audio_quality/test_description/host_speaker_calibration.xml b/suite/audio_quality/test_description/host_speaker_calibration.xml
index 0bdd5a1..576d999 100644
--- a/suite/audio_quality/test_description/host_speaker_calibration.xml
+++ b/suite/audio_quality/test_description/host_speaker_calibration.xml
@@ -31,7 +31,7 @@
 			<sequential repeat="10" index="j">
 				<input device="host" id="host_in" gain="70" time="500" sync="complete" />
 				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
-				<process method="builtin:rms_mva" input="id:host_in,consti:4000,consti:5000" output="val:rms_$i_$j" />
+				<process method="builtin:rms_mva" input="id:host_in,consti:4000,consti:6000" output="val:rms_$i_$j" />
 				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
 			</sequential>
 		</sequential>
diff --git a/suite/audio_quality/test_description/processing/check_spectrum.py b/suite/audio_quality/test_description/processing/check_spectrum.py
index a571fa4..598da96 100644
--- a/suite/audio_quality/test_description/processing/check_spectrum.py
+++ b/suite/audio_quality/test_description/processing/check_spectrum.py
@@ -38,7 +38,7 @@
 
 def do_check_spectrum(hostData, DUTData, samplingRate, fLow, fHigh, margainLow, margainHigh):
     # reduce FFT resolution to have averaging effects
-    N = 256 if (len(hostData) > 512) else len(hostData)
+    N = 512 if (len(hostData) > 512) else len(hostData)
     iLow = N * fLow / samplingRate + 1 # 1 for DC
     if iLow > (N / 2 - 1):
         iLow = (N / 2 - 1)
@@ -67,19 +67,26 @@
         ((1.0 - negativeMin) < margainLow / 100.0) else False
     RatioResult = np.zeros(len(amplitudeRatio), dtype=np.int16)
     for i in range(len(amplitudeRatio)):
-        RatioResult[i] = amplitudeRatio[i] * 256 # make fixed point
+        RatioResult[i] = amplitudeRatio[i] * 1024 # make fixed point
     print "positiveMax", positiveMax, "negativeMin", negativeMin
     return (passFail, negativeMin, positiveMax, RatioResult)
 
+def toMono(stereoData):
+    n = len(stereoData)/2
+    monoData = np.zeros(n)
+    for i in range(n):
+        monoData[i] = stereoData[2 * i]
+    return monoData
+
 def check_spectrum(inputData, inputTypes):
     output = []
     outputData = []
     outputTypes = []
     # basic sanity check
     inputError = False
-    if (inputTypes[0] != TYPE_MONO):
+    if (inputTypes[0] != TYPE_MONO) and (inputTypes[0] != TYPE_STEREO):
         inputError = True
-    if (inputTypes[1] != TYPE_MONO):
+    if (inputTypes[1] != TYPE_MONO) and (inputTypes[1] != TYPE_STEREO):
         inputError = True
     if (inputTypes[2] != TYPE_I64):
         inputError = True
@@ -92,21 +99,37 @@
     if (inputTypes[6] != TYPE_DOUBLE):
         inputError = True
     if inputError:
+        print "input error"
         output.append(RESULT_ERROR)
         output.append(outputData)
         output.append(outputTypes)
         return output
     hostData = inputData[0]
+    if inputTypes[0] == TYPE_STEREO:
+        hostData = toMono(hostData)
     dutData = inputData[1]
+    if inputTypes[1] == TYPE_STEREO:
+        dutData = toMono(dutData)
     samplingRate = inputData[2]
     fLow = inputData[3]
     fHigh = inputData[4]
     margainLow = inputData[5]
     margainHigh = inputData[6]
-    delay = calc_delay.calc_delay(hostData, dutData)
-    N = len(dutData)
+    delay = 0
+    N = 0
+    hostData_ = hostData
+    dutData_ = dutData
+    if len(hostData) > len(dutData):
+        delay = calc_delay.calc_delay(hostData, dutData)
+        N = len(dutData)
+        hostData_ = hostData[delay:delay+N]
+    if len(hostData) < len(dutData):
+        delay = calc_delay.calc_delay(dutData, hostData)
+        N = len(hostData)
+        dutData_ = dutData[delay:delay+N]
+
     print "delay ", delay, "deviceRecording samples ", N
-    (passFail, minError, maxError, TF) = do_check_spectrum(hostData[delay:delay+N], dutData,\
+    (passFail, minError, maxError, TF) = do_check_spectrum(hostData_, dutData_,\
         samplingRate, fLow, fHigh, margainLow, margainHigh)
 
     if passFail:
diff --git a/suite/audio_quality/test_description/processing/check_spectrum_playback.py b/suite/audio_quality/test_description/processing/check_spectrum_playback.py
new file mode 100644
index 0000000..54cf91f
--- /dev/null
+++ b/suite/audio_quality/test_description/processing/check_spectrum_playback.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+
+# Copyright (C) 2012 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.
+
+from consts import *
+import numpy as np
+import scipy as sp
+import scipy.fftpack as fft
+import matplotlib.pyplot as plt
+import sys
+sys.path.append(sys.path[0])
+import calc_delay
+
+# check if amplitude of DUT's playback
+#  lies in the given error boundary
+# input: host record
+#        sampling rate
+#        low frequency in Hz,
+#        high frequency in Hz,
+#        allowed error in negative side for pass in %,
+#        allowed error ih positive side for pass
+# output: min value in negative side, normalized to 1.0
+#         max value in positive side
+#         calculated freq spectrum in amplittude
+
+def do_check_spectrum_playback(hostData, samplingRate, fLow, fHigh, margainLow, margainHigh):
+    # reduce FFT resolution to have averaging effects
+    N = 512 if (len(hostData) > 512) else len(hostData)
+    iLow = N * fLow / samplingRate + 1 # 1 for DC
+    if iLow > (N / 2 - 1):
+        iLow = (N / 2 - 1)
+    iHigh = N * fHigh / samplingRate + 1 # 1 for DC
+    if iHigh > (N / 2 + 1):
+        iHigh = N / 2 + 1
+    print fLow, iLow, fHigh, iHigh, samplingRate
+
+    Phh, freqs = plt.psd(hostData, NFFT=N, Fs=samplingRate, Fc=0, detrend=plt.mlab.detrend_none,\
+        window=plt.mlab.window_hanning, noverlap=0, pad_to=None, sides='onesided',\
+        scale_by_freq=False)
+    print len(Phh)
+    print "Phh", abs(Phh[iLow:iHigh])
+    spectrum = np.sqrt(abs(Phh[iLow:iHigh]))
+    spectrumMean = np.mean(spectrum)
+    spectrum = spectrum / spectrumMean
+    print "Mean ", spectrumMean
+    print "Normalized spectrum", spectrum
+    positiveMax = abs(max(spectrum))
+    negativeMin = abs(min(spectrum))
+    passFail = True if (positiveMax < (margainHigh / 100.0 + 1.0)) and\
+        ((1.0 - negativeMin) < margainLow / 100.0) else False
+    spectrumResult = np.zeros(len(spectrum), dtype=np.int16)
+    for i in range(len(spectrum)):
+        spectrumResult[i] = spectrum[i] * 1024 # make fixed point
+    print "positiveMax", positiveMax, "negativeMin", negativeMin
+    return (passFail, negativeMin, positiveMax, spectrumResult)
+
+def check_spectrum_playback(inputData, inputTypes):
+    output = []
+    outputData = []
+    outputTypes = []
+    # basic sanity check
+    inputError = False
+    if (inputTypes[0] != TYPE_MONO):
+        inputError = True
+    if (inputTypes[1] != TYPE_I64):
+        inputError = True
+    if (inputTypes[2] != TYPE_I64):
+        inputError = True
+    if (inputTypes[3] != TYPE_I64):
+        inputError = True
+    if (inputTypes[4] != TYPE_DOUBLE):
+        inputError = True
+    if (inputTypes[5] != TYPE_DOUBLE):
+        inputError = True
+    if inputError:
+        output.append(RESULT_ERROR)
+        output.append(outputData)
+        output.append(outputTypes)
+        return output
+    hostData = inputData[0]
+    samplingRate = inputData[1]
+    fLow = inputData[2]
+    fHigh = inputData[3]
+    margainLow = inputData[4]
+    margainHigh = inputData[5]
+    (passFail, minError, maxError, Spectrum) = do_check_spectrum_playback(hostData, \
+        samplingRate, fLow, fHigh, margainLow, margainHigh)
+
+    if passFail:
+        output.append(RESULT_PASS)
+    else:
+        output.append(RESULT_OK)
+    outputData.append(minError)
+    outputTypes.append(TYPE_DOUBLE)
+    outputData.append(maxError)
+    outputTypes.append(TYPE_DOUBLE)
+    outputData.append(Spectrum)
+    outputTypes.append(TYPE_MONO)
+    output.append(outputData)
+    output.append(outputTypes)
+    return output
+
+# test code
+if __name__=="__main__":
+    sys.path.append(sys.path[0])
+    mod = __import__("gen_random")
+    peakAmpl = 10000
+    durationInMSec = 1000
+    samplingRate = 44100
+    fLow = 500
+    fHigh = 15000
+    data = getattr(mod, "do_gen_random")(peakAmpl, durationInMSec, samplingRate, fHigh,\
+        stereo=False)
+    print len(data)
+    (passFail, minVal, maxVal, amp) = do_check_spectrum_playback(data, samplingRate, fLow,\
+        fHigh, 1.0, 1.0)
+    plt.plot(amp)
+    plt.show()
diff --git a/suite/audio_quality/test_description/processing/playback_thd.py b/suite/audio_quality/test_description/processing/playback_thd.py
new file mode 100644
index 0000000..7242ef8
--- /dev/null
+++ b/suite/audio_quality/test_description/processing/playback_thd.py
@@ -0,0 +1,62 @@
+#!/usr/bin/python
+
+# Copyright (C) 2012 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.
+
+from consts import *
+import numpy as np
+import scipy as sp
+from calc_thd import *
+import calc_delay
+
+# calculate THD for dut_playback_thd case
+# Input: host recording (mono),
+#        frequency of sine in Hz (i64)
+#        THD pass level in percentile (double)
+# Output: THD device (double) in percentile
+
+def playback_thd(inputData, inputTypes):
+    output = []
+    outputData = []
+    outputTypes = []
+    # basic sanity check
+    inputError = False
+    if (inputTypes[0] != TYPE_MONO):
+        inputError = True
+    if (inputTypes[1] != TYPE_I64):
+        inputError = True
+    if (inputTypes[2] != TYPE_DOUBLE):
+        inputError = True
+    if inputError:
+        output.append(RESULT_ERROR)
+        output.append(outputData)
+        output.append(outputTypes)
+        return output
+
+    hostRecording = inputData[0]
+    signalFrequency = inputData[1]
+    thdPassPercentile = inputData[2]
+    samplingRate = 44100
+
+    thd = calc_thd(hostRecording, signalFrequency, samplingRate, 0.02) * 100
+    print "THD %", thd, "Margain % ", thdPassPercentile
+    if (thd < thdPassPercentile):
+        output.append(RESULT_PASS)
+    else:
+        output.append(RESULT_OK)
+    outputData.append(thd)
+    outputTypes.append(TYPE_DOUBLE)
+    output.append(outputData)
+    output.append(outputTypes)
+    return output
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 1fde1ab..e96acda 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -819,7 +819,10 @@
         <provider android:name="android.content.cts.DummyProvider"
             android:authorities="android.content.cts.dummyprovider"
             android:multiprocess="true" />
-
+        <provider android:name="android.content.cts.MockRemoteContentProvider"
+            android:authorities="remotectstest"
+            android:process=":remoteprovider" android:multiprocess="false" />
+        
         <activity android:name="android.app.cts.ChildTabActivity" android:label="ChildTabActivity" />
 
         <activity android:name="android.app.cts.LauncherActivityStub"
diff --git a/tests/src/android/content/cts/MockContentProvider.java b/tests/src/android/content/cts/MockContentProvider.java
index 29c64805..de82c0d 100644
--- a/tests/src/android/content/cts/MockContentProvider.java
+++ b/tests/src/android/content/cts/MockContentProvider.java
@@ -16,6 +16,12 @@
 
 package android.content.cts;
 
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 
 import android.content.ContentProvider;
@@ -23,37 +29,47 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriMatcher;
+import android.content.ContentProvider.PipeDataWriter;
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
+import android.util.Log;
 
-public class MockContentProvider extends ContentProvider {
+public class MockContentProvider extends ContentProvider
+        implements PipeDataWriter<String> {
 
     private SQLiteOpenHelper mOpenHelper;
 
-    private static final String AUTHORITY = "ctstest";
-    private static final String DBNAME = "ctstest.db";
+    private static final String DEFAULT_AUTHORITY = "ctstest";
+    private static final String DEFAULT_DBNAME = "ctstest.db";
     private static final int DBVERSION = 2;
 
-    private static final UriMatcher URL_MATCHER;
     private static final int TESTTABLE1 = 1;
     private static final int TESTTABLE1_ID = 2;
     private static final int TESTTABLE1_CROSS = 3;
     private static final int TESTTABLE2 = 4;
     private static final int TESTTABLE2_ID = 5;
+    private static final int SELF_ID = 6;
+    private static final int CRASH_ID = 6;
 
-    private static HashMap<String, String> CTSDBTABLE1_LIST_PROJECTION_MAP;
-    private static HashMap<String, String> CTSDBTABLE2_LIST_PROJECTION_MAP;
+    private final String mAuthority;
+    private final String mDbName;
+    private final UriMatcher URL_MATCHER;
+    private HashMap<String, String> CTSDBTABLE1_LIST_PROJECTION_MAP;
+    private HashMap<String, String> CTSDBTABLE2_LIST_PROJECTION_MAP;
 
     private static class DatabaseHelper extends SQLiteOpenHelper {
 
-        DatabaseHelper(Context context) {
-            super(context, DBNAME, null, DBVERSION);
+        DatabaseHelper(Context context, String dbname) {
+            super(context, dbname, null, DBVERSION);
         }
 
         @Override
@@ -75,9 +91,46 @@
         }
     }
 
+    public MockContentProvider() {
+        this(DEFAULT_AUTHORITY, DEFAULT_DBNAME);
+    }
+
+    public MockContentProvider(String authority, String dbName) {
+        mAuthority = authority;
+        mDbName = dbName;
+
+        URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URL_MATCHER.addURI(mAuthority, "testtable1", TESTTABLE1);
+        URL_MATCHER.addURI(mAuthority, "testtable1/#", TESTTABLE1_ID);
+        URL_MATCHER.addURI(mAuthority, "testtable1/cross", TESTTABLE1_CROSS);
+        URL_MATCHER.addURI(mAuthority, "testtable2", TESTTABLE2);
+        URL_MATCHER.addURI(mAuthority, "testtable2/#", TESTTABLE2_ID);
+        URL_MATCHER.addURI(mAuthority, "self", SELF_ID);
+        URL_MATCHER.addURI(mAuthority, "crash", CRASH_ID);
+
+        CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<String, String>();
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("key", "key");
+        CTSDBTABLE1_LIST_PROJECTION_MAP.put("value", "value");
+
+        CTSDBTABLE2_LIST_PROJECTION_MAP = new HashMap<String, String>();
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("_id", "_id");
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("key", "key");
+        CTSDBTABLE2_LIST_PROJECTION_MAP.put("value", "value");
+    }
+
     @Override
     public boolean onCreate() {
-        mOpenHelper = new DatabaseHelper(getContext());
+        mOpenHelper = new DatabaseHelper(getContext(), mDbName);
+        if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                "__cts_crash_on_launch", 0) != 0) {
+            // The test case wants us to crash our process on first launch.
+            // Well, okay then!
+            Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+            android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                    "__cts_crash_on_launch", 0);
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
         return true;
     }
 
@@ -110,6 +163,12 @@
                     (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                     selectionArgs);
             break;
+        case SELF_ID:
+            // Wha...?  Delete ME?!?  O.K.!
+            Log.i("MockContentProvider", "Delete self requested!");
+            count = 1;
+            android.os.Process.killProcess(android.os.Process.myPid());
+            break;
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
         }
@@ -155,11 +214,11 @@
         switch (URL_MATCHER.match(uri)) {
         case TESTTABLE1:
             table = "TestTable1";
-            testUri = Uri.parse("content://" + AUTHORITY + "/testtable1");
+            testUri = Uri.parse("content://" + mAuthority + "/testtable1");
             break;
         case TESTTABLE2:
             table = "TestTable2";
-            testUri = Uri.parse("content://" + AUTHORITY + "/testtable2");
+            testUri = Uri.parse("content://" + mAuthority + "/testtable2");
             break;
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
@@ -217,6 +276,20 @@
             qb.appendWhere("_id=" + uri.getPathSegments().get(1));
             break;
 
+        case CRASH_ID:
+            if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                    "__cts_crash_on_launch", 0) != 0) {
+                // The test case wants us to crash while querying.
+                // Well, okay then!
+                Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0);
+                android.os.Process.killProcess(android.os.Process.myPid());
+            }
+            qb.setTables("TestTable1");
+            qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
+            break;
+
         default:
             throw new IllegalArgumentException("Unknown URL " + uri);
         }
@@ -274,22 +347,71 @@
         return count;
     }
 
-    static {
-        URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1", TESTTABLE1);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1/#", TESTTABLE1_ID);
-        URL_MATCHER.addURI(AUTHORITY, "testtable1/cross", TESTTABLE1_CROSS);
-        URL_MATCHER.addURI(AUTHORITY, "testtable2", TESTTABLE2);
-        URL_MATCHER.addURI(AUTHORITY, "testtable2/#", TESTTABLE2_ID);
+    @Override
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+        switch (URL_MATCHER.match(uri)) {
+            case CRASH_ID:
+                if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0) != 0) {
+                    // The test case wants us to crash while querying.
+                    // Well, okay then!
+                    Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                    android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                            "__cts_crash_on_launch", 0);
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                }
+                return new AssetFileDescriptor(
+                        openPipeHelper(uri, null, null,
+                                "This is the openAssetFile test data!", this), 0,
+                        AssetFileDescriptor.UNKNOWN_LENGTH);
 
-        CTSDBTABLE1_LIST_PROJECTION_MAP = new HashMap<String, String>();
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("_id", "_id");
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("key", "key");
-        CTSDBTABLE1_LIST_PROJECTION_MAP.put("value", "value");
+            default:
+                return super.openAssetFile(uri, mode);
+        }
+    }
 
-        CTSDBTABLE2_LIST_PROJECTION_MAP = new HashMap<String, String>();
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("_id", "_id");
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("key", "key");
-        CTSDBTABLE2_LIST_PROJECTION_MAP.put("value", "value");
+    @Override
+    public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+            throws FileNotFoundException {
+        switch (URL_MATCHER.match(uri)) {
+            case CRASH_ID:
+                if (android.provider.Settings.System.getInt(getContext().getContentResolver(),
+                        "__cts_crash_on_launch", 0) != 0) {
+                    // The test case wants us to crash while querying.
+                    // Well, okay then!
+                    Log.i("MockContentProvider", "TEST IS CRASHING SELF, CROSS FINGERS!");
+                    android.provider.Settings.System.putInt(getContext().getContentResolver(),
+                            "__cts_crash_on_launch", 0);
+                    android.os.Process.killProcess(android.os.Process.myPid());
+                }
+                return new AssetFileDescriptor(
+                        openPipeHelper(uri, null, null,
+                                "This is the openTypedAssetFile test data!", this), 0,
+                        AssetFileDescriptor.UNKNOWN_LENGTH);
+
+            default:
+                return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
+        }
+    }
+
+    @Override
+    public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts,
+            String args) {
+        FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
+        PrintWriter pw = null;
+        try {
+            pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
+            pw.print(args);
+        } catch (UnsupportedEncodingException e) {
+            Log.w("MockContentProvider", "Ooops", e);
+        } finally {
+            if (pw != null) {
+                pw.flush();
+            }
+            try {
+                fout.close();
+            } catch (IOException e) {
+            }
+        }
     }
 }
diff --git a/tests/src/android/content/cts/MockRemoteContentProvider.java b/tests/src/android/content/cts/MockRemoteContentProvider.java
new file mode 100644
index 0000000..fc43701
--- /dev/null
+++ b/tests/src/android/content/cts/MockRemoteContentProvider.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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;
+
+public class MockRemoteContentProvider extends MockContentProvider {
+    public MockRemoteContentProvider() {
+        super("remotectstest", "remotectstest.db");
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index 3276c8e..22c2faa 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -20,6 +20,7 @@
 
 
 import android.accounts.Account;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -33,11 +34,15 @@
 import android.os.CancellationSignal;
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 
 public class ContentResolverTest extends AndroidTestCase {
@@ -50,6 +55,16 @@
     private static final Uri TABLE1_CROSS_URI =
             Uri.parse("content://" + AUTHORITY + "/testtable1/cross");
     private static final Uri TABLE2_URI = Uri.parse("content://" + AUTHORITY + "/testtable2/");
+    private static final Uri SELF_URI = Uri.parse("content://" + AUTHORITY + "/self/");
+    private static final Uri CRASH_URI = Uri.parse("content://" + AUTHORITY + "/crash/");
+
+    private static final String REMOTE_AUTHORITY = "remotectstest";
+    private static final Uri REMOTE_TABLE1_URI = Uri.parse("content://"
+                + REMOTE_AUTHORITY + "/testtable1/");
+    private static final Uri REMOTE_SELF_URI = Uri.parse("content://"
+                + REMOTE_AUTHORITY + "/self/");
+    private static final Uri REMOTE_CRASH_URI = Uri.parse("content://"
+            + REMOTE_AUTHORITY + "/crash/");
 
     private static final Account ACCOUNT = new Account("cts", "cts");
 
@@ -73,20 +88,25 @@
         mContext = getContext();
         mContentResolver = mContext.getContentResolver();
 
+        android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+
         // add three rows to database when every test case start.
         ContentValues values = new ContentValues();
 
         values.put(COLUMN_KEY_NAME, KEY1);
         values.put(COLUMN_VALUE_NAME, VALUE1);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
 
         values.put(COLUMN_KEY_NAME, KEY2);
         values.put(COLUMN_VALUE_NAME, VALUE2);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
 
         values.put(COLUMN_KEY_NAME, KEY3);
         values.put(COLUMN_VALUE_NAME, VALUE3);
         mContentResolver.insert(TABLE1_URI, values);
+        mContentResolver.insert(REMOTE_TABLE1_URI, values);
     }
 
     @Override
@@ -95,6 +115,10 @@
         if ( null != mCursor && !mCursor.isClosed() ) {
             mCursor.close();
         }
+        mContentResolver.delete(REMOTE_TABLE1_URI, null, null);
+        if ( null != mCursor && !mCursor.isClosed() ) {
+            mCursor.close();
+        }
         super.tearDown();
     }
 
@@ -102,6 +126,125 @@
         assertNotNull(mContentResolver);
     }
 
+    public void testCrashOnLaunch() {
+        // This test is going to make sure that the platform deals correctly
+        // with a content provider process going away while a client is waiting
+        // for it to come up.
+        // First, we need to make sure our provider process is gone.  Goodbye!
+        ContentProviderClient client = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // We are going to do something wrong here...  release the client first,
+        // so the act of killing it doesn't kill our own process.
+        client.release();
+        try {
+            client.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Now make sure the thing is actually gone.
+        boolean gone = true;
+        try {
+            client.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                "__cts_crash_on_launch", 0), 0);
+            assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+        }
+    }
+
+    public void testUnstableToStableRefs() {
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Get a stable reference on the remote content provider.
+        ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can still access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release unstable reference.
+        uClient.release();
+        // Verify we can still access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release stable reference, removing last ref.
+        sClient.release();
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            sClient.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+    }
+
+    public void testStableToUnstableRefs() {
+        // Get a stable reference on the remote content provider.
+        ContentProviderClient sClient = mContentResolver.acquireContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can still access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+        
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Release stable reference, leaving only an unstable ref.
+        sClient.release();
+
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            uClient.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+
+        // Release unstable reference.
+        uClient.release();
+    }
+
     public void testGetType() {
         String type1 = mContentResolver.getType(TABLE1_URI);
         assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
@@ -120,6 +263,43 @@
         }
     }
 
+    public void testUnstableGetType() {
+        // Get an unstable refrence on the remote content provider.
+        ContentProviderClient client = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Verify we can access it.
+        String type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            client.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        // Make sure the remote client is actually gone.
+        boolean gone = true;
+        try {
+            client.getType(REMOTE_TABLE1_URI);
+            gone = false;
+        } catch (RemoteException e) {
+        }
+        if (!gone) {
+            fail("Content provider process is not gone!");
+        }
+
+        // Now the remote client is gone, can we recover?
+        // Release our old reference.
+        client.release();
+        // Get a new reference.
+        client = mContentResolver.acquireUnstableContentProviderClient(REMOTE_AUTHORITY);
+        // Verify we can access it.
+        type1 = mContentResolver.getType(REMOTE_TABLE1_URI);
+        assertTrue(type1.startsWith(ContentResolver.CURSOR_DIR_BASE_TYPE, 0));
+    }
+
     public void testQuery() {
         mCursor = mContentResolver.query(TABLE1_URI, null, null, null, null);
 
@@ -170,6 +350,32 @@
         }
     }
 
+    public void testCrashingQuery() {
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            mCursor = mContentResolver.query(REMOTE_CRASH_URI, null, null, null, null);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                "__cts_crash_on_launch", 0), 0);
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+        }
+
+        assertNotNull(mCursor);
+        assertEquals(3, mCursor.getCount());
+        assertEquals(3, mCursor.getColumnCount());
+
+        mCursor.moveToLast();
+        assertEquals(3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
+        assertEquals(KEY3, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
+        assertEquals(VALUE3, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
+
+        mCursor.moveToPrevious();
+        assertEquals(2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_ID_NAME)));
+        assertEquals(KEY2, mCursor.getString(mCursor.getColumnIndexOrThrow(COLUMN_KEY_NAME)));
+        assertEquals(VALUE2, mCursor.getInt(mCursor.getColumnIndexOrThrow(COLUMN_VALUE_NAME)));
+        mCursor.close();
+    }
+
     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
         CancellationSignal cancellationSignal = new CancellationSignal();
 
@@ -334,6 +540,98 @@
         }
     }
 
+    private String consumeAssetFileDescriptor(AssetFileDescriptor afd)
+            throws IOException {
+        FileInputStream stream = null;
+        try {
+            stream = afd.createInputStream();
+            InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+            // Got it...  copy the stream into a local string and return it.
+            StringBuilder builder = new StringBuilder(128);
+            char[] buffer = new char[8192];
+            int len;
+            while ((len=reader.read(buffer)) > 0) {
+                builder.append(buffer, 0, len);
+            }
+            return builder.toString();
+
+        } finally {
+            if (stream != null) {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        
+    }
+
+    public void testCrashingOpenAssetFileDescriptor() throws IOException {
+        AssetFileDescriptor afd = null;
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            afd = mContentResolver.openAssetFileDescriptor(REMOTE_CRASH_URI, "rw");
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                    "__cts_crash_on_launch", 0), 0);
+            assertNotNull(afd);
+            String str = consumeAssetFileDescriptor(afd);
+            afd = null;
+            assertEquals(str, "This is the openAssetFile test data!");
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+            if (afd != null) {
+                afd.close();
+            }
+        }
+
+        // Make sure a content provider crash at this point won't hurt us.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        uClient.release();
+    }
+
+    public void testCrashingOpenTypedAssetFileDescriptor() throws IOException {
+        AssetFileDescriptor afd = null;
+        try {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 1);
+            afd = mContentResolver.openTypedAssetFileDescriptor(
+                    REMOTE_CRASH_URI, "text/plain", null);
+            assertEquals(android.provider.Settings.System.getInt(mContentResolver,
+                    "__cts_crash_on_launch", 0), 0);
+            assertNotNull(afd);
+            String str = consumeAssetFileDescriptor(afd);
+            afd = null;
+            assertEquals(str, "This is the openTypedAssetFile test data!");
+        } finally {
+            android.provider.Settings.System.putInt(mContentResolver, "__cts_crash_on_launch", 0);
+            if (afd != null) {
+                afd.close();
+            }
+        }
+
+        // Make sure a content provider crash at this point won't hurt us.
+        ContentProviderClient uClient = mContentResolver.acquireUnstableContentProviderClient(
+                REMOTE_AUTHORITY);
+        // Kill it.  Note that a bug at this point where it causes our own
+        // process to be killed will result in the entire test failing.
+        try {
+            Log.i("ContentResolverTest",
+                    "Killing remote client -- if test process goes away, that is why!");
+            uClient.delete(REMOTE_SELF_URI, null, null);
+        } catch (RemoteException e) {
+        }
+        uClient.release();
+    }
+
     public void testOpenFileDescriptor() throws IOException {
         Uri uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" +
                 getContext().getCacheDir().getAbsolutePath() +
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
index fad9f30..1e8a4ac 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
@@ -16,12 +16,15 @@
 
 package android.provider.cts;
 
+import com.android.cts.stub.R;
+
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.ParcelFileDescriptor;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
 import android.test.AndroidTestCase;
@@ -122,6 +125,35 @@
         }
     }
 
+    public void testCaseSensitivity() throws IOException {
+        String fileDir = Environment.getExternalStorageDirectory() +
+                "/" + getClass().getCanonicalName();
+        String fileName = fileDir + "/Test.Mp3";
+        writeFile(R.raw.testmp3, fileName);
+
+        String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
+        Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
+        ContentValues values = new ContentValues();
+        values.put(MediaColumns.DATA, fileDir + "/test.mp3");
+        Uri fileUri = mResolver.insert(allFilesUri, values);
+        try {
+            ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
+            pfd.close();
+        } finally {
+            mResolver.delete(fileUri, null, null);
+            new File(fileName).delete();
+            new File(fileDir).delete();
+        }
+    }
+
+    private void writeFile(int resid, String path) throws IOException {
+        File out = new File(path);
+        File dir = out.getParentFile();
+        dir.mkdirs();
+        FileCopyHelper copier = new FileCopyHelper(mContext);
+        copier.copyToExternalStorage(resid, out);
+    }
+
     private int getFileCount(Uri uri) {
         Cursor cursor = mResolver.query(uri, null, null, null, null);
         try {