resolved conflicts for merge of 4e3fadd0 to eclair-mr2
diff --git a/Android.mk b/Android.mk
index 5034c7e..f538d8e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
 	core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
 	core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
 	core/java/com/android/internal/backup/IBackupTransport.aidl \
+	core/java/com/android/internal/os/IDropBoxService.aidl \
 	core/java/com/android/internal/os/IResultReceiver.aidl \
 	core/java/com/android/internal/view/IInputContext.aidl \
 	core/java/com/android/internal/view/IInputContextCallback.aidl \
@@ -217,6 +218,7 @@
 	frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \
 	frameworks/base/core/java/android/net/Uri.aidl \
 	frameworks/base/core/java/android/os/Bundle.aidl \
+	frameworks/base/core/java/android/os/DropBox.aidl \
 	frameworks/base/core/java/android/os/ParcelFileDescriptor.aidl \
 	frameworks/base/core/java/android/os/ParcelUuid.aidl \
 	frameworks/base/core/java/android/view/KeyEvent.aidl \
diff --git a/api/current.xml b/api/current.xml
index 50b45d6..caaa305 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -119272,6 +119272,23 @@
 <parameter name="origId" type="long">
 </parameter>
 </method>
+<method name="cancelThumbnailRequest"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+</method>
 <method name="getContentUri"
  return="android.net.Uri"
  abstract="false"
@@ -119304,6 +119321,27 @@
 <parameter name="options" type="android.graphics.BitmapFactory.Options">
 </parameter>
 </method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -119729,6 +119767,23 @@
 <parameter name="origId" type="long">
 </parameter>
 </method>
+<method name="cancelThumbnailRequest"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+</method>
 <method name="getContentUri"
  return="android.net.Uri"
  abstract="false"
@@ -119761,6 +119816,27 @@
 <parameter name="options" type="android.graphics.BitmapFactory.Options">
 </parameter>
 </method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="groupId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
 <field name="DATA"
  type="java.lang.String"
  transient="false"
@@ -132520,6 +132596,673 @@
 </parameter>
 </method>
 </class>
+<class name="MockCursor"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.database.Cursor">
+</implements>
+<constructor name="MockCursor"
+ type="android.test.mock.MockCursor"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="abortUpdates"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="commitUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="commitUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="values" type="java.util.Map&lt;? extends java.lang.Long, ? extends java.util.Map&lt;java.lang.String, java.lang.Object&gt;&gt;">
+</parameter>
+</method>
+<method name="copyStringToBuffer"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="buffer" type="android.database.CharArrayBuffer">
+</parameter>
+</method>
+<method name="deactivate"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="deleteRow"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getBlob"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getColumnCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getColumnIndex"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnName" type="java.lang.String">
+</parameter>
+</method>
+<method name="getColumnIndexOrThrow"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnName" type="java.lang.String">
+</parameter>
+</method>
+<method name="getColumnName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getColumnNames"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getExtras"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFloat"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getPosition"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getShort"
+ return="short"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="getWantsAllOnMoveCalls"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="hasUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isAfterLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isBeforeFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isClosed"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isNull"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+<method name="move"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="offset" type="int">
+</parameter>
+</method>
+<method name="moveToFirst"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToLast"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="moveToPosition"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="position" type="int">
+</parameter>
+</method>
+<method name="moveToPrevious"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="registerContentObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.ContentObserver">
+</parameter>
+</method>
+<method name="registerDataSetObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.DataSetObserver">
+</parameter>
+</method>
+<method name="requery"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="respond"
+ return="android.os.Bundle"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="extras" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="setNotificationUri"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="supportsUpdates"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="unregisterContentObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.ContentObserver">
+</parameter>
+</method>
+<method name="unregisterDataSetObserver"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="observer" type="android.database.DataSetObserver">
+</parameter>
+</method>
+<method name="updateBlob"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="byte[]">
+</parameter>
+</method>
+<method name="updateDouble"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="double">
+</parameter>
+</method>
+<method name="updateFloat"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="float">
+</parameter>
+</method>
+<method name="updateInt"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="int">
+</parameter>
+</method>
+<method name="updateLong"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="long">
+</parameter>
+</method>
+<method name="updateShort"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="short">
+</parameter>
+</method>
+<method name="updateString"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+<parameter name="value" type="java.lang.String">
+</parameter>
+</method>
+<method name="updateToNull"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="columnIndex" type="int">
+</parameter>
+</method>
+</class>
 <class name="MockDialogInterface"
  extends="java.lang.Object"
  abstract="false"
@@ -144456,6 +145199,29 @@
 </parameter>
 </method>
 </interface>
+<interface name="LeadingMarginSpan.LeadingMarginSpan2"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.text.style.LeadingMarginSpan">
+</implements>
+<implements name="android.text.style.WrapTogetherSpan">
+</implements>
+<method name="getLeadingMarginLineCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
 <class name="LeadingMarginSpan.Standard"
  extends="java.lang.Object"
  abstract="false"
diff --git a/cmds/stagefright/SineSource.cpp b/cmds/stagefright/SineSource.cpp
index e5a6ccb..e272a65 100644
--- a/cmds/stagefright/SineSource.cpp
+++ b/cmds/stagefright/SineSource.cpp
@@ -86,8 +86,8 @@
         x += k;
     }
 
-    buffer->meta_data()->setInt32(kKeyTimeUnits, mPhase);
-    buffer->meta_data()->setInt32(kKeyTimeScale, mSampleRate);
+    buffer->meta_data()->setInt64(
+            kKeyTime, ((int64_t)mPhase * 1000000) / mSampleRate);
 
     mPhase += numFramesPerBuffer;
 
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 323d448..0025c5e 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -23,7 +23,7 @@
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
-#include <media/stagefright/MPEG4Extractor.h>
+#include <media/stagefright/MediaExtractor.h>
 #include <media/stagefright/MPEG4Writer.h>
 #include <media/stagefright/MmapSource.h>
 #include <media/stagefright/OMXClient.h>
@@ -32,8 +32,10 @@
 
 using namespace android;
 
-#if 0
+#if 1
 class DummySource : public MediaSource {
+    static const int32_t kFramerate = 24;  // fps
+
 public:
     DummySource(int width, int height)
         : mWidth(width),
@@ -52,6 +54,7 @@
     }
 
     virtual status_t start(MetaData *params) {
+        mNumFramesOutput = 0;
         return OK;
     }
 
@@ -61,6 +64,12 @@
 
     virtual status_t read(
             MediaBuffer **buffer, const MediaSource::ReadOptions *options) {
+        if (mNumFramesOutput == kFramerate * 10) {
+            // Stop returning data after 10 secs.
+            return ERROR_END_OF_STREAM;
+        }
+
+        // printf("DummySource::read\n");
         status_t err = mGroup.acquire_buffer(buffer);
         if (err != OK) {
             return err;
@@ -69,7 +78,13 @@
         char x = (char)((double)rand() / RAND_MAX * 255);
         memset((*buffer)->data(), x, mSize);
         (*buffer)->set_range(0, mSize);
+        (*buffer)->meta_data()->clear();
+        (*buffer)->meta_data()->setInt64(
+                kKeyTime, (mNumFramesOutput * 1000000) / kFramerate);
+        ++mNumFramesOutput;
 
+        // printf("DummySource::read - returning buffer\n");
+        // LOGI("DummySource::read - returning buffer");
         return OK;
     }
 
@@ -80,6 +95,7 @@
     MediaBufferGroup mGroup;
     int mWidth, mHeight;
     size_t mSize;
+    int64_t mNumFramesOutput;;
 
     DummySource(const DummySource &);
     DummySource &operator=(const DummySource &);
@@ -88,8 +104,8 @@
 sp<MediaSource> createSource(const char *filename) {
     sp<MediaSource> source;
 
-    sp<MPEG4Extractor> extractor =
-        new MPEG4Extractor(new MmapSource(filename));
+    sp<MediaExtractor> extractor =
+        MediaExtractor::Create(new MmapSource(filename));
 
     size_t num_tracks = extractor->countTracks();
 
@@ -144,8 +160,8 @@
     success = success && meta->findInt32(kKeyHeight, &height);
     CHECK(success);
 #else
-    int width = 320;
-    int height = 240;
+    int width = 800;
+    int height = 480;
     sp<MediaSource> decoder = new DummySource(width, height);
 #endif
 
@@ -159,19 +175,26 @@
         OMXCodec::Create(
                 client.interface(), enc_meta, true /* createEncoder */, decoder);
 
-#if 0
+#if 1
     sp<MPEG4Writer> writer = new MPEG4Writer("/sdcard/output.mp4");
-    writer->addSource(enc_meta, encoder);
+    writer->addSource(encoder);
     writer->start();
-    sleep(20);
-    printf("stopping now.\n");
+    while (!writer->reachedEOS()) {
+        usleep(100000);
+    }
     writer->stop();
 #else
     encoder->start();
 
     MediaBuffer *buffer;
     while (encoder->read(&buffer) == OK) {
-        printf("got an output frame of size %d\n", buffer->range_length());
+        int32_t isSync;
+        if (!buffer->meta_data()->findInt32(kKeyIsSyncFrame, &isSync)) {
+            isSync = false;
+        }
+
+        printf("got an output frame of size %d%s\n", buffer->range_length(),
+               isSync ? " (SYNC)" : "");
 
         buffer->release();
         buffer = NULL;
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 4ffc8e4..d26e558 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -52,21 +52,26 @@
 static void playSource(OMXClient *client, const sp<MediaSource> &source) {
     sp<MetaData> meta = source->getFormat();
 
-    int32_t durationUnits;
-    int32_t timeScale;
-    CHECK(meta->findInt32(kKeyDuration, &durationUnits));
-    CHECK(meta->findInt32(kKeyTimeScale, &timeScale));
+    int64_t durationUs;
+    CHECK(meta->findInt64(kKeyDuration, &durationUs));
 
-    int64_t durationUs = ((int64_t)durationUnits * 1000000) / timeScale;
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
 
-    sp<OMXCodec> decoder = OMXCodec::Create(
+    sp<MediaSource> rawSource;
+    if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) {
+        rawSource = source;
+    } else {
+        rawSource = OMXCodec::Create(
             client->interface(), meta, false /* createEncoder */, source);
 
-    if (decoder == NULL) {
-        return;
+        if (rawSource == NULL) {
+            fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime);
+            return;
+        }
     }
 
-    decoder->start();
+    rawSource->start();
 
     if (gReproduceBug >= 3 && gReproduceBug <= 5) {
         status_t err;
@@ -74,24 +79,28 @@
         MediaSource::ReadOptions options;
         int64_t seekTimeUs = -1;
         for (;;) {
-            err = decoder->read(&buffer, &options);
+            err = rawSource->read(&buffer, &options);
             options.clearSeekTo();
 
             bool shouldSeek = false;
-            if (err != OK) {
+            if (err == INFO_FORMAT_CHANGED) {
+                CHECK_EQ(buffer, NULL);
+
+                printf("format changed.\n");
+                continue;
+            } else if (err != OK) {
                 printf("reached EOF.\n");
 
                 shouldSeek = true;
             } else {
-                int32_t timestampUnits;
-                CHECK(buffer->meta_data()->findInt32(kKeyTimeUnits, &timestampUnits));
-
-                int64_t timestampUs = ((int64_t)timestampUnits * 1000000) / timeScale;
+                int64_t timestampUs;
+                CHECK(buffer->meta_data()->findInt64(kKeyTime, &timestampUs));
 
                 bool failed = false;
 
                 if (seekTimeUs >= 0) {
                     int64_t diff = timestampUs - seekTimeUs;
+
                     if (diff < 0) {
                         diff = -diff;
                     }
@@ -134,7 +143,7 @@
             }
         }
 
-        decoder->stop();
+        rawSource->stop();
 
         return;
     }
@@ -151,12 +160,17 @@
         MediaBuffer *buffer;
 
         for (;;) {
-            status_t err = decoder->read(&buffer, &options);
+            status_t err = rawSource->read(&buffer, &options);
             options.clearSeekTo();
 
             if (err != OK) {
                 CHECK_EQ(buffer, NULL);
 
+                if (err == INFO_FORMAT_CHANGED) {
+                    printf("format changed.\n");
+                    continue;
+                }
+
                 break;
             }
 
@@ -188,7 +202,7 @@
         options.setSeekTo(0);
     }
 
-    decoder->stop();
+    rawSource->stop();
     printf("\n");
 
     int64_t delay = getNowUs() - startTime;
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f59c4e..2d36241 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -16,20 +16,27 @@
 
 package android.accounts;
 
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.RegisteredServicesCache;
-import android.content.pm.PackageInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.RegisteredServicesCacheListener;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -37,17 +44,13 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Binder;
 import android.os.SystemProperties;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.app.PendingIntent;
-import android.app.NotificationManager;
-import android.app.Notification;
-import android.Manifest;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -58,8 +61,9 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.R;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyIntents;
 
 /**
  * A system service that provides  account, password, and authtoken management for all
@@ -90,11 +94,8 @@
 
     // Messages that can be sent on mHandler
     private static final int MESSAGE_TIMED_OUT = 3;
-    private static final int MESSAGE_CONNECTED = 7;
-    private static final int MESSAGE_DISCONNECTED = 8;
 
     private final AccountAuthenticatorCache mAuthenticatorCache;
-    private final AuthenticatorBindHelper mBindHelper;
     private final DatabaseHelper mOpenHelper;
     private final SimWatcher mSimWatcher;
 
@@ -221,8 +222,6 @@
 
         mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
         mAuthenticatorCache.setListener(this);
-        mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
-                MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
 
         mSimWatcher = new SimWatcher(mContext);
         sThis.set(this);
@@ -1081,7 +1080,7 @@
     }
 
     private abstract class Session extends IAccountAuthenticatorResponse.Stub
-            implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
+            implements IBinder.DeathRecipient, ServiceConnection {
         IAccountManagerResponse mResponse;
         final String mAccountType;
         final boolean mExpectActivityLaunch;
@@ -1163,7 +1162,7 @@
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
             }
-            if (!mBindHelper.bind(mAccountType, this)) {
+            if (!bindToAuthenticator(mAccountType)) {
                 Log.d(TAG, "bind attempt failed for " + toDebugString());
                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
             }
@@ -1172,7 +1171,7 @@
         private void unbind() {
             if (mAuthenticator != null) {
                 mAuthenticator = null;
-                mBindHelper.unbind(this);
+                mContext.unbindService(this);
             }
         }
 
@@ -1185,7 +1184,7 @@
             mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
         }
 
-        public void onConnected(IBinder service) {
+        public void onServiceConnected(ComponentName name, IBinder service) {
             mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
             try {
                 run();
@@ -1195,9 +1194,7 @@
             }
         }
 
-        public abstract void run() throws RemoteException;
-
-        public void onDisconnected() {
+        public void onServiceDisconnected(ComponentName name) {
             mAuthenticator = null;
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
@@ -1206,6 +1203,8 @@
             }
         }
 
+        public abstract void run() throws RemoteException;
+
         public void onTimedOut() {
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
@@ -1275,6 +1274,39 @@
                 }
             }
         }
+
+        /**
+         * find the component name for the authenticator and initiate a bind
+         * if no authenticator or the bind fails then return false, otherwise return true
+         */
+        private boolean bindToAuthenticator(String authenticatorType) {
+            AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+                    mAuthenticatorCache.getServiceInfo(
+                            AuthenticatorDescription.newKey(authenticatorType));
+            if (authenticatorInfo == null) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "there is no authenticator for " + authenticatorType
+                            + ", bailing out");
+                }
+                return false;
+            }
+
+            Intent intent = new Intent();
+            intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+            intent.setComponent(authenticatorInfo.componentName);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+            }
+            if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+                }
+                return false;
+            }
+
+
+            return true;
+        }
     }
 
     private class MessageHandler extends Handler {
@@ -1283,9 +1315,6 @@
         }
 
         public void handleMessage(Message msg) {
-            if (mBindHelper.handleMessage(msg)) {
-                return;
-            }
             switch (msg.what) {
                 case MESSAGE_TIMED_OUT:
                     Session session = (Session)msg.obj;
@@ -1425,16 +1454,58 @@
          */
         @Override
         public void onReceive(Context context, Intent intent) {
-            // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
-            String imsi = ((TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE)).getSubscriberId();
+            // Check IMSI on every update; nothing happens if the IMSI
+            // is missing or unchanged.
+            TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            if (telephonyManager == null) {
+                Log.w(TAG, "failed to get TelephonyManager");
+                return;
+            }
+            String imsi = telephonyManager.getSubscriberId();
+
+            // If the subscriber ID is an empty string, don't do anything.
             if (TextUtils.isEmpty(imsi)) return;
 
+            // If the current IMSI matches what's stored, don't do anything.
             String storedImsi = getMetaValue("imsi");
-
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
             }
+            if (imsi.equals(storedImsi)) return;
+
+            // If a CDMA phone is unprovisioned, getSubscriberId()
+            // will return a different value, but we *don't* erase the
+            // passwords.  We only erase them if it has a different
+            // subscriber ID once it's provisioned.
+            if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+                IBinder service = ServiceManager.checkService(Context.TELEPHONY_SERVICE);
+                if (service == null) {
+                    Log.w(TAG, "call to checkService(TELEPHONY_SERVICE) failed");
+                    return;
+                }
+                ITelephony telephony = ITelephony.Stub.asInterface(service);
+                if (telephony == null) {
+                    Log.w(TAG, "failed to get ITelephony interface");
+                    return;
+                }
+                boolean needsProvisioning;
+                try {
+                    needsProvisioning = telephony.getCdmaNeedsProvisioning();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "exception while checking provisioning", e);
+                    // default to NOT wiping out the passwords
+                    needsProvisioning = true;
+                }
+                if (needsProvisioning) {
+                    // if the phone needs re-provisioning, don't do anything.
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "current IMSI=" + imsi + " (needs provisioning); stored IMSI=" +
+                              storedImsi);
+                    }
+                    return;
+                }
+            }
 
             if (!imsi.equals(storedImsi) && !TextUtils.isEmpty(storedImsi)) {
                 Log.w(TAG, "wiping all passwords and authtokens because IMSI changed ("
@@ -1578,6 +1649,7 @@
     }
 
     private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+        final boolean inSystemImage = inSystemImage(callerUid);
         final boolean fromAuthenticator = account != null
                 && hasAuthenticatorUid(account.type, callerUid);
         final boolean hasExplicitGrants = account != null
@@ -1588,7 +1660,7 @@
                     + ": is authenticator? " + fromAuthenticator
                     + ", has explicit permission? " + hasExplicitGrants);
         }
-        return fromAuthenticator || hasExplicitGrants || inSystemImage(callerUid);
+        return fromAuthenticator || hasExplicitGrants || inSystemImage;
     }
 
     private boolean hasAuthenticatorUid(String accountType, int callingUid) {
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
deleted file mode 100644
index 2ca1f0e..0000000
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ /dev/null
@@ -1,258 +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.
- */
-
-package android.accounts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-/**
- * A helper object that simplifies binding to Account Authenticators. It uses the
- * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
- * allowing the user to bind by account name. It also allows multiple, simultaneous binds
- * to the same authenticator, with each bind call guaranteed to return either
- * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
- * itself succeeds, even if the authenticator is already bound internally.
- * @hide
- */
-public class AuthenticatorBindHelper {
-    private static final String TAG = "Accounts";
-    private final Handler mHandler;
-    private final Context mContext;
-    private final int mMessageWhatConnected;
-    private final int mMessageWhatDisconnected;
-    private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
-    private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
-    private final AccountAuthenticatorCache mAuthenticatorCache;
-
-    public AuthenticatorBindHelper(Context context,
-            AccountAuthenticatorCache authenticatorCache, Handler handler,
-            int messageWhatConnected, int messageWhatDisconnected) {
-        mContext = context;
-        mHandler = handler;
-        mAuthenticatorCache = authenticatorCache;
-        mMessageWhatConnected = messageWhatConnected;
-        mMessageWhatDisconnected = messageWhatDisconnected;
-    }
-
-    public interface Callback {
-        void onConnected(IBinder service);
-        void onDisconnected();
-    }
-
-    public boolean bind(String authenticatorType, Callback callback) {
-        // if the authenticator is connecting or connected then return true
-        synchronized (mServiceConnections) {
-            if (mServiceConnections.containsKey(authenticatorType)) {
-                MyServiceConnection connection = mServiceConnections.get(authenticatorType);
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "service connection already exists for " + authenticatorType);
-                }
-                mServiceUsers.get(authenticatorType).add(callback);
-                if (connection.mService != null) {
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "the service is connected, scheduling a connected message for "
-                                + authenticatorType);
-                    }
-                    connection.scheduleCallbackConnectedMessage(callback);
-                } else {
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "the service is *not* connected, waiting for for "
-                                + authenticatorType);
-                    }
-                }
-                return true;
-            }
-
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "there is no service connection for " + authenticatorType);
-            }
-
-            // otherwise find the component name for the authenticator and initiate a bind
-            // if no authenticator or the bind fails then return false, otherwise return true
-            AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
-                    mAuthenticatorCache.getServiceInfo(
-                            AuthenticatorDescription.newKey(authenticatorType));
-            if (authenticatorInfo == null) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "there is no authenticator for " + authenticatorType
-                            + ", bailing out");
-                }
-                return false;
-            }
-
-            MyServiceConnection connection = new MyServiceConnection(authenticatorType);
-
-            Intent intent = new Intent();
-            intent.setAction("android.accounts.AccountAuthenticator");
-            intent.setComponent(authenticatorInfo.componentName);
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
-            }
-            if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
-                }
-                return false;
-            }
-
-            mServiceConnections.put(authenticatorType, connection);
-            mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
-            return true;
-        }
-    }
-
-    public void unbind(Callback callbackToUnbind) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "unbinding callback " + callbackToUnbind);
-        }
-        synchronized (mServiceConnections) {
-            for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
-                final String authenticatorType = entry.getKey();
-                final ArrayList<Callback> serviceUsers = entry.getValue();
-                for (Callback callback : serviceUsers) {
-                    if (callback == callbackToUnbind) {
-                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                            Log.v(TAG, "found callback in service" + authenticatorType);
-                        }
-                        serviceUsers.remove(callbackToUnbind);
-                        if (serviceUsers.isEmpty()) {
-                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                                Log.v(TAG, "there are no more callbacks for service "
-                                        + authenticatorType + ", unbinding service");
-                            }
-                            unbindFromServiceLocked(authenticatorType);
-                        } else {
-                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                                Log.v(TAG, "leaving service " + authenticatorType
-                                        + " around since there are still callbacks using it");
-                            }
-                        }
-                        return;
-                    }
-                }
-            }
-            Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
-        }
-    }
-
-    /**
-     * You must synchronized on mServiceConnections before calling this
-     */
-    private void unbindFromServiceLocked(String authenticatorType) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "unbindService from " + authenticatorType);
-        }
-        mContext.unbindService(mServiceConnections.get(authenticatorType));
-        mServiceUsers.remove(authenticatorType);
-        mServiceConnections.remove(authenticatorType);
-    }
-
-    private class ConnectedMessagePayload {
-        public final IBinder mService;
-        public final Callback mCallback;
-        public ConnectedMessagePayload(IBinder service, Callback callback) {
-            mService = service;
-            mCallback = callback;
-        }
-    }
-
-    private class MyServiceConnection implements ServiceConnection {
-        private final String mAuthenticatorType;
-        private IBinder mService = null;
-
-        public MyServiceConnection(String authenticatorType) {
-            mAuthenticatorType = authenticatorType;
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
-            }
-            // post a message for each service user to tell them that the service is connected
-            synchronized (mServiceConnections) {
-                mService = service;
-                for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "the service became connected, scheduling a connected "
-                                + "message for " + mAuthenticatorType);
-                    }
-                    scheduleCallbackConnectedMessage(callback);
-                }
-            }
-        }
-
-        private void scheduleCallbackConnectedMessage(Callback callback) {
-            final ConnectedMessagePayload payload =
-                    new ConnectedMessagePayload(mService, callback);
-            mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
-            }
-            // post a message for each service user to tell them that the service is disconnected,
-            // and unbind from the service.
-            synchronized (mServiceConnections) {
-                final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
-                if (callbackList != null) {
-                    for (Callback callback : callbackList) {
-                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                            Log.v(TAG, "the service became disconnected, scheduling a "
-                                    + "disconnected message for "
-                                    + mAuthenticatorType);
-                        }
-                        mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
-                    }
-                    unbindFromServiceLocked(mAuthenticatorType);
-                }
-            }
-        }
-    }
-
-    boolean handleMessage(Message message) {
-        if (message.what == mMessageWhatConnected) {
-            ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
-            }
-            payload.mCallback.onConnected(payload.mService);
-            return true;
-        } else if (message.what == mMessageWhatDisconnected) {
-            Callback callback = (Callback)message.obj;
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
-            }
-            callback.onDisconnected();
-            return true;
-        } else {
-            return false;
-        }
-    }
-}
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index e3ed2e9..f4b7258 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,14 +18,16 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.widget.TextView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
 import android.view.View;
 import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.Window;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.graphics.drawable.Drawable;
 import com.android.internal.R;
 
 /**
@@ -43,62 +45,68 @@
     private String mAuthTokenType;
     private int mUid;
     private Bundle mResultBundle = null;
+    protected LayoutInflater mInflater;
 
     protected void onCreate(Bundle savedInstanceState) {
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
         super.onCreate(savedInstanceState);
-        getWindow().setContentView(R.layout.grant_credentials_permission);
-        mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
-        mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
-        mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
-        final String accountTypeLabel =
-                getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
-        final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
+        setContentView(R.layout.grant_credentials_permission);
 
-        findViewById(R.id.allow).setOnClickListener(this);
-        findViewById(R.id.deny).setOnClickListener(this);
+        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
-        TextView messageView = (TextView) getWindow().findViewById(R.id.message);
-        String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
-        if (authTokenLabel.length() == 0) {
-            CharSequence grantCredentialsPermissionFormat = getResources().getText(
-                    R.string.grant_credentials_permission_message_desc);
-            messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
-                    mAccount.name, accountTypeLabel));
-        } else {
-            CharSequence grantCredentialsPermissionFormat = getResources().getText(
-                    R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
-            messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
-                    authTokenLabel, mAccount.name, accountTypeLabel));
-        }
+        final Bundle extras = getIntent().getExtras();
+        mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
+        mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+        mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+        final String accountTypeLabel = extras.getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+        final String[] packages = extras.getStringArray(EXTRAS_PACKAGES);
+        final String authTokenLabel = extras.getString(EXTRAS_AUTH_TOKEN_LABEL);
 
-        String[] packageLabels = new String[packages.length];
+        findViewById(R.id.allow_button).setOnClickListener(this);
+        findViewById(R.id.deny_button).setOnClickListener(this);
+
+        LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list);
+
         final PackageManager pm = getPackageManager();
-        for (int i = 0; i < packages.length; i++) {
+        for (String pkg : packages) {
+            String packageLabel;
             try {
-                packageLabels[i] =
-                        pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+                packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
             } catch (PackageManager.NameNotFoundException e) {
-                packageLabels[i] = packages[i];
+                packageLabel = pkg;
             }
+            packagesListView.addView(newPackageView(packageLabel));
         }
-        ((ListView) findViewById(R.id.packages_list)).setAdapter(
-                new PackagesArrayAdapter(this, packageLabels));
+
+        ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+        ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+        TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
+        if (TextUtils.isEmpty(authTokenLabel)) {
+            authTokenTypeView.setVisibility(View.GONE);
+        } else {
+            authTokenTypeView.setText(authTokenLabel);
+        }
+    }
+
+    private View newPackageView(String packageLabel) {
+        View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+        ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+        return view;
     }
 
     public void onClick(View v) {
+        final AccountManagerService accountManagerService = AccountManagerService.getSingleton();
         switch (v.getId()) {
-            case R.id.allow:
-                AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
-                        mUid);
+            case R.id.allow_button:
+                accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid);
                 Intent result = new Intent();
                 result.putExtra("retry", true);
                 setResult(RESULT_OK, result);
                 setAccountAuthenticatorResult(result.getExtras());
                 break;
 
-            case R.id.deny:
-                AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
-                        mUid);
+            case R.id.deny_button:
+                accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid);
                 setResult(RESULT_CANCELED);
                 break;
         }
@@ -110,63 +118,20 @@
     }
 
     /**
-     * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+     * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+     * result isn't present.
      */
     public void finish() {
         Intent intent = getIntent();
-        AccountAuthenticatorResponse accountAuthenticatorResponse =
-                intent.getParcelableExtra(EXTRAS_RESPONSE);
-        if (accountAuthenticatorResponse != null) {
+        AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE);
+        if (response != null) {
             // send the result bundle back if set, otherwise send an error.
             if (mResultBundle != null) {
-                accountAuthenticatorResponse.onResult(mResultBundle);
+                response.onResult(mResultBundle);
             } else {
-                accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+                response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
             }
         }
         super.finish();
     }
-
-    private static class PackagesArrayAdapter extends ArrayAdapter<String> {
-        protected LayoutInflater mInflater;
-        private static final int mResource = R.layout.simple_list_item_1;
-
-        public PackagesArrayAdapter(Context context, String[] items) {
-            super(context, mResource, items);
-            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        }
-
-        static class ViewHolder {
-            TextView label;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            // A ViewHolder keeps references to children views to avoid unneccessary calls
-            // to findViewById() on each row.
-            ViewHolder holder;
-
-            // When convertView is not null, we can reuse it directly, there is no need
-            // to reinflate it. We only inflate a new View when the convertView supplied
-            // by ListView is null.
-            if (convertView == null) {
-                convertView = mInflater.inflate(mResource, null);
-
-                // Creates a ViewHolder and store references to the two children views
-                // we want to bind data to.
-                holder = new ViewHolder();
-                holder.label = (TextView) convertView.findViewById(R.id.text1);
-
-                convertView.setTag(holder);
-            } else {
-                // Get the ViewHolder back to get fast access to the TextView
-                // and the ImageView.
-                holder = (ViewHolder) convertView.getTag();
-            }
-
-            holder.label.setText(getItem(position));
-
-            return convertView;
-        }
-    }
 }
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index f48f150..305ee6a 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -70,6 +70,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.DropBox;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
@@ -93,6 +94,8 @@
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
 
+import com.android.internal.os.IDropBoxService;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -182,6 +185,7 @@
     private ClipboardManager mClipboardManager = null;
     private boolean mRestricted;
     private AccountManager mAccountManager; // protected by mSync
+    private DropBox mDropBox = null;
 
     private final Object mSync = new Object();
 
@@ -896,6 +900,8 @@
             return getClipboardManager();
         } else if (WALLPAPER_SERVICE.equals(name)) {
             return getWallpaperManager();
+        } else if (DROPBOX_SERVICE.equals(name)) {
+            return getDropBox();
         }
 
         return null;
@@ -1045,7 +1051,7 @@
         }
         return mVibrator;
     }
-  
+
     private AudioManager getAudioManager()
     {
         if (mAudioManager == null) {
@@ -1054,6 +1060,17 @@
         return mAudioManager;
     }
 
+    private DropBox getDropBox() {
+        synchronized (mSync) {
+            if (mDropBox == null) {
+                IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+                IDropBoxService service = IDropBoxService.Stub.asInterface(b);
+                mDropBox = new DropBox(service);
+            }
+        }
+        return mDropBox;
+    }
+
     @Override
     public int checkPermission(String permission, int pid, int uid) {
         if (permission == null) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 4684f45..45c33ef 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -46,7 +46,7 @@
  */
 public final class BluetoothAdapter {
     private static final String TAG = "BluetoothAdapter";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;  //STOPSHIP: Remove excess logging
 
     /**
      * Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index eba8715..9866862 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -747,4 +747,8 @@
     public void writeSyncDataBytes(Account account, byte[] data) {
         mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
     }
+
+    protected ContentProvider getSyncStateProvider() {
+        return mSyncState.asContentProvider();
+    }
 }
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index fb6091a..0db6155 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -21,6 +21,7 @@
 import android.os.Process;
 import android.os.NetStat;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.EventLog;
 
 import java.util.concurrent.atomic.AtomicInteger;
@@ -117,6 +118,12 @@
                 }
             }
         }
+
+        public void initialize(Account account, String authority) throws RemoteException {
+            Bundle extras = new Bundle();
+            extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+            startSync(null, authority, account, extras);
+        }
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8f1c671..b4ab408 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1309,7 +1309,7 @@
      * @see #getSystemService
      */
     public static final String APPWIDGET_SERVICE = "appwidget";
-    
+
     /**
      * Use with {@link #getSystemService} to retrieve an
      * {@blink android.backup.IBackupManager IBackupManager} for communicating
@@ -1319,7 +1319,16 @@
      * @see #getSystemService
      */
     public static final String BACKUP_SERVICE = "backup";
-    
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@blink android.os.DropBox DropBox} instance for recording
+     * diagnostic logs.
+     * @hide
+     * @see #getSystemService
+     */
+    public static final String DROPBOX_SERVICE = "dropbox";
+
     /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
index 4660527..dd9d14e 100644
--- a/core/java/android/content/ISyncAdapter.aidl
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -44,4 +44,12 @@
      * @param syncContext the ISyncContext that was passed to {@link #startSync}
      */
     void cancelSync(ISyncContext syncContext);
+
+    /**
+     * Initialize the SyncAdapter for this account and authority.
+     *
+     * @param account the account that should be synced
+     * @param authority the authority that should be synced
+     */
+    void initialize(in Account account, String authority);
 }
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
index 88dc332..af1634e 100644
--- a/core/java/android/content/SyncAdapter.java
+++ b/core/java/android/content/SyncAdapter.java
@@ -38,6 +38,12 @@
         public void cancelSync(ISyncContext syncContext) throws RemoteException {
             SyncAdapter.this.cancelSync();
         }
+
+        public void initialize(Account account, String authority) throws RemoteException {
+            Bundle extras = new Bundle();
+            extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+            startSync(null, authority, account, extras);
+        }
     }
 
     Transport mTransport = new Transport();
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 587586d..cc914c0 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -56,7 +56,9 @@
         if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
         try {
             mLastHeartbeatSendTime = now;
-            mSyncContext.sendHeartbeat();
+            if (mSyncContext != null) {
+                mSyncContext.sendHeartbeat();
+            }
         } catch (RemoteException e) {
             // this should never happen
         }
@@ -64,13 +66,15 @@
 
     public void onFinished(SyncResult result) {
         try {
-            mSyncContext.onFinished(result);
+            if (mSyncContext != null) {
+                mSyncContext.onFinished(result);
+            }
         } catch (RemoteException e) {
             // this should never happen
         }
     }
 
     public IBinder getSyncContextBinder() {
-        return mSyncContext.asBinder();
+        return (mSyncContext == null) ? null : mSyncContext.asBinder();
     }
 }
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index ba18615..1580c66 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -544,6 +544,46 @@
         return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
     }
 
+    private void initializeSyncAdapter(Account account, String authority) {
+        SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
+        RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+                mSyncAdapters.getServiceInfo(syncAdapterType);
+        if (syncAdapterInfo == null) {
+            Log.w(TAG, "can't find a sync adapter for " + syncAdapterType);
+            return;
+        }
+
+        Intent intent = new Intent();
+        intent.setAction("android.content.SyncAdapter");
+        intent.setComponent(syncAdapterInfo.componentName);
+        mContext.bindService(intent, new InitializerServiceConnection(account, authority),
+                Context.BIND_AUTO_CREATE);
+    }
+
+    private class InitializerServiceConnection implements ServiceConnection {
+        private final Account mAccount;
+        private final String mAuthority;
+
+        public InitializerServiceConnection(Account account, String authority) {
+            mAccount = account;
+            mAuthority = authority;
+        }
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
+            } catch (RemoteException e) {
+                // doesn't matter, we will retry again later
+            } finally {
+                mContext.unbindService(this);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            mContext.unbindService(this);
+        }
+    }
+
     /**
      * Returns whether or not sync is enabled.  Sync can be enabled by
      * setting the system property "ro.config.sync" to the value "yes".
@@ -686,36 +726,34 @@
                         continue;
                     }
 
-                    // make this an initialization sync if the isSyncable state is unknown
-                    Bundle extrasCopy = extras;
-                    long delayCopy = delay;
+                    // initialize the SyncAdapter if the isSyncable state is unknown
                     if (isSyncable < 0) {
-                        extrasCopy = new Bundle(extras);
-                        extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
-                        delayCopy = -1; // expedite this
-                    } else {
-                        final boolean syncAutomatically = masterSyncAutomatically
-                                && mSyncStorageEngine.getSyncAutomatically(account, authority);
-                        boolean syncAllowed =
-                                manualSync || (backgroundDataUsageAllowed && syncAutomatically);
-                        if (!syncAllowed) {
-                            if (isLoggable) {
-                                Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
-                                        + " is not allowed, dropping request");
-                            }
-                            continue;
-                        }
+                        initializeSyncAdapter(account, authority);
+                        continue;
                     }
+
+                    final boolean syncAutomatically = masterSyncAutomatically
+                            && mSyncStorageEngine.getSyncAutomatically(account, authority);
+                    boolean syncAllowed =
+                            manualSync || (backgroundDataUsageAllowed && syncAutomatically);
+                    if (!syncAllowed) {
+                        if (isLoggable) {
+                            Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+                                    + " is not allowed, dropping request");
+                        }
+                        continue;
+                    }
+
                     if (isLoggable) {
                         Log.v(TAG, "scheduleSync:"
-                                + " delay " + delayCopy
+                                + " delay " + delay
                                 + ", source " + source
                                 + ", account " + account
                                 + ", authority " + authority
-                                + ", extras " + extrasCopy);
+                                + ", extras " + extras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account, source, authority, extrasCopy, delayCopy));
+                            new SyncOperation(account, source, authority, extras, delay));
                 }
             }
         }
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2d39e39..b8e17da 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -94,7 +94,6 @@
      */
     private static final String HTTP_CONNECTION = "http.connection";
 
-    RequestQueue.ConnectionManager mConnectionManager;
     RequestFeeder mRequestFeeder;
 
     /**
@@ -104,11 +103,9 @@
     private byte[] mBuf;
 
     protected Connection(Context context, HttpHost host,
-                         RequestQueue.ConnectionManager connectionManager,
                          RequestFeeder requestFeeder) {
         mContext = context;
         mHost = host;
-        mConnectionManager = connectionManager;
         mRequestFeeder = requestFeeder;
 
         mCanPersist = false;
@@ -124,18 +121,15 @@
      * necessary
      */
     static Connection getConnection(
-            Context context, HttpHost host,
-            RequestQueue.ConnectionManager connectionManager,
+            Context context, HttpHost host, HttpHost proxy,
             RequestFeeder requestFeeder) {
 
         if (host.getSchemeName().equals("http")) {
-            return new HttpConnection(context, host, connectionManager,
-                                      requestFeeder);
+            return new HttpConnection(context, host, requestFeeder);
         }
 
         // Otherwise, default to https
-        return new HttpsConnection(context, host, connectionManager,
-                                   requestFeeder);
+        return new HttpsConnection(context, host, proxy, requestFeeder);
     }
 
     /**
@@ -338,7 +332,7 @@
                 mRequestFeeder.requeueRequest(tReq);
                 empty = false;
             }
-            if (empty) empty = mRequestFeeder.haveRequest(mHost);
+            if (empty) empty = !mRequestFeeder.haveRequest(mHost);
         }
         return empty;
     }
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 0b30e58..32191d2 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -108,24 +108,11 @@
                 if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
                                             request.mHost + " " + request );
 
-                HttpHost proxy = mConnectionManager.getProxyHost();
-
-                HttpHost host;
-                if (false) {
-                    // Allow https proxy
-                    host = proxy == null ? request.mHost : proxy;
-                } else {
-                    // Disallow https proxy -- tmob proxy server
-                    // serves a request loop for https reqs
-                    host = (proxy == null ||
-                            request.mHost.getSchemeName().equals("https")) ?
-                            request.mHost : proxy;
-                }
-                mConnection = mConnectionManager.getConnection(mContext, host);
+                mConnection = mConnectionManager.getConnection(mContext,
+                        request.mHost);
                 mConnection.processRequests(request);
                 if (mConnection.getCanPersist()) {
-                    if (!mConnectionManager.recycleConnection(host,
-                                mConnection)) {
+                    if (!mConnectionManager.recycleConnection(mConnection)) {
                         mConnection.closeConnection();
                     }
                 } else {
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 8b12d0b..6df86bf 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -35,9 +35,8 @@
 class HttpConnection extends Connection {
 
     HttpConnection(Context context, HttpHost host,
-                   RequestQueue.ConnectionManager connectionManager,
                    RequestFeeder requestFeeder) {
-        super(context, host, connectionManager, requestFeeder);
+        super(context, host, requestFeeder);
     }
 
     /**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 8a69d0d..f735f3d 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -131,13 +131,16 @@
      */
     private boolean mAborted = false;
 
+    // Used when connecting through a proxy.
+    private HttpHost mProxyHost;
+
     /**
      * Contructor for a https connection.
      */
-    HttpsConnection(Context context, HttpHost host,
-                    RequestQueue.ConnectionManager connectionManager,
+    HttpsConnection(Context context, HttpHost host, HttpHost proxy,
                     RequestFeeder requestFeeder) {
-        super(context, host, connectionManager, requestFeeder);
+        super(context, host, requestFeeder);
+        mProxyHost = proxy;
     }
 
     /**
@@ -159,8 +162,7 @@
     AndroidHttpClientConnection openConnection(Request req) throws IOException {
         SSLSocket sslSock = null;
 
-        HttpHost proxyHost = mConnectionManager.getProxyHost();
-        if (proxyHost != null) {
+        if (mProxyHost != null) {
             // If we have a proxy set, we first send a CONNECT request
             // to the proxy; if the proxy returns 200 OK, we negotiate
             // a secure connection to the target server via the proxy.
@@ -172,7 +174,7 @@
             Socket proxySock = null;
             try {
                 proxySock = new Socket
-                    (proxyHost.getHostName(), proxyHost.getPort());
+                    (mProxyHost.getHostName(), mProxyHost.getPort());
 
                 proxySock.setSoTimeout(60 * 1000);
 
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 190ae7a..77cd544 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -42,15 +42,13 @@
     private WebAddress    mUri;
     private String        mMethod;
     private Map<String, String> mHeaders;
-
     private RequestQueue  mRequestQueue;
-
     private Request       mRequest;
-
     private InputStream   mBodyProvider;
     private int           mBodyLength;
-
     private int           mRedirectCount = 0;
+    // Used only with synchronous requests.
+    private Connection    mConnection;
 
     private final static String AUTHORIZATION_HEADER = "Authorization";
     private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
@@ -81,6 +79,19 @@
     }
 
     /**
+     * Creates a new request session with a given Connection. This connection
+     * is used during a synchronous load to handle this request.
+     */
+    public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+            String method, Map<String, String> headers,
+            InputStream bodyProvider, int bodyLength, Request request,
+            Connection conn) {
+        this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+                request);
+        mConnection = conn;
+    }
+
+    /**
      * Cancels this request
      */
     public void cancel() {
@@ -262,6 +273,12 @@
         mRequest.waitUntilComplete();
     }
 
+    public void processRequest() {
+        if (mConnection != null) {
+            mConnection.processRequests(mRequest);
+        }
+    }
+
     /**
      * @return Digest-scheme authentication response.
      */
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index 875caa0..84b6487 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -171,16 +171,17 @@
         }
 
         public Connection getConnection(Context context, HttpHost host) {
+            host = RequestQueue.this.determineHost(host);
             Connection con = mIdleCache.getConnection(host);
             if (con == null) {
                 mTotalConnection++;
-                con = Connection.getConnection(
-                        mContext, host, this, RequestQueue.this);
+                con = Connection.getConnection(mContext, host, mProxyHost,
+                        RequestQueue.this);
             }
             return con;
         }
-        public boolean recycleConnection(HttpHost host, Connection connection) {
-            return mIdleCache.cacheConnection(host, connection);
+        public boolean recycleConnection(Connection connection) {
+            return mIdleCache.cacheConnection(connection.getHost(), connection);
         }
 
     }
@@ -342,6 +343,66 @@
                 req);
     }
 
+    private static class SyncFeeder implements RequestFeeder {
+        // This is used in the case where the request fails and needs to be
+        // requeued into the RequestFeeder.
+        private Request mRequest;
+        SyncFeeder() {
+        }
+        public Request getRequest() {
+            Request r = mRequest;
+            mRequest = null;
+            return r;
+        }
+        public Request getRequest(HttpHost host) {
+            return getRequest();
+        }
+        public boolean haveRequest(HttpHost host) {
+            return mRequest != null;
+        }
+        public void requeueRequest(Request r) {
+            mRequest = r;
+        }
+    }
+
+    public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+            String method, Map<String, String> headers,
+            EventHandler eventHandler, InputStream bodyProvider,
+            int bodyLength) {
+        if (HttpLog.LOGV) {
+            HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+        }
+
+        HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+        Request req = new Request(method, host, mProxyHost, uri.mPath,
+                bodyProvider, bodyLength, eventHandler, headers);
+
+        // Open a new connection that uses our special RequestFeeder
+        // implementation.
+        host = determineHost(host);
+        Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+                new SyncFeeder());
+
+        // TODO: I would like to process the request here but LoadListener
+        // needs a RequestHandle to process some messages.
+        return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+                bodyLength, req, conn);
+
+    }
+
+    // Chooses between the proxy and the request's host.
+    private HttpHost determineHost(HttpHost host) {
+        // There used to be a comment in ConnectionThread about t-mob's proxy
+        // being really bad about https. But, HttpsConnection actually looks
+        // for a proxy and connects through it anyway. I think that this check
+        // is still valid because if a site is https, we will use
+        // HttpsConnection rather than HttpConnection if the proxy address is
+        // not secure.
+        return (mProxyHost == null || "https".equals(host.getSchemeName()))
+                ? host : mProxyHost;
+    }
+
     /**
      * @return true iff there are any non-active requests pending
      */
@@ -478,6 +539,6 @@
     interface ConnectionManager {
         HttpHost getProxyHost();
         Connection getConnection(Context context, HttpHost host);
-        boolean recycleConnection(HttpHost host, Connection connection);
+        boolean recycleConnection(Connection connection);
     }
 }
diff --git a/core/java/android/os/DropBox.aidl b/core/java/android/os/DropBox.aidl
new file mode 100644
index 0000000..77abd22
--- /dev/null
+++ b/core/java/android/os/DropBox.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.os;
+
+parcelable DropBox.Entry;
diff --git a/core/java/android/os/DropBox.java b/core/java/android/os/DropBox.java
new file mode 100644
index 0000000..0551dc1
--- /dev/null
+++ b/core/java/android/os/DropBox.java
@@ -0,0 +1,276 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.).  The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size.  You can think of this as a
+ * persistent, system-wide, blob-oriented "logcat".
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService}
+ * with {@link android.content.Context#DROPBOX_SERVICE}.
+ *
+ * <p>DropBox entries are not sent anywhere directly, but other system services
+ * and debugging tools may scan and upload entries for processing.
+ *
+ * {@pending}
+ */
+public class DropBox {
+    private static final String TAG = "DropBox";
+    private final IDropBoxService mService;
+
+    /** Flag value: Entry's content was deleted to save space. */
+    public static final int IS_EMPTY = 1;
+
+    /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
+    public static final int IS_TEXT = 2;
+
+    /** Flag value: Content can be decompressed with {@link GZIPOutputStream}. */
+    public static final int IS_GZIPPED = 4;
+
+    /**
+     * A single entry retrieved from the drop box.
+     * This may include a reference to a stream, so you must call
+     * {@link #close()} when you are done using it.
+     */
+    public static class Entry implements Parcelable {
+        private final String mTag;
+        private final long mTimeMillis;
+
+        private final byte[] mData;
+        private final ParcelFileDescriptor mFileDescriptor;
+        private final int mFlags;
+
+        /** Create a new empty Entry with no contents. */
+        public Entry(String tag, long millis) {
+            this(tag, millis, (Object) null, IS_EMPTY);
+        }
+
+        /** Create a new Entry with plain text contents. */
+        public Entry(String tag, long millis, String text) {
+            this(tag, millis, (Object) text.getBytes(), IS_TEXT);
+        }
+
+        /**
+         * Create a new Entry with byte array contents.
+         * The data array must not be modified after creating this entry.
+         */
+        public Entry(String tag, long millis, byte[] data, int flags) {
+            this(tag, millis, (Object) data, flags);
+        }
+
+        /**
+         * Create a new Entry with streaming data contents.
+         * Takes ownership of the ParcelFileDescriptor.
+         */
+        public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
+            this(tag, millis, (Object) data, flags);
+        }
+
+        /**
+         * Create a new Entry with the contents read from a file.
+         * The file will be read when the entry's contents are requested.
+         */
+        public Entry(String tag, long millis, File data, int flags) throws IOException {
+            this(tag, millis, (Object) ParcelFileDescriptor.open(
+                    data, ParcelFileDescriptor.MODE_READ_ONLY), flags);
+        }
+
+        /** Internal constructor for CREATOR.createFromParcel(). */
+        private Entry(String tag, long millis, Object value, int flags) {
+            if (tag == null) throw new NullPointerException();
+            if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
+
+            mTag = tag;
+            mTimeMillis = millis;
+            mFlags = flags;
+
+            if (value == null) {
+                mData = null;
+                mFileDescriptor = null;
+            } else if (value instanceof byte[]) {
+                mData = (byte[]) value;
+                mFileDescriptor = null;
+            } else if (value instanceof ParcelFileDescriptor) {
+                mData = null;
+                mFileDescriptor = (ParcelFileDescriptor) value;
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        /** Close the input stream associated with this entry. */
+        public void close() {
+            try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+        }
+
+        /** @return the tag originally attached to the entry. */
+        public String getTag() { return mTag; }
+
+        /** @return time when the entry was originally created. */
+        public long getTimeMillis() { return mTimeMillis; }
+
+        /** @return flags describing the content returned by @{link #getInputStream()}. */
+        public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
+
+        /**
+         * @param maxBytes of string to return (will truncate at this length).
+         * @return the uncompressed text contents of the entry, null if the entry is not text.
+         */
+        public String getText(int maxBytes) {
+            if ((mFlags & IS_TEXT) == 0) return null;
+            if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
+
+            InputStream is = null;
+            try {
+                is = getInputStream();
+                byte[] buf = new byte[maxBytes];
+                return new String(buf, 0, Math.max(0, is.read(buf)));
+            } catch (IOException e) {
+                return null;
+            } finally {
+                try { if (is != null) is.close(); } catch (IOException e) {}
+            }
+        }
+
+        /** @return the uncompressed contents of the entry, or null if the contents were lost */
+        public InputStream getInputStream() throws IOException {
+            InputStream is;
+            if (mData != null) {
+                is = new ByteArrayInputStream(mData);
+            } else if (mFileDescriptor != null) {
+                is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+            } else {
+                return null;
+            }
+            return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+        }
+
+        public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
+            public Entry[] newArray(int size) { return new Entry[size]; }
+            public Entry createFromParcel(Parcel in) {
+                return new Entry(
+                        in.readString(), in.readLong(), in.readValue(null), in.readInt());
+            }
+        };
+
+        public int describeContents() {
+            return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+        }
+
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeString(mTag);
+            out.writeLong(mTimeMillis);
+            if (mFileDescriptor != null) {
+                out.writeValue(mFileDescriptor);
+            } else {
+                out.writeValue(mData);
+            }
+            out.writeInt(mFlags);
+        }
+    }
+
+    /** {@hide} */
+    public DropBox(IDropBoxService service) { mService = service; }
+
+    /**
+     * Create a dummy instance for testing.  All methods will fail unless
+     * overridden with an appropriate mock implementation.  To obtain a
+     * functional instance, use {@link android.content.Context#getSystemService}.
+     */
+    protected DropBox() { mService = null; }
+
+    /**
+     * Stores human-readable text.  The data may be discarded eventually (or even
+     * immediately) if space is limited, or ignored entirely if the tag has been
+     * blocked (see {@link #isTagEnabled}).
+     *
+     * @param tag describing the type of entry being stored
+     * @param data value to store
+     */
+    public void addText(String tag, String data) {
+        try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
+    }
+
+    /**
+     * Stores binary data, which may be ignored or discarded as with {@link #addText}.
+     *
+     * @param tag describing the type of entry being stored
+     * @param data value to store
+     * @param flags describing the data
+     */
+    public void addData(String tag, byte[] data, int flags) {
+        if (data == null) throw new NullPointerException();
+        try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
+    }
+
+    /**
+     * Stores data read from a file descriptor.  The data may be ignored or
+     * discarded as with {@link #addText}.  You must close your
+     * ParcelFileDescriptor object after calling this method!
+     *
+     * @param tag describing the type of entry being stored
+     * @param fd file descriptor to read from
+     * @param flags describing the data
+     */
+    public void addFile(String tag, ParcelFileDescriptor fd, int flags) {
+        if (fd == null) throw new NullPointerException();
+        try { mService.add(new Entry(tag, 0, fd, flags)); } catch (RemoteException e) {}
+    }
+
+    /**
+     * Checks any blacklists (set in system settings) to see whether a certain
+     * tag is allowed.  Entries with disabled tags will be dropped immediately,
+     * so you can save the work of actually constructing and sending the data.
+     *
+     * @param tag that would be used in {@link #addText} or {@link #addFile}
+     * @return whether events with that tag would be accepted
+     */
+    public boolean isTagEnabled(String tag) {
+        try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
+    }
+
+    /**
+     * Gets the next entry from the drop box *after* the specified time.
+     * Requires android.permission.READ_LOGS.  You must always call
+     * {@link Entry#close()} on the return value!
+     *
+     * @param tag of entry to look for, null for all tags
+     * @param msec time of the last entry seen
+     * @return the next entry, or null if there are no more entries
+     */
+    public Entry getNextEntry(String tag, long msec) {
+        try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
+    }
+
+    // TODO: It may be useful to have some sort of notification mechanism
+    // when data is added to the dropbox, for demand-driven readers --
+    // for now readers need to poll the dropbox to find new data.
+}
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
index ca41ce5..1e2ccdf 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/Constants.java
@@ -16,16 +16,47 @@
 package android.pim.vcard;
 
 /**
- * Constants used in both composer and parser.
+ * Constants used in both exporter and importer code.
  */
 /* package */ class Constants {
 
-    public static final String ATTR_TYPE = "TYPE";
-    
     public static final String VERSION_V21 = "2.1";
     public static final String VERSION_V30 = "3.0";
+
+    // The property names valid both in vCard 2.1 and 3.0.
+    public static final String PROPERTY_BEGIN = "BEGIN";
+    public static final String PROPERTY_VERSION = "VERSION";
+    public static final String PROPERTY_N = "N";
+    public static final String PROPERTY_FN = "FN";
+    public static final String PROPERTY_ADR = "ADR";
+    public static final String PROPERTY_EMAIL = "EMAIL";
+    public static final String PROPERTY_NOTE = "NOTE";
+    public static final String PROPERTY_ORG = "ORG";
+    public static final String PROPERTY_SOUND = "SOUND";  // Not fully supported.
+    public static final String PROPERTY_TEL = "TEL";
+    public static final String PROPERTY_TITLE = "TITLE";
+    public static final String PROPERTY_ROLE = "ROLE";
+    public static final String PROPERTY_PHOTO = "PHOTO";
+    public static final String PROPERTY_LOGO = "LOGO";
+    public static final String PROPERTY_URL = "URL";
+    public static final String PROPERTY_BDAY = "BDAY";  // Birthday
+    public static final String PROPERTY_END = "END";
+
+    // Valid property names not supported (not appropriately handled) by our vCard importer now.
+    public static final String PROPERTY_REV = "REV";
+    public static final String PROPERTY_AGENT = "AGENT";
+
+    // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+    public static final String PROPERTY_NAME = "NAME";
+    public static final String PROPERTY_NICKNAME = "NICKNAME";
+    public static final String PROPERTY_SORT_STRING = "SORT-STRING";
     
-    // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
+    // De-fact property values expressing phonetic names.
+    public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
+    public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
+    public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
+
+    // Properties both ContactsStruct in Eclair and de-fact vCard extensions
     // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
     public static final String PROPERTY_X_AIM = "X-AIM";
     public static final String PROPERTY_X_MSN = "X-MSN";
@@ -34,12 +65,24 @@
     public static final String PROPERTY_X_JABBER = "X-JABBER";
     public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
     public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+    // Properties only ContactsStruct has. We alse use this.
+    public static final String PROPERTY_X_QQ = "X-QQ";
+    public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
+
     // Phone number for Skype, available as usual phone.
     public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
-    // Some device emits this "X-" attribute, which is specifically invalid but should be
-    // always properly accepted, and emitted in some special case (for that device/application).
-    public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
-    
+
+    // Property for Android-specific fields.
+    public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
+
+    // Properties for DoCoMo vCard.
+    public static final String PROPERTY_X_CLASS = "X-CLASS";
+    public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
+    public static final String PROPERTY_X_NO = "X-NO";
+    public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
+
+    public static final String PARAM_TYPE = "TYPE";
+
     // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
     //
     // e.g.
@@ -52,42 +95,69 @@
     //
     // So we are currently not sure which type is the best; probably we will have to change which
     // type should be emitted depending on the device.
-    public static final String ATTR_TYPE_HOME = "HOME";
-    public static final String ATTR_TYPE_WORK = "WORK";
-    public static final String ATTR_TYPE_FAX = "FAX";
-    public static final String ATTR_TYPE_CELL = "CELL";
-    public static final String ATTR_TYPE_VOICE = "VOICE";
-    public static final String ATTR_TYPE_INTERNET = "INTERNET";
+    public static final String PARAM_TYPE_HOME = "HOME";
+    public static final String PARAM_TYPE_WORK = "WORK";
+    public static final String PARAM_TYPE_FAX = "FAX";
+    public static final String PARAM_TYPE_CELL = "CELL";
+    public static final String PARAM_TYPE_VOICE = "VOICE";
+    public static final String PARAM_TYPE_INTERNET = "INTERNET";
 
-    public static final String ATTR_TYPE_PREF = "PREF";
+    // Abbreviation of "prefered" according to vCard 2.1 specification.
+    // We interpret this value as "primary" property during import/export.
+    //
+    // Note: Both vCard specs does not mention anything about the requirement for this parameter,
+    //       but there may be some vCard importer which will get confused with more than
+    //       one "PREF"s in one property name, while Android accepts them.
+    public static final String PARAM_TYPE_PREF = "PREF";
 
-    // Phone types valid in vCard and known to ContactsContract, but not so common.
-    public static final String ATTR_TYPE_CAR = "CAR";
-    public static final String ATTR_TYPE_ISDN = "ISDN";
-    public static final String ATTR_TYPE_PAGER = "PAGER";
+    // Phone type parameters valid in vCard and known to ContactsContract, but not so common.
+    public static final String PARAM_TYPE_CAR = "CAR";
+    public static final String PARAM_TYPE_ISDN = "ISDN";
+    public static final String PARAM_TYPE_PAGER = "PAGER";
+    public static final String PARAM_TYPE_TLX = "TLX";  // Telex
 
     // Phone types existing in vCard 2.1 but not known to ContactsContract.
     // TODO: should make parser make these TYPE_CUSTOM.
-    public static final String ATTR_TYPE_MODEM = "MODEM";
-    public static final String ATTR_TYPE_MSG = "MSG";
-    public static final String ATTR_TYPE_BBS = "BBS";
-    public static final String ATTR_TYPE_VIDEO = "VIDEO";
+    public static final String PARAM_TYPE_MODEM = "MODEM";
+    public static final String PARAM_TYPE_MSG = "MSG";
+    public static final String PARAM_TYPE_BBS = "BBS";
+    public static final String PARAM_TYPE_VIDEO = "VIDEO";
 
-    // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
-    // These types are encoded to "X-" attributes when composing vCard for now.
-    // Parser passes these even if "X-" is added to the attribute.
-    public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
-    public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
-    // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
-    public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
-    public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
-    public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
-    public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
-    public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
+    // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
+    // These types are basically encoded to "X-" parameters when composing vCard.
+    // Parser passes these when "X-" is added to the parameter or not.
+    public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
+    public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+    public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+    public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+    // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
+    public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+    // vCard composer translates this type to "VOICE" Just for parsing.
+    public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
 
-    // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
+    // TYPE parameters for postal addresses.
+    public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
+    public static final String PARAM_ADR_TYPE_DOM = "DOM";
+    public static final String PARAM_ADR_TYPE_INTL = "INTL";
+
+    // TYPE parameters not officially valid but used in some vCard exporter.
+    // Do not use in composer side.
+    public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
+
+    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
     // vCard 3.0.
-    public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
+    public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
+    public interface ImportOnly {
+        public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
+        // Some device emits this "X-" parameter for expressing Google Talk,
+        // which is specifically invalid but should be always properly accepted, and emitted
+        // in some special case (for that device/application).
+        public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+    }
+
+    // TODO: Should be in ContactsContract?
+    /* package */ static final int MAX_DATA_COLUMN = 15;
 
     private Constants() {
     }
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 36e5e23..f01659e8 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -46,7 +46,6 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -55,11 +54,11 @@
  */
 public class ContactStruct {
     private static final String LOG_TAG = "vcard.ContactStruct";
-    
+
     // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
     // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
     private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-    
+
     static {
         sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
         sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
@@ -68,12 +67,10 @@
         sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
         sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
         sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
-        sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+        sImMap.put(Constants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+                Im.PROTOCOL_GOOGLE_TALK);
     }
     
-    /**
-     * @hide only for testing
-     */
     static public class PhoneData {
         public final int type;
         public final String data;
@@ -90,7 +87,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof PhoneData) {
+            if (!(obj instanceof PhoneData)) {
                 return false;
             }
             PhoneData phoneData = (PhoneData)obj;
@@ -125,7 +122,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof EmailData) {
+            if (!(obj instanceof EmailData)) {
                 return false;
             }
             EmailData emailData = (EmailData)obj;
@@ -202,7 +199,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof PostalData) {
+            if (!(obj instanceof PostalData)) {
                 return false;
             }
             PostalData postalData = (PostalData)obj;
@@ -251,87 +248,117 @@
         }
     }
     
-    /**
-     * @hide only for testing.
-     */
     static public class OrganizationData {
         public final int type;
-        public final String companyName;
-        // can be changed in some VCard format. 
-        public String positionName;
+        // non-final is Intended: we may change the values since this info is separated into
+        // two parts in vCard: "ORG" + "TITLE".
+        public String companyName;
+        public String departmentName;
+        public String titleName;
         // isPrimary is changable only when there's no appropriate one existing in
         // the original VCard.
         public boolean isPrimary;
-        public OrganizationData(int type, String companyName, String positionName,
+        public OrganizationData(int type,
+                String companyName,
+                String departmentName,
+                String titleName,
                 boolean isPrimary) {
             this.type = type;
             this.companyName = companyName;
-            this.positionName = positionName;
+            this.departmentName = departmentName;
+            this.titleName = titleName;
             this.isPrimary = isPrimary;
         }
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof OrganizationData) {
+            if (!(obj instanceof OrganizationData)) {
                 return false;
             }
             OrganizationData organization = (OrganizationData)obj;
-            return (type == organization.type && companyName.equals(organization.companyName) &&
-                    positionName.equals(organization.positionName) &&
+            return (type == organization.type &&
+                    TextUtils.equals(companyName, organization.companyName) &&
+                    TextUtils.equals(departmentName, organization.departmentName) &&
+                    TextUtils.equals(titleName, organization.titleName) &&
                     isPrimary == organization.isPrimary);
         }
-        
+
         @Override
         public String toString() {
-            return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
-                    type, companyName, positionName, isPrimary);
+            return String.format(
+                    "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+                    type, companyName, departmentName, titleName, isPrimary);
         }
     }
     
     static public class ImData {
+        public final int protocol;
+        public final String customProtocol;
         public final int type;
         public final String data;
-        public final String label;
         public final boolean isPrimary;
         
-        // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
-        public ImData(int type, String data, String label, boolean isPrimary) {
+        public ImData(int protocol, String customProtocol, int type,
+                String data, boolean isPrimary) {
+            this.protocol = protocol;
+            this.customProtocol = customProtocol;
             this.type = type;
             this.data = data;
-            this.label = label;
             this.isPrimary = isPrimary;
         }
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof ImData) {
+            if (!(obj instanceof ImData)) {
                 return false;
             }
             ImData imData = (ImData)obj;
-            return (type == imData.type && data.equals(imData.data) &&
-                    label.equals(imData.label) && isPrimary == imData.isPrimary);
+            return (type == imData.type && protocol == imData.protocol
+                    && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
+                        (imData.customProtocol == null))
+                    && (data != null ? data.equals(imData.data) : (imData.data == null))
+                    && isPrimary == imData.isPrimary);
         }
         
         @Override
         public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, isPrimary);
+            return String.format(
+                    "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+                    type, protocol, customProtocol, data, isPrimary);
         }
     }
     
-    /**
-     * @hide only for testing.
-     */
     static public class PhotoData {
         public static final String FORMAT_FLASH = "SWF";
         public final int type;
         public final String formatName;  // used when type is not defined in ContactsContract.
         public final byte[] photoBytes;
+        public final boolean isPrimary;
 
-        public PhotoData(int type, String formatName, byte[] photoBytes) {
+        public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
             this.type = type;
             this.formatName = formatName;
             this.photoBytes = photoBytes;
+            this.isPrimary = isPrimary;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PhotoData)) {
+                return false;
+            }
+            PhotoData photoData = (PhotoData)obj;
+            return (type == photoData.type &&
+                    (formatName == null ? (photoData.formatName == null) :
+                            formatName.equals(photoData.formatName)) &&
+                    (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+                    (isPrimary == photoData.isPrimary));
+        }
+
+        @Override
+        public String toString() {
+            return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+                    type, formatName, photoBytes.length, isPrimary);
         }
     }
     
@@ -342,10 +369,6 @@
         private List<String> mPropertyValueList = new ArrayList<String>();
         private byte[] mPropertyBytes;
         
-        public Property() {
-            clear();
-        }
-        
         public void setPropertyName(final String propertyName) {
             mPropertyName = propertyName;
         }
@@ -385,6 +408,7 @@
             mPropertyName = null;
             mParameterMap.clear();
             mPropertyValueList.clear();
+            mPropertyBytes = null;
         }
     }
     
@@ -417,21 +441,13 @@
     private List<ImData> mImList;
     private List<PhotoData> mPhotoList;
     private List<String> mWebsiteList;
-    
+    private List<List<String>> mAndroidCustomPropertyList;
+
     private final int mVCardType;
     private final Account mAccount;
 
-    // Each Column of four properties has ISPRIMARY field
-    // (See android.provider.Contacts)
-    // If false even after the parsing loop, we choose the first entry as a "primary"
-    // entry.
-    private boolean mPrefIsSet_Address;
-    private boolean mPrefIsSet_Phone;
-    private boolean mPrefIsSet_Email;
-    private boolean mPrefIsSet_Organization;
-
     public ContactStruct() {
-        this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+        this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
     }
 
     public ContactStruct(int vcardType) {
@@ -444,186 +460,6 @@
     }
 
     /**
-     * @hide only for testing.
-     */
-    public ContactStruct(String givenName,
-            String familyName,
-            String middleName,
-            String prefix,
-            String suffix,
-            String phoneticGivenName,
-            String pheneticFamilyName,
-            String phoneticMiddleName,
-            List<byte[]> photoBytesList,
-            List<String> notes,
-            List<PhoneData> phoneList, 
-            List<EmailData> emailList,
-            List<PostalData> postalList,
-            List<OrganizationData> organizationList,
-            List<PhotoData> photoList,
-            List<String> websiteList) {
-        this(VCardConfig.VCARD_TYPE_DEFAULT);
-        mGivenName = givenName;
-        mFamilyName = familyName;
-        mPrefix = prefix;
-        mSuffix = suffix;
-        mPhoneticGivenName = givenName;
-        mPhoneticFamilyName = familyName;
-        mPhoneticMiddleName = middleName;
-        mEmailList = emailList;
-        mPostalList = postalList;
-        mOrganizationList = organizationList;
-        mPhotoList = photoList;
-        mWebsiteList = websiteList;
-    }
-
-    // All getter methods should be used carefully, since they may change
-    // in the future as of 2009-09-24, on which I cannot be sure this structure
-    // is completely consolidated.
-    // When we are sure we will no longer change them, we'll be happy to
-    // make it complete public (withouth @hide tag)
-    //
-    // Also note that these getter methods should be used only after
-    // all properties being pushed into this object. If not, incorrect
-    // value will "be stored in the local cache and" be returned to you.
-    
-    /**
-     * @hide
-     */
-    public String getFamilyName() {
-        return mFamilyName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getGivenName() {
-        return mGivenName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getMiddleName() {
-        return mMiddleName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPrefix() {
-        return mPrefix;
-    }
-
-    /**
-     * @hide
-     */
-    public String getSuffix() {
-        return mSuffix;
-    }
-
-    /**
-     * @hide
-     */
-    public String getFullName() {
-        return mFullName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticFamilyName() {
-        return mPhoneticFamilyName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticGivenName() {
-        return mPhoneticGivenName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticMiddleName() {
-        return mPhoneticMiddleName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticFullName() {
-        return mPhoneticFullName;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<String> getNickNameList() {
-        return mNickNameList;
-    }
-
-    /**
-     * @hide
-     */
-    public String getDisplayName() {
-        if (mDisplayName == null) {
-            constructDisplayName();
-        }
-        return mDisplayName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getBirthday() {
-        return mBirthday;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<PhotoData> getPhotoList() {
-        return mPhotoList;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<String> getNotes() {
-        return mNoteList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<PhoneData> getPhoneList() {
-        return mPhoneList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<EmailData> getEmailList() {
-        return mEmailList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<PostalData> getPostalList() {
-        return mPostalList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<OrganizationData> getOrganizationList() {
-        return mOrganizationList;
-    }
-    
-    /**
      * Add a phone info to phoneList.
      * @param data phone number
      * @param type type col of content://contacts/phones
@@ -635,18 +471,24 @@
         }
         StringBuilder builder = new StringBuilder();
         String trimed = data.trim();
-        int length = trimed.length();
-        for (int i = 0; i < length; i++) {
-            char ch = trimed.charAt(i);
-            if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
-                builder.append(ch);
+        final String formattedNumber;
+        if (type == Phone.TYPE_PAGER) {
+            formattedNumber = trimed;
+        } else {
+            final int length = trimed.length();
+            for (int i = 0; i < length; i++) {
+                char ch = trimed.charAt(i);
+                if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
+                    builder.append(ch);
+                }
             }
+
+            // Use NANP in default when there's no information about locale.
+            final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
+                    PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+            formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
         }
-
-        PhoneData phoneData = new PhoneData(type,
-                PhoneNumberUtils.formatNumber(builder.toString()),
-                label, isPrimary);
-
+        PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
         mPhoneList.add(phoneData);
     }
 
@@ -666,24 +508,122 @@
     
     private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
         if (mPostalList == null) {
-            mPostalList = new ArrayList<PostalData>();
+            mPostalList = new ArrayList<PostalData>(0);
         }
         mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
     }
     
-    private void addOrganization(int type, final String companyName,
-            final String positionName, boolean isPrimary) {
+    /**
+     * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+     * {@link #handleTitleValue(String)}.
+     */
+    private void addNewOrganization(int type, final String companyName,
+            final String departmentName,
+            final String titleName, boolean isPrimary) {
         if (mOrganizationList == null) {
             mOrganizationList = new ArrayList<OrganizationData>();
         }
-        mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+        mOrganizationList.add(new OrganizationData(type, companyName,
+                departmentName, titleName, isPrimary));
     }
+
+    private static final List<String> sEmptyList = new ArrayList<String>(0);
     
-    private void addIm(int type, String data, String label, boolean isPrimary) {
+    /**
+     * Set "ORG" related values to the appropriate data. If there's more than one
+     * OrganizationData objects, this input data are attached to the last one which does not
+     * have valid values (not including empty but only null). If there's no
+     * OrganizationData object, a new OrganizationData is created, whose title is set to null.
+     */
+    private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+        if (orgList == null) {
+            orgList = sEmptyList;
+        }
+        final String companyName;
+        final String departmentName;
+        final int size = orgList.size();
+        switch (size) {
+            case 0: {
+                companyName = "";
+                departmentName = null;
+                break;
+            }
+            case 1: {
+                companyName = orgList.get(0);
+                departmentName = null;
+                break;
+            }
+            default: {  // More than 1.
+                companyName = orgList.get(0);
+                // We're not sure which is the correct string for department.
+                // In order to keep all the data, concatinate the rest of elements.
+                StringBuilder builder = new StringBuilder();
+                for (int i = 1; i < size; i++) {
+                    if (i > 1) {
+                        builder.append(' ');
+                    }
+                    builder.append(orgList.get(i));
+                }
+                departmentName = builder.toString();
+            }
+        }
+        if (mOrganizationList == null) {
+            // Create new first organization entry, with "null" title which may be
+            // added via handleTitleValue().
+            addNewOrganization(type, companyName, departmentName, null, isPrimary);
+            return;
+        }
+        for (OrganizationData organizationData : mOrganizationList) {
+            // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+            // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+            if (organizationData.companyName == null &&
+                    organizationData.departmentName == null) {
+                // Probably the "TITLE" property comes before the "ORG" property via
+                // handleTitleLine().
+                organizationData.companyName = companyName;
+                organizationData.departmentName = departmentName;
+                organizationData.isPrimary = isPrimary;
+                return;
+            }
+        }
+        // No OrganizatioData is available. Create another one, with "null" title, which may be
+        // added via handleTitleValue().
+        addNewOrganization(type, companyName, departmentName, null, isPrimary);
+    }
+
+    private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+    /**
+     * Set "title" value to the appropriate data. If there's more than one
+     * OrganizationData objects, this input is attached to the last one which does not
+     * have valid title value (not including empty but only null). If there's no
+     * OrganizationData object, a new OrganizationData is created, whose company name is
+     * set to null.
+     */
+    private void handleTitleValue(final String title) {
+        if (mOrganizationList == null) {
+            // Create new first organization entry, with "null" other info, which may be
+            // added via handleOrgValue().
+            addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+            return;
+        }
+        for (OrganizationData organizationData : mOrganizationList) {
+            if (organizationData.titleName == null) {
+                organizationData.titleName = title;
+                return;
+            }
+        }
+        // No Organization is available. Create another one, with "null" other info, which may be
+        // added via handleOrgValue().
+        addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+    }
+
+    private void addIm(int protocol, String customProtocol, int type,
+            String propValue, boolean isPrimary) {
         if (mImList == null) {
             mImList = new ArrayList<ImData>();
         }
-        mImList.add(new ImData(type, data, label, isPrimary));
+        mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
     }
     
     private void addNote(final String note) {
@@ -693,43 +633,14 @@
         mNoteList.add(note);
     }
     
-    private void addPhotoBytes(String formatName, byte[] photoBytes) {
+    private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
         if (mPhotoList == null) {
             mPhotoList = new ArrayList<PhotoData>(1);
         }
-        final PhotoData photoData = new PhotoData(0, null, photoBytes);
+        final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
         mPhotoList.add(photoData);
     }
 
-    /**
-     * Set "position" value to the appropriate data. If there's more than one
-     * OrganizationData objects, the value is set to the last one. If there's no
-     * OrganizationData object, a new OrganizationData is created, whose company name is
-     * empty.  
-     * 
-     * TODO: incomplete logic. fix this:
-     * 
-     * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
-     * know how to handle it in general cases...
-     * ----
-     * TITLE:Software Engineer
-     * ORG:Google
-     * ----
-     */
-    private void setPosition(String positionValue) {
-        if (mOrganizationList == null) {
-            mOrganizationList = new ArrayList<OrganizationData>();
-        }
-        int size = mOrganizationList.size();
-        if (size == 0) {
-            addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
-                    "", null, false);
-            size = 1;
-        }
-        OrganizationData lastData = mOrganizationList.get(size - 1);
-        lastData.positionName = positionValue;
-    }
- 
     @SuppressWarnings("fallthrough")
     private void handleNProperty(List<String> elems) {
         // Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -755,7 +666,7 @@
             mFamilyName = elems.get(0);
         }
     }
-    
+
     /**
      * Some Japanese mobile phones use this field for phonetic name,
      *  since vCard 2.1 does not have "SORT-STRING" type.
@@ -796,28 +707,36 @@
         }
         final String propValue = listToString(propValueList).trim();
         
-        if (propName.equals("VERSION")) {
+        if (propName.equals(Constants.PROPERTY_VERSION)) {
             // vCard version. Ignore this.
-        } else if (propName.equals("FN")) {
+        } else if (propName.equals(Constants.PROPERTY_FN)) {
             mFullName = propValue;
-        } else if (propName.equals("NAME") && mFullName == null) {
+        } else if (propName.equals(Constants.PROPERTY_NAME) && mFullName == null) {
             // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
             // actually exist in the real vCard data, does not exist.
             mFullName = propValue;
-        } else if (propName.equals("N")) {
+        } else if (propName.equals(Constants.PROPERTY_N)) {
             handleNProperty(propValueList);
-        } else if (propName.equals("SORT-STRING")) {
+        } else if (propName.equals(Constants.PROPERTY_SORT_STRING)) {
             mPhoneticFullName = propValue;
-        } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+        } else if (propName.equals(Constants.PROPERTY_NICKNAME) ||
+                propName.equals(Constants.ImportOnly.PROPERTY_X_NICKNAME)) {
             addNickName(propValue);
-        } else if (propName.equals("SOUND")) {
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
-            if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
-                handlePhoneticNameFromSound(propValueList);
+        } else if (propName.equals(Constants.PROPERTY_SOUND)) {
+            Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+            if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_X_IRMC_N)) {
+                // As of 2009-10-08, Parser side does not split a property value into separated
+                // values using ';' (in other words, propValueList.size() == 1),
+                // which is correct behavior from the view of vCard 2.1.
+                // But we want it to be separated, so do the separation here.
+                final List<String> phoneticNameList =
+                        VCardUtils.constructListFromValue(propValue,
+                                VCardConfig.isV30(mVCardType));
+                handlePhoneticNameFromSound(phoneticNameList);
             } else {
                 // Ignore this field since Android cannot understand what it is.
             }
-        } else if (propName.equals("ADR")) {
+        } else if (propName.equals(Constants.PROPERTY_ADR)) {
             boolean valuesAreAllEmpty = true;
             for (String value : propValueList) {
                 if (value.length() > 0) {
@@ -832,27 +751,25 @@
             int type = -1;
             String label = "";
             boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
                     typeString = typeString.toUpperCase();
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
-                        // Only first "PREF" is considered.
-                        mPrefIsSet_Address = true;
+                    if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
                         isPrimary = true;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+                    } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
                         type = StructuredPostal.TYPE_HOME;
                         label = "";
-                    } else if (typeString.equals(Constants.ATTR_TYPE_WORK) || 
-                            typeString.equalsIgnoreCase("COMPANY")) {
+                    } else if (typeString.equals(Constants.PARAM_TYPE_WORK) || 
+                            typeString.equalsIgnoreCase(Constants.PARAM_EXTRA_TYPE_COMPANY)) {
                         // "COMPANY" seems emitted by Windows Mobile, which is not
                         // specifically supported by vCard 2.1. We assume this is same
                         // as "WORK".
                         type = StructuredPostal.TYPE_WORK;
                         label = "";
-                    } else if (typeString.equals("PARCEL") || 
-                            typeString.equals("DOM") ||
-                            typeString.equals("INTL")) {
+                    } else if (typeString.equals(Constants.PARAM_ADR_TYPE_PARCEL) ||
+                            typeString.equals(Constants.PARAM_ADR_TYPE_DOM) ||
+                            typeString.equals(Constants.PARAM_ADR_TYPE_INTL)) {
                         // We do not have any appropriate way to store this information.
                     } else {
                         if (typeString.startsWith("X-") && type < 0) {
@@ -871,23 +788,21 @@
             }
 
             addPostal(type, propValueList, label, isPrimary);
-        } else if (propName.equals("EMAIL")) {
+        } else if (propName.equals(Constants.PROPERTY_EMAIL)) {
             int type = -1;
             String label = null;
             boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
                     typeString = typeString.toUpperCase();
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
-                        // Only first "PREF" is considered.
-                        mPrefIsSet_Email = true;
+                    if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
                         isPrimary = true;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+                    } else if (typeString.equals(Constants.PARAM_TYPE_HOME)) {
                         type = Email.TYPE_HOME;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
+                    } else if (typeString.equals(Constants.PARAM_TYPE_WORK)) {
                         type = Email.TYPE_WORK;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
+                    } else if (typeString.equals(Constants.PARAM_TYPE_CELL)) {
                         type = Email.TYPE_MOBILE;
                     } else {
                         if (typeString.startsWith("X-") && type < 0) {
@@ -905,50 +820,48 @@
                 type = Email.TYPE_OTHER;
             }
             addEmail(type, propValue, label, isPrimary);
-        } else if (propName.equals("ORG")) {
+        } else if (propName.equals(Constants.PROPERTY_ORG)) {
             // vCard specification does not specify other types.
-            int type = Organization.TYPE_WORK;
+            final int type = Organization.TYPE_WORK;
             boolean isPrimary = false;
-            
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
-                        // vCard specification officially does not have PREF in ORG.
-                        // This is just for safety.
-                        mPrefIsSet_Organization = true;
+                    if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
                         isPrimary = true;
                     }
                 }
             }
-
-            StringBuilder builder = new StringBuilder();
-            for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
-                builder.append(iter.next());
-                if (iter.hasNext()) {
-                    builder.append(' ');
-                }
-            }
-            addOrganization(type, builder.toString(), "", isPrimary);
-        } else if (propName.equals("TITLE")) {
-            setPosition(propValue);
-        } else if (propName.equals("ROLE")) {
-            setPosition(propValue);
-        } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
-            String formatName = null;
-            Collection<String> typeCollection = paramMap.get("TYPE");
-            if (typeCollection != null) {
-                formatName = typeCollection.iterator().next();
-            }
+            handleOrgValue(type, propValueList, isPrimary);
+        } else if (propName.equals(Constants.PROPERTY_TITLE)) {
+            handleTitleValue(propValue);
+        } else if (propName.equals(Constants.PROPERTY_ROLE)) {
+            // This conflicts with TITLE. Ignore for now...
+            // handleTitleValue(propValue);
+        } else if (propName.equals(Constants.PROPERTY_PHOTO) ||
+                propName.equals(Constants.PROPERTY_LOGO)) {
             Collection<String> paramMapValue = paramMap.get("VALUE");
             if (paramMapValue != null && paramMapValue.contains("URL")) {
                 // Currently we do not have appropriate example for testing this case.
             } else {
-                addPhotoBytes(formatName, propBytes);
+                final Collection<String> typeCollection = paramMap.get("TYPE");
+                String formatName = null;
+                boolean isPrimary = false;
+                if (typeCollection != null) {
+                    for (String typeValue : typeCollection) {
+                        if (Constants.PARAM_TYPE_PREF.equals(typeValue)) {
+                            isPrimary = true;
+                        } else if (formatName == null){
+                            formatName = typeValue;
+                        }
+                    }
+                }
+                addPhotoBytes(formatName, propBytes, isPrimary);
             }
-        } else if (propName.equals("TEL")) {
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
-            Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+        } else if (propName.equals(Constants.PROPERTY_TEL)) {
+            final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
+            final Object typeObject =
+                VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
             final int type;
             final String label;
             if (typeObject instanceof Integer) {
@@ -960,9 +873,7 @@
             }
             
             final boolean isPrimary;
-            if (!mPrefIsSet_Phone && typeCollection != null &&
-                    typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
-                mPrefIsSet_Phone = true;
+            if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
                 isPrimary = true;
             } else {
                 isPrimary = false;
@@ -970,53 +881,59 @@
             addPhone(type, propValue, label, isPrimary);
         } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
             // The phone number available via Skype.
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
             // XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization)
             int type = Phone.TYPE_OTHER;
             final String label = null;
             final boolean isPrimary;
-            if (!mPrefIsSet_Phone && typeCollection != null &&
-                    typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
-                mPrefIsSet_Phone = true;
+            if (typeCollection != null && typeCollection.contains(Constants.PARAM_TYPE_PREF)) {
                 isPrimary = true;
             } else {
                 isPrimary = false;
             }
             addPhone(type, propValue, label, isPrimary);
-        } else if (sImMap.containsKey(propName)){
-            int type = sImMap.get(propName);
+        } else if (sImMap.containsKey(propName)) {
+            final int protocol = sImMap.get(propName);
             boolean isPrimary = false;
-            final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            int type = -1;
+            final Collection<String> typeCollection = paramMap.get(Constants.PARAM_TYPE);
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+                    if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
                         isPrimary = true;
-                    } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
-                        type = Phone.TYPE_HOME;
-                    } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
-                        type = Phone.TYPE_WORK;
+                    } else if (type < 0) {
+                        if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_HOME)) {
+                            type = Im.TYPE_HOME;
+                        } else if (typeString.equalsIgnoreCase(Constants.PARAM_TYPE_WORK)) {
+                            type = Im.TYPE_WORK;
+                        }
                     }
                 }
             }
             if (type < 0) {
                 type = Phone.TYPE_HOME;
             }
-            addIm(type, propValue, null, isPrimary);
-        } else if (propName.equals("NOTE")) {
+            addIm(protocol, null, type, propValue, isPrimary);
+        } else if (propName.equals(Constants.PROPERTY_NOTE)) {
             addNote(propValue);
-        } else if (propName.equals("URL")) {
+        } else if (propName.equals(Constants.PROPERTY_URL)) {
             if (mWebsiteList == null) {
                 mWebsiteList = new ArrayList<String>(1);
             }
             mWebsiteList.add(propValue);
-        } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
-            mPhoneticGivenName = propValue;
-        } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
-            mPhoneticMiddleName = propValue;
-        } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
-            mPhoneticFamilyName = propValue;
-        } else if (propName.equals("BDAY")) {
+        } else if (propName.equals(Constants.PROPERTY_BDAY)) {
             mBirthday = propValue;
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
+            mPhoneticGivenName = propValue;
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
+            mPhoneticMiddleName = propValue;
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
+            mPhoneticFamilyName = propValue;
+        } else if (propName.equals(Constants.PROPERTY_X_ANDROID_CUSTOM)) {
+            final List<String> customPropertyList =
+                VCardUtils.constructListFromValue(propValue,
+                        VCardConfig.isV30(mVCardType));
+            handleAndroidCustomProperty(customPropertyList);
         /*} else if (propName.equals("REV")) {                
             // Revision of this VCard entry. I think we can ignore this.
         } else if (propName.equals("UID")) {
@@ -1044,11 +961,21 @@
         }
     }
 
+    private void handleAndroidCustomProperty(final List<String> customPropertyList) {
+        if (mAndroidCustomPropertyList == null) {
+            mAndroidCustomPropertyList = new ArrayList<List<String>>();
+        }
+        mAndroidCustomPropertyList.add(customPropertyList);
+    }
+
     /**
      * Construct the display name. The constructed data must not be null.
      */
     private void constructDisplayName() {
-        if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+        // FullName (created via "FN" or "NAME" field) is prefered.
+        if (!TextUtils.isEmpty(mFullName)) {
+            mDisplayName = mFullName;
+        } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
             StringBuilder builder = new StringBuilder();
             List<String> nameList;
             switch (VCardConfig.getNameOrderType(mVCardType)) {
@@ -1079,8 +1006,6 @@
                 }
             }
             mDisplayName = builder.toString();
-        } else if (!TextUtils.isEmpty(mFullName)) {
-            mDisplayName = mFullName;
         } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
                 TextUtils.isEmpty(mPhoneticGivenName))) {
             mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1097,33 +1022,18 @@
             mDisplayName = "";
         }
     }
-    
+
     /**
      * Consolidate several fielsds (like mName) using name candidates, 
      */
     public void consolidateFields() {
         constructDisplayName();
-        
+
         if (mPhoneticFullName != null) {
             mPhoneticFullName = mPhoneticFullName.trim();
         }
-
-        // If there is no "PREF", we choose the first entries as primary.
-        if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) {
-            mPhoneList.get(0).isPrimary = true;
-        }
-
-        if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
-            mPostalList.get(0).isPrimary = true;
-        }
-        if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
-            mEmailList.get(0).isPrimary = true;
-        }
-        if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
-            mOrganizationList.get(0).isPrimary = true;
-        }
     }
-    
+
     // From GoogleSource.java in Contacts app.
     private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
     private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
@@ -1161,7 +1071,7 @@
         }
         operationList.add(builder.build());
 
-        {
+        if (!nameFieldsAreEmpty()) {
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
             builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
             builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
@@ -1172,31 +1082,31 @@
             builder.withValue(StructuredName.PREFIX, mPrefix);
             builder.withValue(StructuredName.SUFFIX, mSuffix);
 
-            builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
-            builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
-            builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+            if (!(TextUtils.isEmpty(mPhoneticGivenName)
+                    && TextUtils.isEmpty(mPhoneticFamilyName)
+                    && TextUtils.isEmpty(mPhoneticMiddleName))) {
+                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
+                builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
+                builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+            } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
+                builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
+            }
 
             builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
             operationList.add(builder.build());
         }
 
         if (mNickNameList != null && mNickNameList.size() > 0) {
-            boolean first = true;
             for (String nickName : mNickNameList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
                 builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
                 builder.withValue(Nickname.NAME, nickName);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                    first = false;
-                }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mPhoneList != null) {
             for (PhoneData phoneData : mPhoneList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1119,34 @@
                 }
                 builder.withValue(Phone.NUMBER, phoneData.data);
                 if (phoneData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
+                    builder.withValue(Phone.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mOrganizationList != null) {
-            boolean first = true;
             for (OrganizationData organizationData : mOrganizationList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
-                // Currently, we do not use TYPE_CUSTOM.
                 builder.withValue(Organization.TYPE, organizationData.type);
-                builder.withValue(Organization.COMPANY, organizationData.companyName);
-                builder.withValue(Organization.TITLE, organizationData.positionName);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
+                if (organizationData.companyName != null) {
+                    builder.withValue(Organization.COMPANY, organizationData.companyName);
+                }
+                if (organizationData.departmentName != null) {
+                    builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+                }
+                if (organizationData.titleName != null) {
+                    builder.withValue(Organization.TITLE, organizationData.titleName);
+                }
+                if (organizationData.isPrimary) {
+                    builder.withValue(Organization.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mEmailList != null) {
             for (EmailData emailData : mEmailList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1265,12 +1179,11 @@
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-                
                 builder.withValue(Im.TYPE, imData.type);
-                if (imData.type == Im.TYPE_CUSTOM) {
-                    builder.withValue(Im.LABEL, imData.label);
+                builder.withValue(Im.PROTOCOL, imData.protocol);
+                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
                 }
-                builder.withValue(Im.DATA, imData.data);
                 if (imData.isPrimary) {
                     builder.withValue(Data.IS_PRIMARY, 1);
                 }
@@ -1282,22 +1195,19 @@
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
                 builder.withValue(Note.NOTE, note);
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mPhotoList != null) {
-            boolean first = true;
             for (PhotoData photoData : mPhotoList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
                 builder.withValue(Photo.PHOTO, photoData.photoBytes);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                    first = false;
+                if (photoData.isPrimary) {
+                    builder.withValue(Photo.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
@@ -1310,12 +1220,12 @@
                 builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
                 builder.withValue(Website.URL, website);
                 // There's no information about the type of URL in vCard.
-                // We use TYPE_HOME for safety. 
-                builder.withValue(Website.TYPE, Website.TYPE_HOME);
+                // We use TYPE_HOMEPAGE for safety. 
+                builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
                 operationList.add(builder.build());
             }
         }
-        
+
         if (!TextUtils.isEmpty(mBirthday)) {
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
             builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1325,6 +1235,36 @@
             operationList.add(builder.build());
         }
 
+        if (mAndroidCustomPropertyList != null) {
+            for (List<String> customPropertyList : mAndroidCustomPropertyList) {
+                int size = customPropertyList.size();
+                if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
+                    continue;
+                } else if (size > Constants.MAX_DATA_COLUMN + 1) {
+                    size = Constants.MAX_DATA_COLUMN + 1;
+                    customPropertyList =
+                        customPropertyList.subList(0, Constants.MAX_DATA_COLUMN + 2);
+                }
+
+                int i = 0;
+                for (final String customPropertyValue : customPropertyList) {
+                    if (i == 0) {
+                        final String mimeType = customPropertyValue;
+                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                        builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+                        builder.withValue(Data.MIMETYPE, mimeType);
+                    } else {  // 1 <= i && i <= MAX_DATA_COLUMNS  
+                        if (!TextUtils.isEmpty(customPropertyValue)) {
+                            builder.withValue("data" + i, customPropertyValue);
+                        }
+                    }
+
+                    i++;
+                }
+                operationList.add(builder.build());
+            }
+        }
+
         if (myGroupsId != null) {
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
             builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
@@ -1342,6 +1282,19 @@
         }
     }
 
+    private boolean nameFieldsAreEmpty() {
+        return (TextUtils.isEmpty(mFamilyName)
+                && TextUtils.isEmpty(mMiddleName)
+                && TextUtils.isEmpty(mGivenName)
+                && TextUtils.isEmpty(mPrefix)
+                && TextUtils.isEmpty(mSuffix)
+                && TextUtils.isEmpty(mFullName)
+                && TextUtils.isEmpty(mPhoneticFamilyName)
+                && TextUtils.isEmpty(mPhoneticMiddleName)
+                && TextUtils.isEmpty(mPhoneticGivenName)
+                && TextUtils.isEmpty(mPhoneticFullName));
+    }
+
     public boolean isIgnorable() {
         return getDisplayName().length() == 0;
     }
@@ -1364,4 +1317,99 @@
             return "";
         }
     }
+
+    // All getter methods should be used carefully, since they may change
+    // in the future as of 2009-10-05, on which I cannot be sure this structure
+    // is completely consolidated.
+    //
+    // Also note that these getter methods should be used only after
+    // all properties being pushed into this object. If not, incorrect
+    // value will "be stored in the local cache and" be returned to you.
+
+    public String getFamilyName() {
+        return mFamilyName;
+    }
+
+    public String getGivenName() {
+        return mGivenName;
+    }
+
+    public String getMiddleName() {
+        return mMiddleName;
+    }
+
+    public String getPrefix() {
+        return mPrefix;
+    }
+
+    public String getSuffix() {
+        return mSuffix;
+    }
+
+    public String getFullName() {
+        return mFullName;
+    }
+
+    public String getPhoneticFamilyName() {
+        return mPhoneticFamilyName;
+    }
+
+    public String getPhoneticGivenName() {
+        return mPhoneticGivenName;
+    }
+
+    public String getPhoneticMiddleName() {
+        return mPhoneticMiddleName;
+    }
+
+    public String getPhoneticFullName() {
+        return mPhoneticFullName;
+    }
+
+    public final List<String> getNickNameList() {
+        return mNickNameList;
+    }
+
+    public String getBirthday() {
+        return mBirthday;
+    }
+
+    public final List<String> getNotes() {
+        return mNoteList;
+    }
+
+    public final List<PhoneData> getPhoneList() {
+        return mPhoneList;
+    }
+
+    public final List<EmailData> getEmailList() {
+        return mEmailList;
+    }
+
+    public final List<PostalData> getPostalList() {
+        return mPostalList;
+    }
+
+    public final List<OrganizationData> getOrganizationList() {
+        return mOrganizationList;
+    }
+
+    public final List<ImData> getImList() {
+        return mImList;
+    }
+
+    public final List<PhotoData> getPhotoList() {
+        return mPhotoList;
+    }
+
+    public final List<String> getWebsiteList() {
+        return mWebsiteList;
+    }
+
+    public String getDisplayName() {
+        if (mDisplayName == null) {
+            constructDisplayName();
+        }
+        return mDisplayName;
+    }
 }
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 9638262..9d72c5f 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -53,6 +53,7 @@
 import java.io.OutputStreamWriter;
 import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -72,19 +73,38 @@
  * Usually, this class should be used like this.
  * </p>
  *
- * <pre class="prettyprint"> VCardComposer composer = null; try { composer = new
- * VCardComposer(context); composer.addHandler(composer.new
- * HandlerForOutputStream(outputStream)); if (!composer.init()) { // Do
- * something handling the situation. return; } while (!composer.isAfterLast()) {
- * if (mCanceled) { // Assume a user may cancel this operation during the
- * export. return; } if (!composer.createOneEntry()) { // Do something handling
- * the error situation. return; } } } finally { if (composer != null) {
- * composer.terminate(); } } </pre>
+ * <pre class="prettyprint">VCardComposer composer = null;
+ * try {
+ *     composer = new VCardComposer(context);
+ *     composer.addHandler(
+ *             composer.new HandlerForOutputStream(outputStream));
+ *     if (!composer.init()) {
+ *         // Do something handling the situation.
+ *         return;
+ *     }
+ *     while (!composer.isAfterLast()) {
+ *         if (mCanceled) {
+ *             // Assume a user may cancel this operation during the export.
+ *             return;
+ *         }
+ *         if (!composer.createOneEntry()) {
+ *             // Do something handling the error situation.
+ *             return;
+ *         }
+ *     }
+ * } finally {
+ *     if (composer != null) {
+ *         composer.terminate();
+ *     }
+ * } </pre>
  */
 public class VCardComposer {
     private static final String LOG_TAG = "vcard.VCardComposer";
 
-    private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+    // TODO: Should be configurable?
+    public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+    public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+    public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
 
     public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
         "Failed to get database information";
@@ -95,28 +115,84 @@
     public static final String FAILURE_REASON_NOT_INITIALIZED =
         "The vCard composer object is not correctly initialized";
 
+    /** Should be visible only from developers... (no need to translate, hopefully) */
+    public static final String FAILURE_REASON_UNSUPPORTED_URI =
+        "The Uri vCard composer received is not supported by the composer.";
+
     public static final String NO_ERROR = "No error";
 
+    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+    // Property for call log entry
+    private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
+    private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
+    private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
+    private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
+
+    private static final String VCARD_DATA_VCARD = "VCARD";
+    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+    private static final String VCARD_PARAM_SEPARATOR = ";";
+    private static final String VCARD_END_OF_LINE = "\r\n";
+    private static final String VCARD_DATA_SEPARATOR = ":";
+    private static final String VCARD_ITEM_SEPARATOR = ";";
+    private static final String VCARD_WS = " ";
+    private static final String VCARD_PARAM_EQUAL = "=";
+
+    private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
+
+    private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+    private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+
+    private static final String SHIFT_JIS = "SHIFT_JIS";
+    private static final String UTF_8 = "UTF-8";
+
+    /**
+     * Special URI for testing.
+     */
+    public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
+    public static final Uri VCARD_TEST_AUTHORITY_URI =
+        Uri.parse("content://" + VCARD_TEST_AUTHORITY);
+    public static final Uri CONTACTS_TEST_CONTENT_URI =
+        Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
+
     private static final Uri sDataRequestUri;
+    private static final Map<Integer, String> sImMap;
+
+    /**
+     * See the comment in {@link VCardConfig#FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES}.
+     */
+    private static final Set<String> sPrimaryPropertyNameSet;
 
     static {
         Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
         builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
         sDataRequestUri = builder.build();
+        sImMap = new HashMap<Integer, String>();
+        sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+        sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+        sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+        sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+        sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+        sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+        // Google talk is a special case.
+
+        // TODO: incomplete. Implement properly
+        sPrimaryPropertyNameSet = new HashSet<String>();
+        sPrimaryPropertyNameSet.add(Constants.PROPERTY_N);
+        sPrimaryPropertyNameSet.add(Constants.PROPERTY_FN);
+        sPrimaryPropertyNameSet.add(Constants.PROPERTY_SOUND);
     }
 
     public static interface OneEntryHandler {
         public boolean onInit(Context context);
-
         public boolean onEntryCreated(String vcard);
-
         public void onTerminate();
     }
 
     /**
      * <p>
-     * An useful example handler, which emits VCard String to outputstream one
-     * by one.
+     * An useful example handler, which emits VCard String to outputstream one by one.
      * </p>
      * <p>
      * The input OutputStream object is closed() on {{@link #onTerminate()}.
@@ -211,65 +287,6 @@
         }
     }
 
-    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
-    private static final String VCARD_PROPERTY_ADR = "ADR";
-    private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
-    private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
-    private static final String VCARD_PROPERTY_END = "END";
-    private static final String VCARD_PROPERTY_NAME = "N";
-    private static final String VCARD_PROPERTY_FULL_NAME = "FN";
-    private static final String VCARD_PROPERTY_NOTE = "NOTE";
-    private static final String VCARD_PROPERTY_ORG = "ORG";
-    private static final String VCARD_PROPERTY_SOUND = "SOUND";
-    private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
-    private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
-    private static final String VCARD_PROPERTY_TEL = "TEL";
-    private static final String VCARD_PROPERTY_TITLE = "TITLE";
-    private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
-    private static final String VCARD_PROPERTY_VERSION = "VERSION";
-    private static final String VCARD_PROPERTY_URL = "URL";
-    private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
-    private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
-    private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
-    private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
-    // Android specific properties
-    // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
-    private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
-    // Property for call log entry
-    private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
-    private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
-    private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
-    private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
-    // Properties for DoCoMo vCard.
-    private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
-    private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
-    private static final String VCARD_PROPERTY_X_NO = "X-NO";
-    private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
-    private static final String VCARD_DATA_VCARD = "VCARD";
-    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
-    private static final String VCARD_ATTR_SEPARATOR = ";";
-    private static final String VCARD_COL_SEPARATOR = "\r\n";
-    private static final String VCARD_DATA_SEPARATOR = ":";
-    private static final String VCARD_ITEM_SEPARATOR = ";";
-    private static final String VCARD_WS = " ";
-    private static final String VCARD_ATTR_EQUAL = "=";
-
-    // Type strings are now in VCardConstants.java.
-
-    private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
-    private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
-    private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-
     private final Context mContext;
     private final int mVCardType;
     private final boolean mCareHandlerErrors;
@@ -286,34 +303,21 @@
     private final boolean mUsesDefactProperty;
     private final boolean mUsesUtf8;
     private final boolean mUsesShiftJis;
-    private final boolean mUsesQPToPrimaryProperties;
+    private final boolean mAppendTypeParamName;
+    private final boolean mRefrainsQPToPrimaryProperties;
+    private final boolean mNeedsToConvertPhoneticString;
 
     private Cursor mCursor;
     private int mIdColumn;
 
     private final String mCharsetString;
-    private final String mVCardAttributeCharset;
+    private final String mVCardCharsetParameter;
     private boolean mTerminateIsCalled;
     final private List<OneEntryHandler> mHandlerList;
 
     private String mErrorReason = NO_ERROR;
 
-    private static final Map<Integer, String> sImMap;
-
-    static {
-        sImMap = new HashMap<Integer, String>();
-        sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
-        sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
-        sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
-        sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
-        sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
-        sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
-        // Google talk is a special case.
-    }
-
-    private boolean mIsCallLogComposer = false;
-
-    private boolean mNeedPhotoForVCard = true;
+    private boolean mIsCallLogComposer;
 
     private static final String[] sContactsProjection = new String[] {
         Contacts._ID,
@@ -334,108 +338,98 @@
     private static final String FLAG_TIMEZONE_UTC = "Z";
 
     public VCardComposer(Context context) {
-        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false, true);
+        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
     }
 
-    public VCardComposer(Context context, String vcardTypeStr,
-            boolean careHandlerErrors) {
-        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
-                careHandlerErrors, false, true);
+    public VCardComposer(Context context, int vcardType) {
+        this(context, vcardType, true);
     }
 
-    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
-        this(context, vcardType, careHandlerErrors, false, true);
+    public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
+        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
     }
 
     /**
      * Construct for supporting call log entry vCard composing.
-     *
-     * @param isCallLogComposer true if this composer is for creating Call Log vCard.
      */
-    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
-            boolean isCallLogComposer, boolean needPhotoInVCard) {
+    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
         mContext = context;
         mVCardType = vcardType;
         mCareHandlerErrors = careHandlerErrors;
-        mIsCallLogComposer = isCallLogComposer;
-        mNeedPhotoForVCard = needPhotoInVCard;
         mContentResolver = context.getContentResolver();
 
         mIsV30 = VCardConfig.isV30(vcardType);
         mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
         mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
-        mIsJapaneseMobilePhone = VCardConfig
-                .needsToConvertPhoneticString(vcardType);
-        mOnlyOneNoteFieldIsAvailable = VCardConfig
-                .onlyOneNoteFieldIsAvailable(vcardType);
-        mUsesAndroidProperty = VCardConfig
-                .usesAndroidSpecificProperty(vcardType);
+        mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+        mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
+        mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
         mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
         mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
         mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
-        mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
+        mRefrainsQPToPrimaryProperties = VCardConfig.refrainsQPToPrimaryProperties(vcardType);
+        mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+        mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
         mHandlerList = new ArrayList<OneEntryHandler>();
 
         if (mIsDoCoMo) {
-            mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+            String charset;
+            try {
+                charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+            } catch (UnsupportedCharsetException e) {
+                Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+                charset = SHIFT_JIS;
+            }
+            mCharsetString = charset;
             // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
             // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
             // Android, not shown to the public).
-            mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+            mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
         } else if (mUsesShiftJis) {
-            mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
-            mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+            String charset;
+            try {
+                charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+            } catch (UnsupportedCharsetException e) {
+                Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+                charset = SHIFT_JIS;
+            }
+            mCharsetString = charset;
+            mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
         } else {
-            mCharsetString = "UTF-8";
-            mVCardAttributeCharset = "CHARSET=UTF-8";
+            mCharsetString = UTF_8;
+            mVCardCharsetParameter = "CHARSET=" + UTF_8;
         }
     }
 
     /**
-     * This static function is to compose vCard for phone own number
-     */
-    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
-            String phoneNumber, boolean vcardVer21) {
-        final StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        if (!vcardVer21) {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
-        } else {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
-        }
-
-        boolean needCharset = false;
-        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
-            needCharset = true;
-        }
-        // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
-        appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
-        appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
-        String label = Integer.toString(phonetype);
-        appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
-
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
-        return builder.toString();
-    }
-
-    /**
      * Must call before {{@link #init()}.
      */
     public void addHandler(OneEntryHandler handler) {
-        mHandlerList.add(handler);
-    }
-
-    public boolean init() {
-        return init(null, null);
+        if (handler != null) {
+            mHandlerList.add(handler);
+        }
     }
 
     /**
      * @return Returns true when initialization is successful and all the other
      *          methods are available. Returns false otherwise.
      */
+    public boolean init() {
+        return init(null, null);
+    }
+
     public boolean init(final String selection, final String[] selectionArgs) {
+        return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
+    }
+
+    /**
+     * Note that this is unstable interface, may be deleted in the future.
+     */
+    public boolean init(final Uri contentUri, final String selection,
+            final String[] selectionArgs, final String sortOrder) {
+        if (contentUri == null) {
+            return false;
+        }
         if (mCareHandlerErrors) {
             List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
                     mHandlerList.size());
@@ -454,13 +448,19 @@
             }
         }
 
-        if (mIsCallLogComposer) {
-            mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
-                    selection, selectionArgs, null);
+        final String[] projection;
+        if (CallLog.Calls.CONTENT_URI.equals(contentUri)) {
+            projection = sCallLogProjection;
+            mIsCallLogComposer = true;
+        } else if (Contacts.CONTENT_URI.equals(contentUri) ||
+                CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+            projection = sContactsProjection;
         } else {
-            mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
-                    selection, selectionArgs, null);
+            mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
+            return false;
         }
+        mCursor = mContentResolver.query(
+                contentUri, projection, selection, selectionArgs, sortOrder);
 
         if (mCursor == null) {
             mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -537,89 +537,6 @@
         return true;
     }
 
-    /**
-     * Format according to RFC 2445 DATETIME type.
-     * The format is: ("%Y%m%dT%H%M%SZ").
-     */
-    private final String toRfc2455Format(final long millSecs) {
-        Time startDate = new Time();
-        startDate.set(millSecs);
-        String date = startDate.format2445();
-        return date + FLAG_TIMEZONE_UTC;
-    }
-
-    /**
-     * Try to append the property line for a call history time stamp field if possible.
-     * Do nothing if the call log type gotton from the database is invalid.
-     */
-    private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
-        // Extension for call history as defined in
-        // in the Specification for Ic Mobile Communcation - ver 1.1,
-        // Oct 2000. This is used to send the details of the call
-        // history - missed, incoming, outgoing along with date and time
-        // to the requesting device (For example, transferring phone book
-        // when connected over bluetooth)
-        //
-        // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
-        final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
-        final String callLogTypeStr;
-        switch (callLogType) {
-            case Calls.INCOMING_TYPE: {
-                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
-                break;
-            }
-            case Calls.OUTGOING_TYPE: {
-                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
-                break;
-            }
-            case Calls.MISSED_TYPE: {
-                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
-                break;
-            }
-            default: {
-                Log.w(LOG_TAG, "Call log type not correct.");
-                return;
-            }
-        }
-
-        final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
-        builder.append(VCARD_PROPERTY_X_TIMESTAMP);
-        builder.append(VCARD_ATTR_SEPARATOR);
-        appendTypeAttribute(builder, callLogTypeStr);
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(toRfc2455Format(dateAsLong));
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private String createOneCallLogEntryInternal() {
-        final StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
-        } else {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
-        }
-        String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
-        if (TextUtils.isEmpty(name)) {
-            name = mCursor.getString(NUMBER_COLUMN_INDEX);
-        }
-        final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
-        // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
-        appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
-        appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
-
-        String number = mCursor.getString(NUMBER_COLUMN_INDEX);
-        int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
-        String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
-        if (TextUtils.isEmpty(label)) {
-            label = Integer.toString(type);
-        }
-        appendVCardTelephoneLine(builder, type, label, number);
-        tryAppendCallHistoryTimeStampField(builder);
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-        return builder.toString();
-    }
-
     private String createOneEntryInternal(final String contactId) {
         final Map<String, List<ContentValues>> contentValuesListMap =
                 new HashMap<String, List<ContentValues>>();
@@ -636,8 +553,7 @@
             dataExists = entityIterator.hasNext();
             while (entityIterator.hasNext()) {
                 Entity entity = entityIterator.next();
-                for (NamedContentValues namedContentValues : entity
-                        .getSubValues()) {
+                for (NamedContentValues namedContentValues : entity.getSubValues()) {
                     ContentValues contentValues = namedContentValues.values;
                     String key = contentValues.getAsString(Data.MIMETYPE);
                     if (key != null) {
@@ -666,11 +582,11 @@
         }
 
         final StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
         if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
         } else {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
         }
 
         appendStructuredNames(builder, contentValuesListMap);
@@ -682,20 +598,18 @@
         appendWebsites(builder, contentValuesListMap);
         appendBirthday(builder, contentValuesListMap);
         appendOrganizations(builder, contentValuesListMap);
-        if (mNeedPhotoForVCard) {
-            appendPhotos(builder, contentValuesListMap);
-        }
+        appendPhotos(builder, contentValuesListMap);
         appendNotes(builder, contentValuesListMap);
-        // TODO: GroupMembership
+        // TODO: GroupMembership, Relation, Event other than birthday.
 
         if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
-            appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
-            appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
-            appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
+            appendVCardLine(builder, Constants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+            appendVCardLine(builder, Constants.PROPERTY_X_REDUCTION, "");
+            appendVCardLine(builder, Constants.PROPERTY_X_NO, "");
+            appendVCardLine(builder, Constants.PROPERTY_X_DCM_HMN_MODE, "");
         }
 
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
 
         return builder.toString();
     }
@@ -753,11 +667,11 @@
         if (contentValuesList != null && contentValuesList.size() > 0) {
             appendStructuredNamesInternal(builder, contentValuesList);
         } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
         } else if (mIsV30) {
             // vCard 3.0 requires "N" and "FN" properties.
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
+            appendVCardLine(builder, Constants.PROPERTY_FN, "");
         }
     }
 
@@ -767,51 +681,80 @@
         // may get confused with multiple "N", "FN", etc. properties, though it is valid in
         // vCard spec.
         ContentValues primaryContentValues = null;
+        ContentValues subprimaryContentValues = null;
         for (ContentValues contentValues : contentValuesList) {
+            if (contentValues == null){
+                continue;
+            }
             Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
-            if (isSuperPrimary != null && isSuperPrimary != 0) {
+            if (isSuperPrimary != null && isSuperPrimary > 0) {
                 // We choose "super primary" ContentValues.
                 primaryContentValues = contentValues;
                 break;
-            } else if (primaryContentValues == null && contentValues != null) {
-                // We choose the first ContentValues if "super primary" ContentValues does not exist.
-                primaryContentValues = contentValues;
+            } else if (primaryContentValues == null) {
+                // We choose the first "primary" ContentValues
+                // if "super primary" ContentValues does not exist.
+                Integer primary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+                if (primary != null && primary > 0) {
+                    primaryContentValues = contentValues;
+                    // Do not break, since there may be ContentValues with "super primary"
+                    // afterword.
+                } else if (subprimaryContentValues == null) {
+                    subprimaryContentValues = contentValues;
+                }
             }
         }
 
         if (primaryContentValues == null) {
-            Log.e(LOG_TAG, "All ContentValues given from database is empty.");
-            primaryContentValues = new ContentValues();
+            if (subprimaryContentValues != null) {
+                // We choose the first ContentValues if any "primary" ContentValues does not exist.
+                primaryContentValues = subprimaryContentValues;
+            } else {
+                Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+                primaryContentValues = new ContentValues();
+            }
         }
 
-        final String familyName = primaryContentValues
-                .getAsString(StructuredName.FAMILY_NAME);
-        final String middleName = primaryContentValues
-                .getAsString(StructuredName.MIDDLE_NAME);
-        final String givenName = primaryContentValues
-                .getAsString(StructuredName.GIVEN_NAME);
-        final String prefix = primaryContentValues
-                .getAsString(StructuredName.PREFIX);
-        final String suffix = primaryContentValues
-                .getAsString(StructuredName.SUFFIX);
-        final String displayName = primaryContentValues
-                .getAsString(StructuredName.DISPLAY_NAME);
+        final String familyName = primaryContentValues.getAsString(StructuredName.FAMILY_NAME);
+        final String middleName = primaryContentValues.getAsString(StructuredName.MIDDLE_NAME);
+        final String givenName = primaryContentValues.getAsString(StructuredName.GIVEN_NAME);
+        final String prefix = primaryContentValues.getAsString(StructuredName.PREFIX);
+        final String suffix = primaryContentValues.getAsString(StructuredName.SUFFIX);
+        final String displayName = primaryContentValues.getAsString(StructuredName.DISPLAY_NAME);
 
         if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+            final boolean shouldAppendCharsetParameterToName =
+                !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
+                shouldAppendCharsetParameters(Arrays.asList(
+                        familyName, givenName, middleName, prefix, suffix));
+            final boolean reallyUseQuotedPrintableToName =
+                    (!mRefrainsQPToPrimaryProperties &&
+                            !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
+                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
+                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
+                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
+                                    VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
+
+            final String formattedName;
+            if (!TextUtils.isEmpty(displayName)) {
+                formattedName = displayName;
+            } else {
+                formattedName = VCardUtils.constructNameFromElements(
+                        VCardConfig.getNameOrderType(mVCardType),
+                        familyName, middleName, givenName, prefix, suffix);
+            }
+            final boolean shouldAppendCharsetParameterToFN =
+                    !(mIsV30 && UTF_8.equalsIgnoreCase(mCharsetString)) &&
+                    shouldAppendCharsetParameter(formattedName);
+            final boolean reallyUseQuotedPrintableToFN =
+                    !mRefrainsQPToPrimaryProperties &&
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
             final String encodedFamily;
             final String encodedGiven;
             final String encodedMiddle;
             final String encodedPrefix;
             final String encodedSuffix;
-
-            final boolean reallyUseQuotedPrintableToName =
-                (mUsesQPToPrimaryProperties &&
-                    !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
-                            VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
-                            VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
-                            VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
-                            VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
             if (reallyUseQuotedPrintableToName) {
                 encodedFamily = encodeQuotedPrintable(familyName);
                 encodedGiven = encodeQuotedPrintable(givenName);
@@ -826,72 +769,79 @@
                 encodedSuffix = escapeCharacters(suffix);
             }
 
-            // N property. This order is specified by vCard spec and does not depend on countries.
-            builder.append(VCARD_PROPERTY_NAME);
-            if (shouldAppendCharsetAttribute(Arrays.asList(
-                    familyName, givenName, middleName, prefix, suffix))) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(mVCardAttributeCharset);
+            final String encodedFormattedname =
+                    (reallyUseQuotedPrintableToFN ?
+                            encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+            builder.append(Constants.PROPERTY_N);
+            if (mIsDoCoMo) {
+                if (shouldAppendCharsetParameterToName) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintableToName) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                builder.append(VCARD_DATA_SEPARATOR);
+                // DoCoMo phones require that all the elements in the "family name" field.
+                builder.append(formattedName);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(VCARD_ITEM_SEPARATOR);
+            } else {
+                if (shouldAppendCharsetParameterToName) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintableToName) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                builder.append(VCARD_DATA_SEPARATOR);
+                builder.append(encodedFamily);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(encodedGiven);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(encodedMiddle);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(encodedPrefix);
+                builder.append(VCARD_ITEM_SEPARATOR);
+                builder.append(encodedSuffix);
             }
-            if (reallyUseQuotedPrintableToName) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
-            }
-
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodedFamily);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(encodedGiven);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(encodedMiddle);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(encodedPrefix);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(encodedSuffix);
-            builder.append(VCARD_COL_SEPARATOR);
-
-            final String fullname = VCardUtils.constructNameFromElements(
-                    VCardConfig.getNameOrderType(mVCardType),
-                    encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
-            final boolean reallyUseQuotedPrintableToFullname =
-                mUsesQPToPrimaryProperties &&
-                !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname);
-
-            final String encodedFullname =
-                reallyUseQuotedPrintableToFullname ?
-                        encodeQuotedPrintable(fullname) :
-                            escapeCharacters(fullname);
+            builder.append(VCARD_END_OF_LINE);
 
             // FN property
-            builder.append(VCARD_PROPERTY_FULL_NAME);
-            if (shouldAppendCharsetAttribute(encodedFullname)) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(mVCardAttributeCharset);
+            builder.append(Constants.PROPERTY_FN);
+            if (shouldAppendCharsetParameterToFN) {
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(mVCardCharsetParameter);
             }
-            if (reallyUseQuotedPrintableToFullname) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
+            if (reallyUseQuotedPrintableToFN) {
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(VCARD_PARAM_ENCODING_QP);
             }
             builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodedFullname);
-            builder.append(VCARD_COL_SEPARATOR);
+            builder.append(encodedFormattedname);
+            builder.append(VCARD_END_OF_LINE);
         } else if (!TextUtils.isEmpty(displayName)) {
             final boolean reallyUseQuotedPrintableToDisplayName =
-                (mUsesQPToPrimaryProperties &&
+                (!mRefrainsQPToPrimaryProperties &&
                         !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
             final String encodedDisplayName =
                     reallyUseQuotedPrintableToDisplayName ?
                             encodeQuotedPrintable(displayName) :
                                 escapeCharacters(displayName);
 
-            builder.append(VCARD_PROPERTY_NAME);
-            if (shouldAppendCharsetAttribute(encodedDisplayName)) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(mVCardAttributeCharset);
+            builder.append(Constants.PROPERTY_N);
+            if (shouldAppendCharsetParameter(displayName)) {
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(mVCardCharsetParameter);
             }
             if (reallyUseQuotedPrintableToDisplayName) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(VCARD_PARAM_ENCODING_QP);
             }
             builder.append(VCARD_DATA_SEPARATOR);
             builder.append(encodedDisplayName);
@@ -899,68 +849,85 @@
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
-        } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+            builder.append(VCARD_END_OF_LINE);
+            if (mIsV30) {
+                builder.append(Constants.PROPERTY_FN);
+                // TODO: Not allowed formally...
+                if (shouldAppendCharsetParameter(displayName)) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
+                }
+                builder.append(VCARD_DATA_SEPARATOR);
+                builder.append(encodedDisplayName);
+                builder.append(VCARD_END_OF_LINE);
+            }
         } else if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+            // vCard 3.0 specification requires these fields.
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
+            appendVCardLine(builder, Constants.PROPERTY_FN, "");
+        } else if (mIsDoCoMo) {
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
         }
 
-        String phoneticFamilyName = primaryContentValues
-                .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-        String phoneticMiddleName = primaryContentValues
-                .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-        String phoneticGivenName = primaryContentValues
-                .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
-        if (!(TextUtils.isEmpty(phoneticFamilyName)
-                && TextUtils.isEmpty(phoneticMiddleName) &&
-                TextUtils.isEmpty(phoneticGivenName))) { // if not empty
-            if (mIsJapaneseMobilePhone) {
-                phoneticFamilyName = VCardUtils
-                        .toHalfWidthString(phoneticFamilyName);
-                phoneticMiddleName = VCardUtils
-                        .toHalfWidthString(phoneticMiddleName);
-                phoneticGivenName = VCardUtils
-                        .toHalfWidthString(phoneticGivenName);
+        final String phoneticFamilyName;
+        final String phoneticMiddleName;
+        final String phoneticGivenName;
+        {
+            String tmpPhoneticFamilyName =
+                primaryContentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+            String tmpPhoneticMiddleName =
+                primaryContentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+            String tmpPhoneticGivenName =
+                primaryContentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+            if (mNeedsToConvertPhoneticString) {
+                phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+                phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+                phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+            } else {
+                phoneticFamilyName = tmpPhoneticFamilyName;
+                phoneticMiddleName = tmpPhoneticMiddleName;
+                phoneticGivenName = tmpPhoneticGivenName;
             }
+        }
+        if (!(TextUtils.isEmpty(phoneticFamilyName)
+                && TextUtils.isEmpty(phoneticMiddleName)
+                && TextUtils.isEmpty(phoneticGivenName))) {
 
             if (mIsV30) {
                 final String sortString = VCardUtils
                         .constructNameFromElements(mVCardType,
-                                phoneticFamilyName,
-                                phoneticMiddleName,
-                                phoneticGivenName);
-                builder.append(VCARD_PROPERTY_SORT_STRING);
+                                phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+                builder.append(Constants.PROPERTY_SORT_STRING);
 
                 // Do not need to care about QP, since vCard 3.0 does not allow it.
                 final String encodedSortString = escapeCharacters(sortString);
-                if (shouldAppendCharsetAttribute(encodedSortString)) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
+                if (shouldAppendCharsetParameter(encodedSortString)) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
                 }
                 builder.append(VCARD_DATA_SEPARATOR);
                 builder.append(encodedSortString);
-                builder.append(VCARD_COL_SEPARATOR);
-            } else {
+                builder.append(VCARD_END_OF_LINE);
+            } else if (mIsJapaneseMobilePhone) {
                 // Note: There is no appropriate property for expressing
                 //       phonetic name in vCard 2.1, while there is in
                 //       vCard 3.0 (SORT-STRING).
-                //       We chose to use DoCoMo's way since it is supported by
+                //       We chose to use DoCoMo's way when the device is Japanese one
+                //       since it is supported by
                 //       a lot of Japanese mobile phones. This is "X-" property, so
                 //       any parser hopefully would not get confused with this.
-                builder.append(VCARD_PROPERTY_SOUND);
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+                builder.append(Constants.PROPERTY_SOUND);
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(Constants.PARAM_TYPE_X_IRMC_N);
 
                 boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticFamilyName) &&
-                              VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticMiddleName) &&
-                              VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticGivenName)));
+                    (!mRefrainsQPToPrimaryProperties
+                            && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                    phoneticFamilyName)
+                                    && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                            phoneticMiddleName)
+                                    && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                            phoneticGivenName)));
 
                 final String encodedPhoneticFamilyName;
                 final String encodedPhoneticMiddleName;
@@ -975,11 +942,11 @@
                     encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
                 }
 
-                if (shouldAppendCharsetAttribute(Arrays.asList(
+                if (shouldAppendCharsetParameters(Arrays.asList(
                         encodedPhoneticFamilyName, encodedPhoneticMiddleName,
                         encodedPhoneticGivenName))) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
                 }
                 builder.append(VCARD_DATA_SEPARATOR);
                 builder.append(encodedPhoneticFamilyName);
@@ -989,89 +956,86 @@
                 builder.append(encodedPhoneticMiddleName);
                 builder.append(VCARD_ITEM_SEPARATOR);
                 builder.append(VCARD_ITEM_SEPARATOR);
-                builder.append(VCARD_COL_SEPARATOR);
+                builder.append(VCARD_END_OF_LINE);
             }
         } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_SOUND);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+            builder.append(Constants.PROPERTY_SOUND);
+            builder.append(VCARD_PARAM_SEPARATOR);
+            builder.append(Constants.PARAM_TYPE_X_IRMC_N);
             builder.append(VCARD_DATA_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
+            builder.append(VCARD_END_OF_LINE);
         }
 
         if (mUsesDefactProperty) {
             if (!TextUtils.isEmpty(phoneticGivenName)) {
                 final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName);
                 final String encodedPhoneticGivenName;
                 if (reallyUseQuotedPrintable) {
                     encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
                 } else {
                     encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
-                if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
+                builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME);
+                if (shouldAppendCharsetParameter(encodedPhoneticGivenName)) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
                 }
                 if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(VCARD_PARAM_ENCODING_QP);
                 }
                 builder.append(VCARD_DATA_SEPARATOR);
                 builder.append(encodedPhoneticGivenName);
-                builder.append(VCARD_COL_SEPARATOR);
+                builder.append(VCARD_END_OF_LINE);
             }
             if (!TextUtils.isEmpty(phoneticMiddleName)) {
                 final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName);
                 final String encodedPhoneticMiddleName;
                 if (reallyUseQuotedPrintable) {
                     encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
                 } else {
                     encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
-                if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
+                builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+                if (shouldAppendCharsetParameter(encodedPhoneticMiddleName)) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
                 }
                 if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(VCARD_PARAM_ENCODING_QP);
                 }
                 builder.append(VCARD_DATA_SEPARATOR);
                 builder.append(encodedPhoneticMiddleName);
-                builder.append(VCARD_COL_SEPARATOR);
+                builder.append(VCARD_END_OF_LINE);
             }
             if (!TextUtils.isEmpty(phoneticFamilyName)) {
                 final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName);
                 final String encodedPhoneticFamilyName;
                 if (reallyUseQuotedPrintable) {
                     encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
                 } else {
                     encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
-                if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
+                builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME);
+                if (shouldAppendCharsetParameter(encodedPhoneticFamilyName)) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(mVCardCharsetParameter);
                 }
                 if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
+                    builder.append(VCARD_PARAM_SEPARATOR);
+                    builder.append(VCARD_PARAM_ENCODING_QP);
                 }
                 builder.append(VCARD_DATA_SEPARATOR);
                 builder.append(encodedPhoneticFamilyName);
-                builder.append(VCARD_COL_SEPARATOR);
+                builder.append(VCARD_END_OF_LINE);
             }
         }
     }
@@ -1080,45 +1044,31 @@
             final Map<String, List<ContentValues>> contentValuesListMap) {
         final List<ContentValues> contentValuesList = contentValuesListMap
                 .get(Nickname.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            final String propertyNickname;
-            if (mIsV30) {
-                propertyNickname = VCARD_PROPERTY_NICKNAME;
-            } else if (mUsesAndroidProperty) {
-                propertyNickname = VCARD_PROPERTY_X_NICKNAME;
-            } else {
-                // There's no way to add this field.
-                return;
+        if (contentValuesList == null) {
+            return;
+        }
+
+        final boolean useAndroidProperty;
+        if (mIsV30) {
+            useAndroidProperty = false;
+        } else if (mUsesAndroidProperty) {
+            useAndroidProperty = true;
+        } else {
+            // There's no way to add this field.
+            return;
+        }
+
+        for (ContentValues contentValues : contentValuesList) {
+            final String nickname = contentValues.getAsString(Nickname.NAME);
+            if (TextUtils.isEmpty(nickname)) {
+                continue;
             }
-
-            for (ContentValues contentValues : contentValuesList) {
-                final String nickname = contentValues.getAsString(Nickname.NAME);
-                if (TextUtils.isEmpty(nickname)) {
-                    continue;
-                }
-
-                final String encodedNickname;
-                final boolean reallyUseQuotedPrintable =
-                    (mUsesQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
-                if (reallyUseQuotedPrintable) {
-                    encodedNickname = encodeQuotedPrintable(nickname);
-                } else {
-                    encodedNickname = escapeCharacters(nickname);
-                }
-
-                builder.append(propertyNickname);
-                if (shouldAppendCharsetAttribute(propertyNickname)) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
-                }
-                if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedNickname);
-                builder.append(VCARD_COL_SEPARATOR);
+            if (useAndroidProperty) {
+                appendAndroidSpecificProperty(builder, Nickname.CONTENT_ITEM_TYPE,
+                        contentValues);
+            } else {
+                appendVCardLineWithCharsetAndQPDetection(builder,
+                        Constants.PROPERTY_NICKNAME, nickname);
             }
         }
     }
@@ -1133,6 +1083,9 @@
             for (ContentValues contentValues : contentValuesList) {
                 final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
                 final String label = contentValues.getAsString(Phone.LABEL);
+                final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+                final boolean isPrimary = (isPrimaryAsInteger != null ?
+                        (isPrimaryAsInteger > 0) : false);
                 String phoneNumber = contentValues.getAsString(Phone.NUMBER);
                 if (phoneNumber != null) {
                     phoneNumber = phoneNumber.trim();
@@ -1141,18 +1094,18 @@
                     continue;
                 }
                 phoneLineExists = true;
-                int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
+                int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
                 // TODO: Premature, since this allows two phone numbers which are
                 //        same from the view of phone number format (e.g. "100" v.s. "1-0-0")
                 if (!phoneSet.contains(phoneNumber)) {
                     phoneSet.add(phoneNumber);
-                    appendVCardTelephoneLine(builder, type, label, phoneNumber);
+                    appendVCardTelephoneLine(builder, type, label, phoneNumber, isPrimary);
                 }
             }
         }
 
         if (!phoneLineExists && mIsDoCoMo) {
-            appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
+            appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "", false);
         }
     }
 
@@ -1160,14 +1113,11 @@
             final Map<String, List<ContentValues>> contentValuesListMap) {
         final List<ContentValues> contentValuesList = contentValuesListMap
                 .get(Email.CONTENT_ITEM_TYPE);
+
         boolean emailAddressExists = false;
         if (contentValuesList != null) {
-            Set<String> addressSet = new HashSet<String>();
+            final Set<String> addressSet = new HashSet<String>();
             for (ContentValues contentValues : contentValuesList) {
-                Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
-                final int type = (typeAsObject != null ?
-                        typeAsObject : Email.TYPE_OTHER);
-                final String label = contentValues.getAsString(Email.LABEL);
                 String emailAddress = contentValues.getAsString(Email.DATA);
                 if (emailAddress != null) {
                     emailAddress = emailAddress.trim();
@@ -1175,16 +1125,23 @@
                 if (TextUtils.isEmpty(emailAddress)) {
                     continue;
                 }
+                Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+                final int type = (typeAsObject != null ?
+                        typeAsObject : DEFAULT_EMAIL_TYPE);
+                final String label = contentValues.getAsString(Email.LABEL);
+                Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+                final boolean isPrimary = (isPrimaryAsInteger != null ?
+                        (isPrimaryAsInteger > 0) : false);
                 emailAddressExists = true;
                 if (!addressSet.contains(emailAddress)) {
                     addressSet.add(emailAddress);
-                    appendVCardEmailLine(builder, type, label, emailAddress);
+                    appendVCardEmailLine(builder, type, label, emailAddress, isPrimary);
                 }
             }
         }
 
         if (!emailAddressExists && mIsDoCoMo) {
-            appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
+            appendVCardEmailLine(builder, Email.TYPE_HOME, "", "", false);
         }
     }
 
@@ -1199,11 +1156,11 @@
                 appendPostalsForGeneric(builder, contentValuesList);
             }
         } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_ADR);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(Constants.ATTR_TYPE_HOME);
+            builder.append(Constants.PROPERTY_ADR);
+            builder.append(VCARD_PARAM_SEPARATOR);
+            builder.append(Constants.PARAM_TYPE_HOME);
             builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
+            builder.append(VCARD_END_OF_LINE);
         }
     }
 
@@ -1241,7 +1198,10 @@
             final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
             final String label = contentValues.getAsString(StructuredPostal.LABEL);
             if (type == preferedType) {
-                appendVCardPostalLine(builder, type, label, contentValues);
+                // Note: Not sure why we need to emit "empty" line even when actual
+                //       data does not exist. There may be some reason or may not.
+                //       We keep safer side since the previous implementation did so.
+                appendVCardPostalLine(builder, type, label, contentValues, true, true);
                 return true;
             }
         }
@@ -1251,11 +1211,18 @@
     private void appendPostalsForGeneric(final StringBuilder builder,
             final List<ContentValues> contentValuesList) {
         for (ContentValues contentValues : contentValuesList) {
-            final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
-            final String label = contentValues.getAsString(StructuredPostal.LABEL);
-            if (type != null) {
-                appendVCardPostalLine(builder, type, label, contentValues);
+            if (contentValues == null) {
+                continue;
             }
+            final Integer typeAsObject = contentValues.getAsInteger(StructuredPostal.TYPE);
+            final int type = (typeAsObject != null ?
+                    typeAsObject : DEFAULT_POSTAL_TYPE);
+            final String label = contentValues.getAsString(StructuredPostal.LABEL);
+            final Integer isPrimaryAsInteger =
+                contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+            final boolean isPrimary = (isPrimaryAsInteger != null ?
+                    (isPrimaryAsInteger > 0) : false);
+            appendVCardPostalLine(builder, type, label, contentValues, isPrimary, false);
         }
     }
 
@@ -1263,24 +1230,63 @@
             final Map<String, List<ContentValues>> contentValuesListMap) {
         final List<ContentValues> contentValuesList = contentValuesListMap
                 .get(Im.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
-                String data = contentValues.getAsString(Im.DATA);
-                if (data != null) {
-                    data = data.trim();
-                }
-                if (TextUtils.isEmpty(data)) {
-                    continue;
-                }
-
-                if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
-                    if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
-                        appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
+        if (contentValuesList == null) {
+            return;
+        }
+        for (ContentValues contentValues : contentValuesList) {
+            final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+            if (protocolAsObject == null) {
+                continue;
+            }
+            final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+            if (propertyName == null) {
+                continue;
+            }
+            String data = contentValues.getAsString(Im.DATA);
+            if (data != null) {
+                data = data.trim();
+            }
+            if (TextUtils.isEmpty(data)) {
+                continue;
+            }
+            final String typeAsString;
+            {
+                final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+                switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+                    case Im.TYPE_HOME: {
+                        typeAsString = Constants.PARAM_TYPE_HOME;
+                        break;
                     }
-                    // TODO: add "X-GOOGLE TALK" case...
+                    case Im.TYPE_WORK: {
+                        typeAsString = Constants.PARAM_TYPE_WORK;
+                        break;
+                    }
+                    case Im.TYPE_CUSTOM: {
+                        final String label = contentValues.getAsString(Im.LABEL);
+                        typeAsString = (label != null ? "X-" + label : null);
+                        break;
+                    }
+                    case Im.TYPE_OTHER:  // Ignore
+                    default: {
+                        typeAsString = null;
+                        break;
+                    }
                 }
             }
+
+            List<String> parameterList = new ArrayList<String>();
+            if (!TextUtils.isEmpty(typeAsString)) {
+                parameterList.add(typeAsString);
+            }
+            final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+            final boolean isPrimary = (isPrimaryAsInteger != null ?
+                    (isPrimaryAsInteger > 0) : false);
+            if (isPrimary) {
+                parameterList.add(Constants.PARAM_TYPE_PREF);
+            }
+
+            appendVCardLineWithCharsetAndQPDetection(
+                    builder, propertyName, parameterList, data);
         }
     }
 
@@ -1288,39 +1294,84 @@
             final Map<String, List<ContentValues>> contentValuesListMap) {
         final List<ContentValues> contentValuesList = contentValuesListMap
                 .get(Website.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                String website = contentValues.getAsString(Website.URL);
-                if (website != null) {
-                    website = website.trim();
-                }
-                if (!TextUtils.isEmpty(website)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_URL, website);
-                }
+        if (contentValuesList == null) {
+            return;
+        }
+        for (ContentValues contentValues : contentValuesList) {
+            String website = contentValues.getAsString(Website.URL);
+            if (website != null) {
+                website = website.trim();
+            }
+
+            // Note: vCard 3.0 does not allow any parameter addition toward "URL"
+            //       property, while there's no document in vCard 2.1.
+            //
+            // TODO: Should we allow adding it when appropriate?
+            //       (Actually, we drop some data. Using "group.X-URL-TYPE" or something
+            //        may help)
+            if (!TextUtils.isEmpty(website)) {
+                appendVCardLine(builder, Constants.PROPERTY_URL, website);
             }
         }
     }
 
+    /**
+     * Theoretically, there must be only one birthday for each vCard entry.
+     * Also, we are afraid of some importer's parse error during its import.
+     * We emit only one birthday entry even when there are more than one.
+     */
     private void appendBirthday(final StringBuilder builder,
             final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(Event.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null && contentValuesList.size() > 0) {
-            Integer eventType = contentValuesList.get(0).getAsInteger(Event.TYPE);
+        final List<ContentValues> contentValuesList =
+                contentValuesListMap.get(Event.CONTENT_ITEM_TYPE);
+        if (contentValuesList == null) {
+            return;
+        }
+        String primaryBirthday = null;
+        String secondaryBirthday = null;
+        for (ContentValues contentValues : contentValuesList) {
+            if (contentValues == null) {
+                continue;
+            }
+            final Integer eventType = contentValues.getAsInteger(Event.TYPE);
             if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
-                return;
+                continue;
             }
-            // Theoretically, there must be only one birthday for each vCard data and
-            // we are afraid of some parse error occuring in some devices, so
-            // we emit only one birthday entry for now.
-            String birthday = contentValuesList.get(0).getAsString(Event.START_DATE);
-            if (birthday != null) {
-                birthday = birthday.trim();
+            final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+            if (birthdayCandidate == null) {
+                continue;
             }
-            if (!TextUtils.isEmpty(birthday)) {
-                appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+            final Integer isSuperPrimaryAsInteger =
+                contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+            final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+                    (isSuperPrimaryAsInteger > 0) : false);
+            if (isSuperPrimary) {
+                // "super primary" birthday should the prefered one.
+                primaryBirthday = birthdayCandidate;
+                break;
+            }
+            final Integer isPrimaryAsInteger =
+                contentValues.getAsInteger(Event.IS_PRIMARY);
+            final boolean isPrimary = (isPrimaryAsInteger != null ?
+                    (isPrimaryAsInteger > 0) : false);
+            if (isPrimary) {
+                // We don't break here since "super primary" birthday may exist later.
+                primaryBirthday = birthdayCandidate;
+            } else if (secondaryBirthday == null) {
+                // First entry is set to the "secondary" candidate.
+                secondaryBirthday = birthdayCandidate;
             }
         }
+
+        final String birthday;
+        if (primaryBirthday != null) {
+            birthday = primaryBirthday.trim();
+        } else if (secondaryBirthday != null){
+            birthday = secondaryBirthday.trim();
+        } else {
+            return;
+        }
+        appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_BDAY, birthday);
     }
 
     private void appendOrganizations(final StringBuilder builder,
@@ -1329,25 +1380,37 @@
                 .get(Organization.CONTENT_ITEM_TYPE);
         if (contentValuesList != null) {
             for (ContentValues contentValues : contentValuesList) {
-                String company = contentValues
-                        .getAsString(Organization.COMPANY);
+                String company = contentValues.getAsString(Organization.COMPANY);
                 if (company != null) {
                     company = company.trim();
                 }
-                String title = contentValues
-                        .getAsString(Organization.TITLE);
+                String department = contentValues.getAsString(Organization.DEPARTMENT);
+                if (department != null) {
+                    department = department.trim();
+                }
+                String title = contentValues.getAsString(Organization.TITLE);
                 if (title != null) {
                     title = title.trim();
                 }
 
+                StringBuilder orgBuilder = new StringBuilder();
                 if (!TextUtils.isEmpty(company)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
-                            !VCardUtils.containsOnlyPrintableAscii(company),
-                            (mUsesQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
+                    orgBuilder.append(company);
                 }
+                if (!TextUtils.isEmpty(department)) {
+                    if (orgBuilder.length() > 0) {
+                        orgBuilder.append(';');
+                    }
+                    orgBuilder.append(department);
+                }
+                final String orgline = orgBuilder.toString();
+                appendVCardLine(builder, Constants.PROPERTY_ORG, orgline,
+                        !VCardUtils.containsOnlyPrintableAscii(orgline),
+                        (mUsesQuotedPrintable &&
+                                !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
                 if (!TextUtils.isEmpty(title)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
+                    appendVCardLine(builder, Constants.PROPERTY_TITLE, title,
                             !VCardUtils.containsOnlyPrintableAscii(title),
                             (mUsesQuotedPrintable &&
                                     !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
@@ -1374,11 +1437,9 @@
                     photoType = "GIF";
                 } else if (data.length >= 4 && data[0] == (byte) 0x89
                         && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
-                    // Note: vCard 2.1 officially does not support PNG, but we
-                    // may have it
-                    // and using X- word like "X-PNG" may not let importers know
-                    // it is
-                    // PNG. So we use the String "PNG" as is...
+                    // Note: vCard 2.1 officially does not support PNG, but we may
+                    // have it and using X- word like "X-PNG" may not let importers
+                    // know it is PNG. So we use the String "PNG" as is...
                     photoType = "PNG";
                 } else if (data.length >= 2 && data[0] == (byte) 0xff
                         && data[1] == (byte) 0xd8) {
@@ -1425,7 +1486,7 @@
                 final boolean reallyUseQuotedPrintable =
                         (mUsesQuotedPrintable &&
                             !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+                appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
                         shouldAppendCharsetInfo, reallyUseQuotedPrintable);
             } else {
                 for (ContentValues contentValues : contentValuesList) {
@@ -1436,7 +1497,7 @@
                         final boolean reallyUseQuotedPrintable =
                                 (mUsesQuotedPrintable &&
                                     !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                        appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+                        appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
                                 shouldAppendCharsetInfo, reallyUseQuotedPrintable);
                     }
                 }
@@ -1444,6 +1505,33 @@
         }
     }
 
+    private void appendAndroidSpecificProperty(final StringBuilder builder,
+            final String mimeType, ContentValues contentValues) {
+        List<String> rawDataList = new ArrayList<String>();
+        rawDataList.add(mimeType);
+        final List<String> columnNameList;
+        if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            
+        } else {
+            // If you add the other field, please check all the columns are able to be
+            // converted to String.
+            //
+            // e.g. BLOB is not what we can handle here now.
+            return;
+        }
+
+        for (int i = 1; i <= Constants.MAX_DATA_COLUMN; i++) {
+            String value = contentValues.getAsString("data" + i);
+            if (value == null) {
+                value = "";
+            }
+            rawDataList.add(value);
+        }
+
+        appendVCardLineWithCharsetAndQPDetection(builder,
+                Constants.PROPERTY_X_ANDROID_CUSTOM, rawDataList);
+    }
+
     /**
      * Append '\' to the characters which should be escaped. The character set is different
      * not only between vCard 2.1 and vCard 3.0 but also among each device.
@@ -1459,7 +1547,7 @@
         final StringBuilder tmpBuilder = new StringBuilder();
         final int length = unescaped.length();
         for (int i = 0; i < length; i++) {
-            char ch = unescaped.charAt(i);
+            final char ch = unescaped.charAt(i);
             switch (ch) {
                 case ';': {
                     tmpBuilder.append('\\');
@@ -1470,7 +1558,7 @@
                     if (i + 1 < length) {
                         char nextChar = unescaped.charAt(i);
                         if (nextChar == '\n') {
-                            continue;
+                            break;
                         } else {
                             // fall through
                         }
@@ -1522,15 +1610,15 @@
     private void appendVCardPhotoLine(final StringBuilder builder,
             final String encodedData, final String photoType) {
         StringBuilder tmpBuilder = new StringBuilder();
-        tmpBuilder.append(VCARD_PROPERTY_PHOTO);
-        tmpBuilder.append(VCARD_ATTR_SEPARATOR);
+        tmpBuilder.append(Constants.PROPERTY_PHOTO);
+        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
         if (mIsV30) {
-            tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
+            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
         } else {
-            tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
+            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
         }
-        tmpBuilder.append(VCARD_ATTR_SEPARATOR);
-        appendTypeAttribute(tmpBuilder, photoType);
+        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+        appendTypeParameter(tmpBuilder, photoType);
         tmpBuilder.append(VCARD_DATA_SEPARATOR);
         tmpBuilder.append(encodedData);
 
@@ -1542,69 +1630,136 @@
             tmpBuilder.append(tmpStr.charAt(i));
             lineCount++;
             if (lineCount > 72) {
-                tmpBuilder.append(VCARD_COL_SEPARATOR);
+                tmpBuilder.append(VCARD_END_OF_LINE);
                 tmpBuilder.append(VCARD_WS);
                 lineCount = 0;
             }
         }
         builder.append(tmpBuilder.toString());
-        builder.append(VCARD_COL_SEPARATOR);
-        builder.append(VCARD_COL_SEPARATOR);
+        builder.append(VCARD_END_OF_LINE);
+        builder.append(VCARD_END_OF_LINE);
     }
 
-    private void appendVCardPostalLine(final StringBuilder builder,
-            final Integer typeAsObject, final String label,
-            final ContentValues contentValues) {
-        builder.append(VCARD_PROPERTY_ADR);
-        builder.append(VCARD_ATTR_SEPARATOR);
+    private class PostalStruct {
+        final boolean reallyUseQuotedPrintable;
+        final boolean appendCharset;
+        final String addressData;
+        public PostalStruct(final boolean reallyUseQuotedPrintable,
+                final boolean appendCharset, final String addressData) {
+            this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+            this.appendCharset = appendCharset;
+            this.addressData = addressData;
+        }
+    }
 
-        // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
-        // There may be some reason or may not be any. We keep safer side.
-        // TODO: investigate this.
-        boolean dataExists = false;
+    /**
+     * @return null when there's no information available to construct the data.
+     */
+    private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+        boolean reallyUseQuotedPrintable = false;
+        boolean appendCharset = false;
+
+        boolean dataArrayExists = false;
         String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
-        boolean actuallyUseQuotedPrintable = false;
-        boolean shouldAppendCharset = false;
         for (String data : dataArray) {
             if (!TextUtils.isEmpty(data)) {
-                dataExists = true;
-                if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
-                    shouldAppendCharset = true;
+                dataArrayExists = true;
+                if (!appendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
+                    appendCharset = true;
                 }
                 if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
-                    actuallyUseQuotedPrintable = true;
+                    reallyUseQuotedPrintable = true;
                     break;
                 }
             }
         }
 
-        int length = dataArray.length;
-        for (int i = 0; i < length; i++) {
-            String data = dataArray[i];
-            if (!TextUtils.isEmpty(data)) {
-                if (actuallyUseQuotedPrintable) {
-                    dataArray[i] = encodeQuotedPrintable(data);
+        if (dataArrayExists) {
+            StringBuffer addressBuffer = new StringBuffer();
+            boolean first = true;
+            for (String data : dataArray) {
+                if (first) {
+                    first = false;
                 } else {
-                    dataArray[i] = escapeCharacters(data);
+                    addressBuffer.append(VCARD_ITEM_SEPARATOR);
                 }
+                if (!TextUtils.isEmpty(data)) {
+                    if (reallyUseQuotedPrintable) {
+                        addressBuffer.append(encodeQuotedPrintable(data));
+                    } else {
+                        addressBuffer.append(escapeCharacters(data));
+                    }
+                }
+            }
+            return new PostalStruct(reallyUseQuotedPrintable, appendCharset,
+                    addressBuffer.toString());
+        }
+
+        String formattedAddress =
+            contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+        if (!TextUtils.isEmpty(formattedAddress)) {
+            reallyUseQuotedPrintable =
+                !VCardUtils.containsOnlyPrintableAscii(formattedAddress);
+            appendCharset =
+                !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedAddress);
+            if (reallyUseQuotedPrintable) {
+                formattedAddress = encodeQuotedPrintable(formattedAddress);
+            } else {
+                formattedAddress = escapeCharacters(formattedAddress);
+            }
+            // We use the second value ("Extended Address").
+            //
+            // adr-value    = 0*6(text-value ";") text-value
+            //              ; PO Box, Extended Address, Street, Locality, Region, Postal
+            //              ; Code, Country Name
+            StringBuffer addressBuffer = new StringBuffer();
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(formattedAddress);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            return new PostalStruct(
+                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+        }
+        return null;  // There's no data available.
+    }
+
+    private void appendVCardPostalLine(final StringBuilder builder,
+            final int type, final String label, final ContentValues contentValues,
+            final boolean isPrimary, final boolean emitLineEveryTime) {
+        final boolean reallyUseQuotedPrintable;
+        final boolean appendCharset;
+        final String addressData;
+        {
+            PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+            if (postalStruct == null) {
+                if (emitLineEveryTime) {
+                    reallyUseQuotedPrintable = false;
+                    appendCharset = false;
+                    addressData = "";
+                } else {
+                    return;
+                }
+            } else {
+                reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+                appendCharset = postalStruct.appendCharset;
+                addressData = postalStruct.addressData;
             }
         }
 
-        final int typeAsPrimitive;
-        if (typeAsObject == null) {
-            typeAsPrimitive = StructuredPostal.TYPE_OTHER;
-        } else {
-            typeAsPrimitive = typeAsObject;
+        List<String> parameterList = new ArrayList<String>();
+        if (isPrimary) {
+            parameterList.add(Constants.PARAM_TYPE_PREF);
         }
-
-        String typeAsString = null;
-        switch (typeAsPrimitive) {
+        switch (type) {
             case StructuredPostal.TYPE_HOME: {
-                typeAsString = Constants.ATTR_TYPE_HOME;
+                parameterList.add(Constants.PARAM_TYPE_HOME);
                 break;
             }
             case StructuredPostal.TYPE_WORK: {
-                typeAsString = Constants.ATTR_TYPE_WORK;
+                parameterList.add(Constants.PARAM_TYPE_WORK);
                 break;
             }
             case StructuredPostal.TYPE_CUSTOM: {
@@ -1614,9 +1769,7 @@
                     // ("IANA-token" in the vCard 3.0 is unclear...)
                     // Just  for safety, we add "X-" at the beggining of each label.
                     // Also checks the label obeys with vCard 3.0 spec.
-                    builder.append("X-");
-                    builder.append(label);
-                    builder.append(VCARD_DATA_SEPARATOR);
+                    parameterList.add("X-" + label);
                 }
                 break;
             }
@@ -1624,132 +1777,111 @@
                 break;
             }
             default: {
-                Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
+                Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
                 break;
             }
         }
 
-        // Attribute(s).
+        // Actual data construction starts from here.
+        // TODO: add a new version of appendVCardLine() for this purpose.
+        
+        builder.append(Constants.PROPERTY_ADR);
+        builder.append(VCARD_PARAM_SEPARATOR);
 
+        // Parameters
         {
-            boolean shouldAppendAttrSeparator = false;
-            if (typeAsString != null) {
-                appendTypeAttribute(builder, typeAsString);
-                shouldAppendAttrSeparator = true;
+            boolean shouldAppendParamSeparator = false;
+            if (!parameterList.isEmpty()) {
+                appendTypeParameters(builder, parameterList);
+                shouldAppendParamSeparator = true;
             }
 
-            if (dataExists) {
-                if (shouldAppendCharset) {
-                    // Strictly, vCard 3.0 does not allow exporters to emit charset information,
-                    // but we will add it since the information should be useful for importers,
-                    //
-                    // Assume no parser does not emit error with this attribute in vCard 3.0.
-                    if (shouldAppendAttrSeparator) {
-                        builder.append(VCARD_ATTR_SEPARATOR);
-                    }
-                    builder.append(mVCardAttributeCharset);
-                    shouldAppendAttrSeparator = true;
+            if (appendCharset) {
+                // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+                // but we will add it since the information should be useful for importers,
+                //
+                // Assume no parser does not emit error with this parameter in vCard 3.0.
+                if (shouldAppendParamSeparator) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
                 }
+                builder.append(mVCardCharsetParameter);
+                shouldAppendParamSeparator = true;
+            }
 
-                if (actuallyUseQuotedPrintable) {
-                    if (shouldAppendAttrSeparator) {
-                        builder.append(VCARD_ATTR_SEPARATOR);
-                    }
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                    shouldAppendAttrSeparator = true;
+            if (reallyUseQuotedPrintable) {
+                if (shouldAppendParamSeparator) {
+                    builder.append(VCARD_PARAM_SEPARATOR);
                 }
+                builder.append(VCARD_PARAM_ENCODING_QP);
+                shouldAppendParamSeparator = true;
             }
         }
 
-        // Property values.
-
         builder.append(VCARD_DATA_SEPARATOR);
-        if (dataExists) {
-            // The elements in dataArray are already encoded to quoted printable
-            // if needed.
-            // See above.
-            //
-            // TODO: in vCard 3.0, one line may become too huge. Fix this.
-            builder.append(dataArray[0]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[1]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[2]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[3]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[4]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[5]);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(dataArray[6]);
-        }
-        builder.append(VCARD_COL_SEPARATOR);
+        builder.append(addressData);
+        builder.append(VCARD_END_OF_LINE);
     }
 
     private void appendVCardEmailLine(final StringBuilder builder,
-            final Integer typeAsObject, final String label, final String data) {
-        builder.append(VCARD_PROPERTY_EMAIL);
-
-        final int typeAsPrimitive;
-        if (typeAsObject == null) {
-            typeAsPrimitive = Email.TYPE_OTHER;
-        } else {
-            typeAsPrimitive = typeAsObject;
-        }
-
+            final int type, final String label,
+            final String rawData, final boolean isPrimary) {
         final String typeAsString;
-        switch (typeAsPrimitive) {
+        switch (type) {
             case Email.TYPE_CUSTOM: {
                 // For backward compatibility.
                 // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
                 //         To support mobile type at that time, this custom label had been used.
                 if (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME
                         .equals(label)) {
-                    typeAsString = Constants.ATTR_TYPE_CELL;
+                    typeAsString = Constants.PARAM_TYPE_CELL;
                 } else if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
                         && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
                     typeAsString = "X-" + label;
                 } else {
-                    typeAsString = DEFAULT_EMAIL_TYPE;
+                    typeAsString = null;
                 }
                 break;
             }
             case Email.TYPE_HOME: {
-                typeAsString = Constants.ATTR_TYPE_HOME;
+                typeAsString = Constants.PARAM_TYPE_HOME;
                 break;
             }
             case Email.TYPE_WORK: {
-                typeAsString = Constants.ATTR_TYPE_WORK;
+                typeAsString = Constants.PARAM_TYPE_WORK;
                 break;
             }
             case Email.TYPE_OTHER: {
-                typeAsString = DEFAULT_EMAIL_TYPE;
+                typeAsString = null;
                 break;
             }
             case Email.TYPE_MOBILE: {
-                typeAsString = Constants.ATTR_TYPE_CELL;
+                typeAsString = Constants.PARAM_TYPE_CELL;
                 break;
             }
             default: {
-                Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
-                typeAsString = DEFAULT_EMAIL_TYPE;
+                Log.e(LOG_TAG, "Unknown Email type: " + type);
+                typeAsString = null;
                 break;
             }
         }
 
-        builder.append(VCARD_ATTR_SEPARATOR);
-        appendTypeAttribute(builder, typeAsString);
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(data);
-        builder.append(VCARD_COL_SEPARATOR);
+        final List<String> parameterList = new ArrayList<String>();
+        if (isPrimary) {
+            parameterList.add(Constants.PARAM_TYPE_PREF);
+        }
+        if (!TextUtils.isEmpty(typeAsString)) {
+            parameterList.add(typeAsString);
+        }
+
+        appendVCardLineWithCharsetAndQPDetection(builder, Constants.PROPERTY_EMAIL,
+                parameterList, rawData);
     }
 
     private void appendVCardTelephoneLine(final StringBuilder builder,
             final Integer typeAsObject, final String label,
-            String encodedData) {
-        builder.append(VCARD_PROPERTY_TEL);
-        builder.append(VCARD_ATTR_SEPARATOR);
+            final String encodedData, boolean isPrimary) {
+        builder.append(Constants.PROPERTY_TEL);
+        builder.append(VCARD_PARAM_SEPARATOR);
 
         final int typeAsPrimitive;
         if (typeAsObject == null) {
@@ -1758,56 +1890,105 @@
             typeAsPrimitive = typeAsObject;
         }
 
+        ArrayList<String> parameterList = new ArrayList<String>();
         switch (typeAsPrimitive) {
         case Phone.TYPE_HOME:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
+            parameterList.addAll(
+                    Arrays.asList(Constants.PARAM_TYPE_HOME));
             break;
         case Phone.TYPE_WORK:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
+            parameterList.addAll(
+                    Arrays.asList(Constants.PARAM_TYPE_WORK));
             break;
         case Phone.TYPE_FAX_HOME:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
+            parameterList.addAll(
+                    Arrays.asList(Constants.PARAM_TYPE_HOME, Constants.PARAM_TYPE_FAX));
             break;
         case Phone.TYPE_FAX_WORK:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
+            parameterList.addAll(
+                    Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_FAX));
             break;
         case Phone.TYPE_MOBILE:
-            builder.append(Constants.ATTR_TYPE_CELL);
+            parameterList.add(Constants.PARAM_TYPE_CELL);
             break;
         case Phone.TYPE_PAGER:
             if (mIsDoCoMo) {
                 // Not sure about the reason, but previous implementation had
                 // used "VOICE" instead of "PAGER"
-                // Also, refrain from using appendType() so that "TYPE=" is never be appended.
-                builder.append(Constants.ATTR_TYPE_VOICE);
+                parameterList.add(Constants.PARAM_TYPE_VOICE);
             } else {
-                appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
+                parameterList.add(Constants.PARAM_TYPE_PAGER);
             }
             break;
         case Phone.TYPE_OTHER:
-            appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+            parameterList.add(Constants.PARAM_TYPE_VOICE);
+            break;
+        case Phone.TYPE_CAR:
+            parameterList.add(Constants.PARAM_TYPE_CAR);
+            break;
+        case Phone.TYPE_COMPANY_MAIN:
+            // There's no relevant field in vCard (at least 2.1).
+            parameterList.add(Constants.PARAM_TYPE_WORK);
+            isPrimary = true;
+            break;
+        case Phone.TYPE_ISDN:
+            parameterList.add(Constants.PARAM_TYPE_ISDN);
+            break;
+        case Phone.TYPE_MAIN:
+            isPrimary = true;
+            break;
+        case Phone.TYPE_OTHER_FAX:
+            parameterList.add(Constants.PARAM_TYPE_FAX);
+            break;
+        case Phone.TYPE_TELEX:
+            parameterList.add(Constants.PARAM_TYPE_TLX);
+            break;
+        case Phone.TYPE_WORK_MOBILE:
+            parameterList.addAll(
+                    Arrays.asList(Constants.PARAM_TYPE_WORK, Constants.PARAM_TYPE_CELL));
+            break;
+        case Phone.TYPE_WORK_PAGER:
+            parameterList.add(Constants.PARAM_TYPE_WORK);
+            // See above.
+            if (mIsDoCoMo) {
+                parameterList.add(Constants.PARAM_TYPE_VOICE);
+            } else {
+                parameterList.add(Constants.PARAM_TYPE_PAGER);
+            }
+            break;
+        case Phone.TYPE_MMS:
+            parameterList.add(Constants.PARAM_TYPE_MSG);
             break;
         case Phone.TYPE_CUSTOM:
             if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
                         && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                appendTypeAttribute(builder, "X-" + label);
+                // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+                //       "TYPE=" string.
+                parameterList.add("X-" + label);
             } else {
                 // Just ignore the custom type.
-                appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
+                parameterList.add(Constants.PARAM_TYPE_VOICE);
             }
             break;
+        case Phone.TYPE_RADIO:
+        case Phone.TYPE_TTY_TDD:
         default:
-            appendUncommonPhoneType(builder, typeAsPrimitive);
             break;
         }
 
+        if (isPrimary) {
+            parameterList.add(Constants.PARAM_TYPE_PREF);
+        }
+
+        if (parameterList.isEmpty()) {
+            appendUncommonPhoneType(builder, typeAsPrimitive);
+        } else {
+            appendTypeParameters(builder, parameterList);
+        }
+
         builder.append(VCARD_DATA_SEPARATOR);
         builder.append(encodedData);
-        builder.append(VCARD_COL_SEPARATOR);
+        builder.append(VCARD_END_OF_LINE);
     }
 
     /**
@@ -1817,35 +1998,65 @@
         if (mIsDoCoMo) {
             // The previous implementation for DoCoMo had been conservative
             // about miscellaneous types.
-            builder.append(Constants.ATTR_TYPE_VOICE);
+            builder.append(Constants.PARAM_TYPE_VOICE);
         } else {
-            String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
-            if (phoneAttribute != null) {
-                appendTypeAttribute(builder, phoneAttribute);
+            String phoneType = VCardUtils.getPhoneTypeString(type);
+            if (phoneType != null) {
+                appendTypeParameter(builder, phoneType);
             } else {
                 Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
             }
         }
     }
 
+    // appendVCardLine() variants accepting one String.
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName, final String rawData) {
+        appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawData);
+    }
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName,
+            final List<String> parameterList, final String rawData) {
+        final boolean needCharset =
+            (mUsesQuotedPrintable && !VCardUtils.containsOnlyPrintableAscii(rawData));
+        final boolean reallyUseQuotedPrintable =
+            !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData);
+        appendVCardLine(builder, propertyName, parameterList,
+                rawData, needCharset, reallyUseQuotedPrintable);
+    }
+
     private void appendVCardLine(final StringBuilder builder,
             final String propertyName, final String rawData) {
         appendVCardLine(builder, propertyName, rawData, false, false);
     }
 
     private void appendVCardLine(final StringBuilder builder,
-            final String field, final String rawData, final boolean needCharset,
+            final String propertyName, final String rawData, final boolean needCharset,
             boolean needQuotedPrintable) {
-        builder.append(field);
+        appendVCardLine(builder, propertyName, null, rawData, needCharset, needQuotedPrintable);
+    }
+
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName,
+            final List<String> parameterList,
+            final String rawData, final boolean needCharset,
+            boolean needQuotedPrintable) {
+        builder.append(propertyName);
+        if (parameterList != null && parameterList.size() > 0) {
+            builder.append(VCARD_PARAM_SEPARATOR);
+            appendTypeParameters(builder, parameterList);
+        }
         if (needCharset) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(mVCardAttributeCharset);
+            builder.append(VCARD_PARAM_SEPARATOR);
+            builder.append(mVCardCharsetParameter);
         }
 
         final String encodedData;
         if (needQuotedPrintable) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_ENCODING_QP);
+            builder.append(VCARD_PARAM_SEPARATOR);
+            builder.append(VCARD_PARAM_ENCODING_QP);
             encodedData = encodeQuotedPrintable(rawData);
         } else {
             // TODO: one line may be too huge, which may be invalid in vCard spec, though
@@ -1855,10 +2066,94 @@
 
         builder.append(VCARD_DATA_SEPARATOR);
         builder.append(encodedData);
-        builder.append(VCARD_COL_SEPARATOR);
+        builder.append(VCARD_END_OF_LINE);
     }
 
-    private void appendTypeAttributes(final StringBuilder builder,
+    // appendVCardLine() variants accepting List<String>.
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList) {
+        appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawDataList);
+    }
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName,
+            final List<String> parameterList, final List<String> rawDataList) {
+        boolean needCharset = false;
+        boolean reallyUseQuotedPrintable = false;
+        for (String rawData : rawDataList) {
+            if (!needCharset && mUsesQuotedPrintable &&
+                    !VCardUtils.containsOnlyPrintableAscii(rawData)) {
+                needCharset = true;
+            }
+            if (!reallyUseQuotedPrintable &&
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData)) {
+                reallyUseQuotedPrintable = true;
+            }
+            if (needCharset && reallyUseQuotedPrintable) {
+                break;
+            }
+        }
+
+        appendVCardLine(builder, propertyName, parameterList,
+                rawDataList, needCharset, reallyUseQuotedPrintable);
+    }
+
+    /*
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList) {
+        appendVCardLine(builder, propertyName, rawDataList, false, false);
+    }
+
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList,
+            final boolean needCharset, boolean needQuotedPrintable) {
+        appendVCardLine(builder, propertyName, null, rawDataList, needCharset, needQuotedPrintable);
+    }*/
+
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName,
+            final List<String> parameterList,
+            final List<String> rawDataList, final boolean needCharset,
+            boolean needQuotedPrintable) {
+        builder.append(propertyName);
+        if (parameterList != null && parameterList.size() > 0) {
+            builder.append(VCARD_PARAM_SEPARATOR);
+            appendTypeParameters(builder, parameterList);
+        }
+        if (needCharset) {
+            builder.append(VCARD_PARAM_SEPARATOR);
+            builder.append(mVCardCharsetParameter);
+        }
+
+        builder.append(VCARD_DATA_SEPARATOR);
+        boolean first = true;
+        for (String rawData : rawDataList) {
+            final String encodedData;
+            if (needQuotedPrintable) {
+                builder.append(VCARD_PARAM_SEPARATOR);
+                builder.append(VCARD_PARAM_ENCODING_QP);
+                encodedData = encodeQuotedPrintable(rawData);
+            } else {
+                // TODO: one line may be too huge, which may be invalid in vCard spec, though
+                //       several (even well-known) applications do not care this.
+                encodedData = escapeCharacters(rawData);
+            }
+
+            if (first) {
+                first = false;
+            } else {
+                builder.append(VCARD_ITEM_SEPARATOR);
+            }
+            builder.append(encodedData);
+        }
+        builder.append(VCARD_END_OF_LINE);
+    }
+
+    /**
+     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+     */
+    private void appendTypeParameters(final StringBuilder builder,
             final List<String> types) {
         // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
         // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
@@ -1867,47 +2162,54 @@
             if (first) {
                 first = false;
             } else {
-                builder.append(VCARD_ATTR_SEPARATOR);
+                builder.append(VCARD_PARAM_SEPARATOR);
             }
-            appendTypeAttribute(builder, type);
+            appendTypeParameter(builder, type);
         }
     }
 
-    private void appendTypeAttribute(final StringBuilder builder, final String type) {
+    /**
+     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+     */
+    private void appendTypeParameter(final StringBuilder builder, final String type) {
+        // Refrain from using appendType() so that "TYPE=" is not be appended when the
+        // device is DoCoMo's (just for safety).
+        //
         // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
-        if (mIsV30) {
-            builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL);
+        if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+            builder.append(Constants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
         }
         builder.append(type);
     }
 
     /**
-     * Returns true when the property line should contain charset attribute
+     * Returns true when the property line should contain charset parameter
      * information. This method may return true even when vCard version is 3.0.
      *
      * Strictly, adding charset information is invalid in VCard 3.0.
-     * However we'll add the info only when used charset is not UTF-8
+     * However we'll add the info only when charset we use is not UTF-8
      * in vCard 3.0 format, since parser side may be able to use the charset
-     * via this field, though we may encounter another problem by adding it...
+     * via this field, though we may encounter another problem by adding it.
      *
      * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
      * recommends UTF-8. By adding this field, parsers may be able
      * to know this text is NOT UTF-8 but Shift_Jis.
      */
-    private boolean shouldAppendCharsetAttribute(final String propertyValue) {
+    private boolean shouldAppendCharsetParameter(final String propertyValue) {
         return (!VCardUtils.containsOnlyPrintableAscii(propertyValue) &&
                         (!mIsV30 || !mUsesUtf8));
     }
 
-    private boolean shouldAppendCharsetAttribute(final List<String> propertyValueList) {
-        boolean shouldAppendBasically = false;
+    private boolean shouldAppendCharsetParameters(final List<String> propertyValueList) {
+        if (mIsV30 && mUsesUtf8) {
+            return false;
+        }
         for (String propertyValue : propertyValueList) {
             if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
-                shouldAppendBasically = true;
-                break;
+                return true;
             }
         }
-        return shouldAppendBasically && (!mIsV30 || !mUsesUtf8);
+        return false;
     }
 
     private String encodeQuotedPrintable(String str) {
@@ -1966,4 +2268,116 @@
 
         return tmpBuilder.toString();
     }
+
+    //// The methods bellow are for call log history ////
+
+    /**
+     * This static function is to compose vCard for phone own number
+     */
+    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+            String phoneNumber, boolean vcardVer21) {
+        final StringBuilder builder = new StringBuilder();
+        appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        if (!vcardVer21) {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+        } else {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+        }
+
+        boolean needCharset = false;
+        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+            needCharset = true;
+        }
+        appendVCardLine(builder, Constants.PROPERTY_FN, phoneName, needCharset, false);
+        appendVCardLine(builder, Constants.PROPERTY_N, phoneName, needCharset, false);
+
+        String label = Integer.toString(phonetype);
+        appendVCardTelephoneLine(builder, phonetype, label, phoneNumber, false);
+
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+
+        return builder.toString();
+    }
+
+    /**
+     * Format according to RFC 2445 DATETIME type.
+     * The format is: ("%Y%m%dT%H%M%SZ").
+     */
+    private final String toRfc2455Format(final long millSecs) {
+        Time startDate = new Time();
+        startDate.set(millSecs);
+        String date = startDate.format2445();
+        return date + FLAG_TIMEZONE_UTC;
+    }
+
+    /**
+     * Try to append the property line for a call history time stamp field if possible.
+     * Do nothing if the call log type gotton from the database is invalid.
+     */
+    private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
+        // Extension for call history as defined in
+        // in the Specification for Ic Mobile Communcation - ver 1.1,
+        // Oct 2000. This is used to send the details of the call
+        // history - missed, incoming, outgoing along with date and time
+        // to the requesting device (For example, transferring phone book
+        // when connected over bluetooth)
+        //
+        // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
+        final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
+        final String callLogTypeStr;
+        switch (callLogType) {
+            case Calls.INCOMING_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
+                break;
+            }
+            case Calls.OUTGOING_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
+                break;
+            }
+            case Calls.MISSED_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
+                break;
+            }
+            default: {
+                Log.w(LOG_TAG, "Call log type not correct.");
+                return;
+            }
+        }
+
+        final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+        builder.append(VCARD_PROPERTY_X_TIMESTAMP);
+        builder.append(VCARD_PARAM_SEPARATOR);
+        appendTypeParameter(builder, callLogTypeStr);
+        builder.append(VCARD_DATA_SEPARATOR);
+        builder.append(toRfc2455Format(dateAsLong));
+        builder.append(VCARD_END_OF_LINE);
+    }
+
+    private String createOneCallLogEntryInternal() {
+        final StringBuilder builder = new StringBuilder();
+        appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        if (mIsV30) {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+        } else {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+        }
+        String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+        if (TextUtils.isEmpty(name)) {
+            name = mCursor.getString(NUMBER_COLUMN_INDEX);
+        }
+        final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+        appendVCardLine(builder, Constants.PROPERTY_FN, name, needCharset, false);
+        appendVCardLine(builder, Constants.PROPERTY_N, name, needCharset, false);
+
+        String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+        int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+        String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+        if (TextUtils.isEmpty(label)) {
+            label = Integer.toString(type);
+        }
+        appendVCardTelephoneLine(builder, type, label, number, false);
+        tryAppendCallHistoryTimeStampField(builder);
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+        return builder.toString();
+    }
 }
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..545c09b 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,14 +15,20 @@
  */
 package android.pim.vcard;
 
+import android.util.Log;
+
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * The class representing VCard related configurations. Useful static methods are not in this class
  * but in VCardUtils.
  */
 public class VCardConfig {
+    private static final String LOG_TAG = "vcard.VCardConfig";
+
     // TODO: may be better to make the instance of this available and stop using static methods and
     //        one integer. 
 
@@ -41,8 +47,8 @@
     // TODO: make the other codes use this flag
     public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
     
-    private static final int FLAG_V21 = 0;
-    private static final int FLAG_V30 = 1;
+    public static final int FLAG_V21 = 0;
+    public static final int FLAG_V30 = 1;
 
     // 0x2 is reserved for the future use ...
 
@@ -95,96 +101,187 @@
     private static final int FLAG_DOCOMO = 0x20000000;
 
     /**
-     * The flag indicating the vCard composer use Quoted-Printable toward even "primary" types.
-     * In this context, "primary" types means "N", "FN", etc. which are usually "not" encoded
-     * into Quoted-Printable format in external exporters.
-     * This flag is useful when some target importer does not accept "primary" property values
-     * without Quoted-Printable encoding.
-     *
-     * @hide Temporaly made public. We don't strictly define "primary", so we may change the
-     * behavior around this flag in the future. Do not use this flag without any reason.
+     * <P>
+     * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
+     * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
+     * </P>
+     * <P>
+     * We actually cannot define what is the "primary" property. Note that this is NOT defined
+     * in vCard specification either. Also be aware that it is NOT related to "primary" notion
+     * used in {@link android.provider.ContactsContract}.
+     * This notion is just for vCard composition in Android.
+     * </P>
+     * <P>
+     * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
+     * do NOT use Quoted-Printable encoding toward some properties like "N", "FN", etc. even when
+     * their values contain non-ascii or/and CR/LF, while they use the encoding in the other
+     * properties like "ADR", "ORG", etc.
+     * <P>
+     * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
+     * not used in such fields.
+     * </P>
+     * <P>
+     * This flag is useful when some target importer you are going to focus on does not accept
+     * such "primary" property values with Quoted-Printable encoding.
+     * </P>
+     * <P>
+     * Again, we should not use this flag at all for complying vCard 2.1 spec.
+     * </P>
+     * <P>
+     * We will change the behavior around this flag in the future, after understanding the other
+     * real vCard cases around this problem. Please use this flag with extreme caution even when
+     * needed.
+     * </P>
+     * <P>
+     * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+     * kind of problem (hopefully).
+     * </P>
      */
-    public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-    
-    // VCard types
+    public static final int FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
 
     /**
-     * General vCard format with the version 2.1. Uses UTF-8 for the charset.
-     * When composing a vCard entry, the US convension will be used.
-     * 
-     * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
-     * while in Japan, it should be "Prefix Family Middle Given Suffix".
+     * <P>
+     * The flag indicating that phonetic name related fields must be converted to
+     * appropriate form. Note that "appropriate" is not defined in any vCard specification.
+     * This is Android-specific.
+     * </P>
+     * <P>
+     * One typical (and currently sole) example where we need this flag is the time when
+     * we need to emit Japanese phonetic names into vCard entries. The property values
+     * should be encoded into half-width katakana when the target importer is Japanese mobile
+     * phones', which are probably not able to parse full-width hiragana/katakana for
+     * historical reasons, while the vCard importers embedded to softwares for PC should be
+     * able to parse them as we expect.
+     * </P>
      */
-    public static final int VCARD_TYPE_V21_GENERIC =
+    public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+
+    /**
+     * <P>
+     * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string every time
+     * possible. The default behavior does not emit it and is valid, while adding "TYPE="
+     * is also valid. In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in
+     * vCard 3.0 specification.
+     *
+     * If you are targeting to some importer which cannot accept type parameters
+     * without "TYPE=" string (which should be rare though), please use this flag.
+     *
+     * XXX: Really rare?
+     *
+     * e.g. int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
+     */
+    public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+    //// The followings are VCard types available from importer/exporter. ////
+
+    /**
+     * <P>
+     * General vCard format with the version 2.1. Uses UTF-8 for the charset.
+     * When composing a vCard entry, the US convension will be used toward formatting
+     * some values
+     * </P>
+     * <P>
+     * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
+     * while it should be "Prefix Family Middle Given Suffix" in Japan.
+     * </P>
+     */
+    public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
         (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+    /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
     
     /**
+     * <P>
      * General vCard format with the version 3.0. Uses UTF-8 for the charset.
-     * 
-     * Note that this type is not fully implemented, so probably some bugs remain both in
-     * parsing and composing.
-     *
-     * TODO: implement this type correctly.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
      */
-    public static final int VCARD_TYPE_V30_GENERIC =
+    public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
         (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+    /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
     
     /**
-     * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+     * <P>
+     * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
      * Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+     * </P>
      */
-    public static final int VCARD_TYPE_V21_EUROPE =
+    public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
         (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
     
-    /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+    /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
     
     /**
+     * <P>
      * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
      */
-    public static final int VCARD_TYPE_V30_EUROPE =
+    public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
         (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
     
     /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-    
-    /**
-     * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
-     * parsing/composing the vCard data.
-     */
-    public static final int VCARD_TYPE_V21_JAPANESE =
-        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | 
-                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
-    /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
-    
     /**
-     * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+     * <P>
+     * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
      */
     public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
         (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
 
     /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+
+    /**
+     * <P>
+     * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+     * parsing/composing the vCard data.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
+     */
+    public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
+        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+                FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+    /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
     
     /**
+     * <P>
      * vCard format for miscellaneous Japanese devices, using Shift_Jis for
      * parsing/composing the vCard data.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
      */
-    public static final int VCARD_TYPE_V30_JAPANESE =
+    public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
         (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
                 FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
         
-    /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
+    /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
     
     /**
-     * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+     * <P>
+     * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
      */
     public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
         (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
@@ -193,38 +290,72 @@
     /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
 
     /**
-     *  VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
-     *  Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
-     *  No Android-specific property nor defact property is included.
+     * <P>
+     * The vCard 2.1 based format which (partially) considers the convention in Japanese
+     * mobile phones, where phonetic names are translated to half-width katakana if
+     * possible, etc.
+     * </P>
+     * <P>
+     * Not ready yet. Use with caution when you use this.
+     * </P>
+     */
+    public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+                FLAG_CONVERT_PHONETIC_NAME_STRINGS |
+                FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES);
+
+    public static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+    /**
+     * <P>
+     * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+     * </p>
+     * <P>
+     * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+     * No Android-specific property nor defact property is included. The "Primary" properties
+     * are NOT encoded to Quoted-Printable.
+     * </P>
      */
     public static final int VCARD_TYPE_DOCOMO =
-        (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+        (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
 
     private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
     
-    public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+    public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
 
-    private static final Map<String, Integer> VCARD_TYPES_MAP;
+    private static final Map<String, Integer> sVCardTypeMap;
+    private static final Set<Integer> sJapaneseMobileTypeSet;
     
     static {
-        VCARD_TYPES_MAP = new HashMap<String, Integer>();
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
-        VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+        sVCardTypeMap = new HashMap<String, Integer>();
+        sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
+        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
+        sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+        sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
+        sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+
+        sJapaneseMobileTypeSet = new HashSet<Integer>();
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+        sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
     }
 
     public static int getVCardTypeFromString(String vcardTypeString) {
         String loweredKey = vcardTypeString.toLowerCase();
-        if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
-            return VCARD_TYPES_MAP.get(loweredKey);
+        if (sVCardTypeMap.containsKey(loweredKey)) {
+            return sVCardTypeMap.get(loweredKey);
         } else {
             // XXX: should return the value indicating the input is invalid?
+            Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
             return VCARD_TYPE_DEFAULT;
         }
     }
@@ -237,22 +368,6 @@
         return !isV30(vcardType);
     }
 
-    public static boolean isDoCoMo(int vcardType) {
-        return ((vcardType & FLAG_DOCOMO) != 0);
-    }
-    
-    /**
-     * @return true if the device is Japanese and some Japanese convension is
-     * applied to creating "formatted" something like FORMATTED_ADDRESS.
-     */
-    public static boolean isJapaneseDevice(int vcardType) {
-        return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
-                (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
-                (vcardType == VCARD_TYPE_V30_JAPANESE) ||
-                (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
-                (vcardType == VCARD_TYPE_DOCOMO));
-    }
-
     public static boolean usesUtf8(int vcardType) {
         return ((vcardType & FLAG_CHARSET_UTF8) != 0);
     }
@@ -261,17 +376,6 @@
         return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
     }
     
-    /**
-     * @return true when Japanese phonetic string must be converted to a string
-     * containing only half-width katakana. This method exists since Japanese mobile
-     * phones usually use only half-width katakana for expressing phonetic names and
-     * some devices are not ready for parsing other phonetic strings like hiragana and
-     * full-width katakana.
-     */
-    public static boolean needsToConvertPhoneticString(int vcardType) {
-        return (vcardType == VCARD_TYPE_DOCOMO);
-    }
-
     public static int getNameOrderType(int vcardType) {
         return vcardType & NAME_ORDER_MASK;
     }
@@ -284,20 +388,37 @@
         return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
     }
 
-    public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
-        return vcardType == VCARD_TYPE_DOCOMO;
-    }
-
     public static boolean showPerformanceLog() {
         return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
     }
 
+    public static boolean refrainsQPToPrimaryProperties(int vcardType) {
+       return (!usesQuotedPrintable(vcardType) ||
+               ((vcardType & FLAG_REFRAIN_QP_TO_PRIMARY_PROPERTIES) != 0));
+    }
+
+    public static boolean appendTypeParamName(int vcardType) {
+        return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+    }
+
     /**
-     * @hide
+     * @return true if the device is Japanese and some Japanese convension is
+     * applied to creating "formatted" something like FORMATTED_ADDRESS.
      */
-    public static boolean usesQPToPrimaryProperties(int vcardType) {
-       return (usesQuotedPrintable(vcardType) &&
-               ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0));
+    public static boolean isJapaneseDevice(int vcardType) {
+        return sJapaneseMobileTypeSet.contains(vcardType);
+    }
+
+    public static boolean needsToConvertPhoneticString(int vcardType) {
+        return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+    }
+
+    public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
+        return vcardType == VCARD_TYPE_DOCOMO;
+    }
+
+    public static boolean isDoCoMo(int vcardType) {
+        return ((vcardType & FLAG_DOCOMO) != 0);
     }
 
     private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java
index d2026d0..76ad482 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardDataBuilder.java
@@ -69,7 +69,7 @@
     private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
     
     public VCardDataBuilder() {
-        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
     }
 
     /**
@@ -86,7 +86,7 @@
             boolean strictLineBreakParsing, int vcardType, Account account) {
         this(null, charset, strictLineBreakParsing, vcardType, account);
     }
-    
+
     /**
      * @hide
      */
@@ -127,6 +127,18 @@
     }
 
     /**
+     * Called when the parse failed between startRecord() and endRecord().
+     * Currently it happens only when the vCard format is 3.0.
+     * (VCardVersionException is thrown by VCardParser_V21 and this object is reused by
+     * VCardParser_V30. At that time, startRecord() is called twice before endRecord() is called.)
+     * TODO: Should this be in VCardBuilder interface?
+     */
+    public void clear() {
+        mCurrentContactStruct = null;
+        mCurrentProperty = new ContactStruct.Property();
+    }
+
+    /**
      * Assume that VCard is not nested. In other words, this code does not accept 
      */
     public void startRecord(String type) {
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index b5e5049..0ffdb6c 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -35,7 +35,7 @@
      * 
      * In order to avoid "misunderstanding" charset as much as possible, this method
      * use "ISO-8859-1" for reading the stream. When charset is specified in some property
-     * (with "CHARSET=..." attribute), the string is decoded to raw bytes and encoded to
+     * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
      * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
      * characters, which is not completely sure. In some cases, this "decoding-encoding"
      * scheme may fail. To avoid the case,
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..b3ff8fa 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -144,10 +144,14 @@
         }
     }
 
-    protected String getVersion() {
-        return "2.1";
+    protected int getVersion() {
+        return VCardConfig.FLAG_V21;
     }
-    
+
+    protected String getVersionString() {
+        return Constants.VERSION_V21;
+    }
+
     /**
      * @return true when the propertyName is a valid property name.
      */
@@ -356,7 +360,7 @@
      *      / [groups "."] "ADR"   [params] ":" addressparts CRLF
      *      / [groups "."] "ORG"   [params] ":" orgparts CRLF
      *      / [groups "."] "N"     [params] ":" nameparts CRLF
-     *      / [groups "."] "AGENT" [params] ":" vcard CRLF 
+     *      / [groups "."] "AGENT" [params] ":" vcard CRLF
      */
     protected boolean parseItem() throws IOException, VCardException {
         mEncoding = sDefaultEncoding;
@@ -392,9 +396,10 @@
                 } else {
                     throw new VCardException("Unknown BEGIN type: " + propertyValue);
                 }
-            } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+            } else if (propertyName.equals("VERSION") &&
+                    !propertyValue.equals(getVersionString())) {
                 throw new VCardVersionException("Incompatible version: " + 
-                        propertyValue + " != " + getVersion());
+                        propertyValue + " != " + getVersionString());
             }
             start = System.currentTimeMillis();
             handlePropertyValue(propertyName, propertyValue);
@@ -520,11 +525,19 @@
                 throw new VCardException("Unknown type \"" + paramName + "\"");
             }
         } else {
-            handleType(strArray[0]);
+            handleParamWithoutName(strArray[0]);
         }
     }
     
     /**
+     * vCard 3.0 parser may throw VCardException.
+     */
+    @SuppressWarnings("unused")
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        handleType(paramValue);
+    }
+
+    /**
      * ptypeval = knowntype / "X-" word
      */
     protected void handleType(String ptypeval) {
@@ -761,32 +774,11 @@
         }
 
         if (mBuilder != null) {
-            StringBuilder builder = new StringBuilder();
-            ArrayList<String> list = new ArrayList<String>();
-            int length = propertyValue.length();
-            for (int i = 0; i < length; i++) {
-                char ch = propertyValue.charAt(i);
-                if (ch == '\\' && i < length - 1) {
-                    char nextCh = propertyValue.charAt(i + 1);
-                    String unescapedString = maybeUnescapeCharacter(nextCh); 
-                    if (unescapedString != null) {
-                        builder.append(unescapedString);
-                        i++;
-                    } else {
-                        builder.append(ch);
-                    }
-                } else if (ch == ';') {
-                    list.add(builder.toString());
-                    builder = new StringBuilder();
-                } else {
-                    builder.append(ch);
-                }
-            }
-            list.add(builder.toString());
-            mBuilder.propertyValues(list);
+            mBuilder.propertyValues(VCardUtils.constructListFromValue(
+                    propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
         }
     }
-    
+
     /**
      * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
      * 
@@ -819,12 +811,16 @@
     protected String maybeUnescapeText(String text) {
         return text;
     }
-    
+
     /**
      * Returns unescaped String if the character should be unescaped. Return null otherwise.
      * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
      */
     protected String maybeUnescapeCharacter(char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(char ch) {
         // Original vCard 2.1 specification does not allow transformation
         // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
         // this class allowed them, so keep it as is.
@@ -844,6 +840,9 @@
     @Override
     public boolean parse(InputStream is, String charset, VCardBuilder builder)
             throws IOException, VCardException {
+        if (charset == null) {
+            charset = VCardConfig.DEFAULT_CHARSET;
+        }
         final InputStreamReader tmpReader = new InputStreamReader(is, charset);
         if (VCardConfig.showPerformanceLog()) {
             mReader = new CustomBufferedReader(tmpReader);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..86e7625 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -46,14 +46,42 @@
     private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
 
     private String mPreviousLine;
-    
+
     private boolean mEmittedAgentWarning = false;
-    
+
+    /**
+     * True when the caller wants the parser to be strict about the input.
+     * Currently this is only for testing.
+     */
+    private final boolean mStrictParsing;
+
+    public VCardParser_V30() {
+        super();
+        mStrictParsing = false;
+    }
+
+    /**
+     * @param strictParsing when true, this object throws VCardException when the vcard is not
+     * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
+     * is not fully yet for being used with this flag and may not notice invalid line(s).
+     *
+     * @hide currently only for testing! 
+     */
+    public VCardParser_V30(boolean strictParsing) {
+        super();
+        mStrictParsing = strictParsing;
+    }
+
     @Override
-    protected String getVersion() {
+    protected int getVersion() {
+        return VCardConfig.FLAG_V30;
+    }
+
+    @Override
+    protected String getVersionString() {
         return Constants.VERSION_V30;
     }
-    
+
     @Override
     protected boolean isValidPropertyName(String propertyName) {
         if (!(sAcceptablePropsWithParam.contains(propertyName) ||
@@ -199,7 +227,16 @@
         // TODO: fix this.
         super.handleAnyParam(paramName, paramValue);
     }
-    
+
+    @Override
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        if (mStrictParsing) {
+            throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
+        } else {
+            super.handleParamWithoutName(paramValue);
+        }
+    }
+
     /**
      *  vCard 3.0 defines
      *  
@@ -284,6 +321,10 @@
      */ 
     @Override
     protected String maybeUnescapeText(String text) {
+        return unescapeText(text);
+    }
+
+    public static String unescapeText(String text) {
         StringBuilder builder = new StringBuilder();
         int length = text.length();
         for (int i = 0; i < length; i++) {
@@ -299,15 +340,19 @@
                 builder.append(ch);
             }
         }
-        return builder.toString();
+        return builder.toString();        
     }
     
     @Override
     protected String maybeUnescapeCharacter(char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(char ch) {
         if (ch == 'n' || ch == 'N') {
             return "\n";
         } else {
             return String.valueOf(ch);
-        }
+        }        
     }
 }
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..05e6730 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -18,13 +18,16 @@
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
 import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -37,53 +40,69 @@
      */
 
     // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
-    // converted to two attribute Strings. These only contain some minor fields valid in both
+    // converted to two parameter Strings. These only contain some minor fields valid in both
     // vCard and current (as of 2009-08-07) Contacts structure. 
     private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
     private static final Set<String> sPhoneTypesSetUnknownToContacts;
-    
-    private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
-    
+
+    private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+
+    private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+
     static {
         sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
-        sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
+        sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
 
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
-        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.PARAM_TYPE_CAR);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.PARAM_TYPE_PAGER);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.PARAM_TYPE_ISDN);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
         
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
                 
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
-        sKnownPhoneTypesMap_StoI.put(
-                Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
-        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_CALLBACK, Phone.TYPE_CALLBACK);
+        sKnownPhoneTypeMap_StoI.put(
+                Constants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD);
+        sKnownPhoneTypeMap_StoI.put(Constants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+                Phone.TYPE_ASSISTANT);
 
         sPhoneTypesSetUnknownToContacts = new HashSet<String>();
-        sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
-        sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
-        sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
-        sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+        sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_MODEM);
+        sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_BBS);
+        sPhoneTypesSetUnknownToContacts.add(Constants.PARAM_TYPE_VIDEO);
+
+        sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK, Constants.PROPERTY_X_GOOGLE_TALK);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, Constants.PROPERTY_X_QQ);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, Constants.PROPERTY_X_NETMEETING);
     }
-    
-    public static String getPhoneAttributeString(Integer type) {
+
+    public static String getPhoneTypeString(Integer type) {
         return sKnownPhoneTypesMap_ItoS.get(type);
     }
-    
+
     /**
      * Returns Interger when the given types can be parsed as known type. Returns String object
      * when not, which should be set to label. 
      */
-    public static Object getPhoneTypeFromStrings(Collection<String> types) {
+    public static Object getPhoneTypeFromStrings(Collection<String> types,
+            String number) {
+        if (number == null) {
+            number = "";
+        }
         int type = -1;
         String label = null;
         boolean isFax = false;
@@ -92,17 +111,30 @@
         if (types != null) {
             for (String typeString : types) {
                 typeString = typeString.toUpperCase(); 
-                if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+                if (typeString.equals(Constants.PARAM_TYPE_PREF)) {
                     hasPref = true;
-                } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+                } else if (typeString.equals(Constants.PARAM_TYPE_FAX)) {
                     isFax = true;
                 } else {
                     if (typeString.startsWith("X-") && type < 0) {
                         typeString = typeString.substring(2);
                     }
-                    Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+                    Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
                     if (tmp != null) {
-                        type = tmp;
+                        final int typeCandidate = tmp;
+                        // TYPE_PAGER is prefered when the number contains @ surronded by
+                        // a pager number and a domain name.
+                        // e.g.
+                        // o 1111@domain.com
+                        // x @domain.com
+                        // x 1111@
+                        final int indexOfAt = number.indexOf("@");
+                        if ((typeCandidate == Phone.TYPE_PAGER
+                                && 0 < indexOfAt && indexOfAt < number.length() - 1)
+                                || type < 0
+                                || type == Phone.TYPE_CUSTOM) {
+                            type = tmp;
+                        }
                     } else if (type < 0) {
                         type = Phone.TYPE_CUSTOM;
                         label = typeString;
@@ -133,15 +165,19 @@
             return type;
         }
     }
-    
-    public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
+
+    public static String getPropertyNameForIm(int protocol) {
+        return sKnownImPropNameMap_ItoS.get(protocol);
+    }
+
+    public static boolean isValidPhoneType(String phoneType, int vcardType) {
         // TODO: check the following.
         // - it may violate vCard spec
         // - it may contain non-ASCII characters
         //
         // TODO: use vcardType
-        return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
-                sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
+        return (phoneType.startsWith("X-") || phoneType.startsWith("x-") ||
+                sPhoneTypesSetUnknownToContacts.contains(phoneType));
     }
     
     public static String[] sortNameElements(int vcardType,
@@ -187,7 +223,10 @@
         }
 
         builder.withValue(StructuredPostal.POBOX, postalData.pobox);
-        // Extended address is dropped since there's no relevant entry in ContactsContract.
+        // TODO: Japanese phone seems to use this field for expressing all the address including
+        // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
+        // better than dropping them all.
+        builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
         builder.withValue(StructuredPostal.STREET, postalData.street);
         builder.withValue(StructuredPostal.CITY, postalData.localty);
         builder.withValue(StructuredPostal.REGION, postalData.region);
@@ -200,12 +239,12 @@
             builder.withValue(Data.IS_PRIMARY, 1);
         }
     }
-    
+
     /**
      * Returns String[] containing address information based on vCard spec
      * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
      * All String objects are non-null ("" is used when the relevant data is empty).
-     * 
+     *
      * Note that the data structure of ContactsContract is different from that defined in vCard.
      * So some conversion may be performed in this method. See also
      * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
@@ -213,13 +252,20 @@
      * android.pim.vcard.ContactStruct.PostalData)}
      */
     public static String[] getVCardPostalElements(ContentValues contentValues) {
+        // adr-value    = 0*6(text-value ";") text-value
+        //              ; PO Box, Extended Address, Street, Locality, Region, Postal
+        //              ; Code, Country Name
         String[] dataArray = new String[7];
         dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
         if (dataArray[0] == null) {
             dataArray[0] = "";
         }
-        // Extended addr. There's no relevant data in ContactsContract.
-        dataArray[1] = "";
+        // We keep all the data in StructuredPostal, presuming NEIGHBORHOOD is
+        // similar to "Extended Address".
+        dataArray[1] = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+        if (dataArray[1] == null) {
+            dataArray[1] = "";
+        }
         dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
         if (dataArray[2] == null) {
             dataArray[2] = "";
@@ -282,12 +328,41 @@
         }
         return builder.toString();
     }
-    
+
+    public static List<String> constructListFromValue(final String value,
+            final boolean isV30) {
+        final List<String> list = new ArrayList<String>();
+        StringBuilder builder = new StringBuilder();
+        int length = value.length();
+        for (int i = 0; i < length; i++) {
+            char ch = value.charAt(i);
+            if (ch == '\\' && i < length - 1) {
+                char nextCh = value.charAt(i + 1);
+                final String unescapedString =
+                    (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+                        VCardParser_V21.unescapeCharacter(nextCh));
+                if (unescapedString != null) {
+                    builder.append(unescapedString);
+                    i++;
+                } else {
+                    builder.append(ch);
+                }
+            } else if (ch == ';') {
+                list.add(builder.toString());
+                builder = new StringBuilder();
+            } else {
+                builder.append(ch);
+            }
+        }
+        list.add(builder.toString());
+        return list;
+    }
+
     public static boolean containsOnlyPrintableAscii(String str) {
         if (TextUtils.isEmpty(str)) {
             return true;
         }
-        
+
         final int length = str.length();
         final int asciiFirst = 0x20;
         final int asciiLast = 0x126;
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@
         mContext = context;
 
         TypedArray a = context.obtainStyledAttributes(attrs,
-                com.android.internal.R.styleable.Preference);
-        if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
-                a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
-            // This preference has a custom layout defined (not one taken from
-            // the default style)
-            mHasSpecifiedLayout = true;
-        }
-        a.recycle();
-        
-        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
-                defStyle, 0);
+                com.android.internal.R.styleable.Preference, defStyle, 0);
         for (int i = a.getIndexCount(); i >= 0; i--) {
             int attr = a.getIndex(i); 
             switch (attr) {
@@ -252,6 +242,11 @@
             }
         }
         a.recycle();
+
+        if (!getClass().getName().startsWith("android.preference")) {
+            // For subclasses not in this package, assume the worst and don't cache views
+            mHasSpecifiedLayout = true;
+        }
     }
     
     /**
@@ -332,11 +327,11 @@
      * @see #setWidgetLayoutResource(int)
      */
     public void setLayoutResource(int layoutResId) {
-        
-        if (!mHasSpecifiedLayout) {
+        if (layoutResId != mLayoutResId) {
+            // Layout changed
             mHasSpecifiedLayout = true;
         }
-        
+
         mLayoutResId = layoutResId;
     }
     
@@ -360,6 +355,10 @@
      * @see #setLayoutResource(int)
      */
     public void setWidgetLayoutResource(int widgetLayoutResId) {
+        if (widgetLayoutResId != mWidgetLayoutResId) {
+            // Layout changed
+            mHasSpecifiedLayout = true;
+        }
         mWidgetLayoutResId = widgetLayoutResId;
     }
 
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@
      * count once--when the adapter is being set). We will not recycle views for
      * Preference subclasses seen after the count has been returned.
      */
-    private List<String> mPreferenceClassNames;
+    private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+    private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
 
     /**
      * Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@
         }
     };
 
+    private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+        private int resId;
+        private int widgetResId;
+        private String name;
+
+        public int compareTo(PreferenceLayout other) {
+            int compareNames = name.compareTo(other.name);
+            if (compareNames == 0) {
+                if (resId == other.resId) {
+                    if (widgetResId == other.widgetResId) {
+                        return 0;
+                    } else {
+                        return widgetResId - other.widgetResId;
+                    }
+                } else {
+                    return resId - other.resId;
+                }
+            } else {
+                return compareNames;
+            }
+        }
+    }
+
     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
         mPreferenceGroup = preferenceGroup;
         // If this group gets or loses any children, let us know
         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-        
+
         mPreferenceList = new ArrayList<Preference>();
-        mPreferenceClassNames = new ArrayList<String>();
-        
+        mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
         syncMyPreferences();
     }
 
@@ -102,7 +127,7 @@
             if (mIsSyncing) {
                 return;
             }
-        
+
             mIsSyncing = true;
         }
 
@@ -128,7 +153,7 @@
             
             preferences.add(preference);
             
-            if (!mHasReturnedViewTypeCount) {
+            if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
                 addPreferenceClassName(preference);
             }
             
@@ -143,15 +168,28 @@
         }
     }
 
+    /**
+     * Creates a string that includes the preference name, layout id and widget layout id.
+     * If a particular preference type uses 2 different resources, they will be treated as
+     * different view types.
+     */
+    private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+        PreferenceLayout pl = in != null? in : new PreferenceLayout();
+        pl.name = preference.getClass().getName();
+        pl.resId = preference.getLayoutResource();
+        pl.widgetResId = preference.getWidgetLayoutResource();
+        return pl;
+    }
+
     private void addPreferenceClassName(Preference preference) {
-        final String name = preference.getClass().getName();
-        int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-        
+        final PreferenceLayout pl = createPreferenceLayout(preference, null);
+        int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
         // Only insert if it doesn't exist (when it is negative).
         if (insertPos < 0) {
             // Convert to insert index
             insertPos = insertPos * -1 - 1;
-            mPreferenceClassNames.add(insertPos, name);
+            mPreferenceLayouts.add(insertPos, pl);
         }
     }
     
@@ -171,19 +209,15 @@
 
     public View getView(int position, View convertView, ViewGroup parent) {
         final Preference preference = this.getItem(position);
-        
-        if (preference.hasSpecifiedLayout()) {
-            // If the preference had specified a layout (as opposed to the
-            // default), don't use convert views.
+        // Build a PreferenceLayout to compare with known ones that are cacheable.
+        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+        // If it's not one of the cached ones, set the convertView to null so that 
+        // the layout gets re-created by the Preference.
+        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
             convertView = null;
-        } else {
-            // TODO: better way of doing this
-            final String name = preference.getClass().getName();
-            if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
-                convertView = null;
-            }
         }
-        
+
         return preference.getView(convertView, parent);
     }
 
@@ -225,8 +259,9 @@
             return IGNORE_ITEM_VIEW_TYPE;
         }
 
-        final String name = preference.getClass().getName();
-        int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+        int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
         if (viewType < 0) {
             // This is a class that was seen after we returned the count, so
             // don't recycle it.
@@ -242,7 +277,7 @@
             mHasReturnedViewTypeCount = true;
         }
         
-        return Math.max(1, mPreferenceClassNames.size());
+        return Math.max(1, mPreferenceLayouts.size());
     }
 
 }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..b8cf6c2 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -76,6 +76,15 @@
         Uri.parse("content://" + AUTHORITY);
 
     /**
+     * An optional insert, update or delete URI parameter that allows the caller
+     * to specify that it is a sync adapter. The default value is false. If true
+     * the dirty flag is not automatically set and the "syncToNetwork" parameter
+     * is set to false when calling
+     * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+     */
+    public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+    /**
      * Columns from the Calendars table that other tables join into themselves.
      */
     public interface CalendarsColumns
@@ -341,11 +350,11 @@
          * This field is copied here so that we can efficiently filter out
          * events that are declined without having to look in the Attendees
          * table.
-         * 
+         *
          * <P>Type: INTEGER (int)</P>
          */
         public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
-        
+
         /**
          * The comments feed uri.
          * <P>Type: TEXT</P>
@@ -514,6 +523,12 @@
          * <P>Type: String</P>
          */
         public static final String OWNER_ACCOUNT = "ownerAccount";
+
+        /**
+         * Whether the row has been deleted.  A deleted row should be ignored.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String DELETED = "deleted";
     }
 
     /**
@@ -862,10 +877,10 @@
          */
         public static final String MAX_BUSYBITS = "maxBusyBits";
     }
-    
+
     public static final class CalendarMetaData implements CalendarMetaDataColumns {
     }
-    
+
     public interface BusyBitsColumns {
         /**
          * The Julian day number.
@@ -889,22 +904,22 @@
          */
         public static final String ALL_DAY_COUNT = "allDayCount";
     }
-    
+
     public static final class BusyBits implements BusyBitsColumns {
         public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
 
         public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
-        
+
         // The number of minutes represented by one busy bit
         public static final int MINUTES_PER_BUSY_INTERVAL = 60;
-        
+
         // The number of intervals in a day
         public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
 
         /**
          * Retrieves the busy bits for the Julian days starting at "startDay"
          * for "numDays".
-         * 
+         *
          * @param cr the ContentResolver
          * @param startDay the first Julian day in the range
          * @param numDays the number of days to load (must be at least 1)
@@ -1032,14 +1047,14 @@
             CalendarAlertsColumns, EventsColumns, CalendarsColumns {
         public static final String TABLE_NAME = "CalendarAlerts";
         public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
-        
+
         /**
          * This URI is for grouping the query results by event_id and begin
          * time.  This will return one result per instance of an event.  So
          * events with multiple alarms will appear just once, but multiple
          * instances of a repeating event will show up multiple times.
          */
-        public static final Uri CONTENT_URI_BY_INSTANCE = 
+        public static final Uri CONTENT_URI_BY_INSTANCE =
             Uri.parse("content://calendar/calendar_alerts/by_instance");
 
         public static final Uri insert(ContentResolver cr, long eventId,
@@ -1063,11 +1078,11 @@
             return cr.query(CONTENT_URI, projection, selection, selectionArgs,
                     DEFAULT_SORT_ORDER);
         }
-        
+
         /**
          * Finds the next alarm after (or equal to) the given time and returns
          * the time of that alarm or -1 if no such alarm exists.
-         * 
+         *
          * @param cr the ContentResolver
          * @param millis the time in UTC milliseconds
          * @return the next alarm time greater than or equal to "millis", or -1
@@ -1091,13 +1106,13 @@
             }
             return alarmTime;
         }
-        
+
         /**
          * Searches the CalendarAlerts table for alarms that should have fired
          * but have not and then reschedules them.  This method can be called
          * at boot time to restore alarms that may have been lost due to a
          * phone reboot.
-         * 
+         *
          * @param cr the ContentResolver
          * @param context the Context
          * @param manager the AlarmManager
@@ -1125,7 +1140,7 @@
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "missed alarms found: " + cursor.getCount());
             }
-            
+
             try {
                 while (cursor.moveToNext()) {
                     long id = cursor.getLong(0);
@@ -1146,14 +1161,14 @@
             } finally {
                 cursor.close();
             }
-            
+
         }
-        
+
         /**
          * Searches for an entry in the CalendarAlerts table that matches
          * the given event id, begin time and alarm time.  If one is found
          * then this alarm already exists and this method returns true.
-         * 
+         *
          * @param cr the ContentResolver
          * @param eventId the event id to match
          * @param begin the start time of the event in UTC millis
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..cd71682 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -238,6 +238,7 @@
         private static final int FULL_SCREEN_KIND = 2;
         private static final int MICRO_KIND = 3;
         private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+        static final int DEFAULT_GROUP_ID = 0;
 
         /**
          * This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +247,14 @@
          *
          * @param cr ContentResolver
          * @param origId original image or video id. use -1 to cancel all requests.
+         * @param groupId the same groupId used in getThumbnail
          * @param baseUri the base URI of requested thumbnails
          */
-        static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+        static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+                long groupId) {
             Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
-                    .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+                    .appendQueryParameter("orig_id", String.valueOf(origId))
+                    .appendQueryParameter("group_id", String.valueOf(groupId)).build();
             Cursor c = null;
             try {
                 c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,9 +275,10 @@
          * @param kind could be MINI_KIND or MICRO_KIND
          * @param options this is only used for MINI_KIND when decoding the Bitmap
          * @param baseUri the base URI of requested thumbnails
+         * @param groupId the id of group to which this request belongs
          * @return Bitmap bitmap of specified thumbnail kind
          */
-        static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+        static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
                 BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
             Bitmap bitmap = null;
             String filePath = null;
@@ -297,7 +302,8 @@
             Cursor c = null;
             try {
                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
-                        .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+                        .appendQueryParameter("orig_id", String.valueOf(origId))
+                        .appendQueryParameter("group_id", String.valueOf(groupId)).build();
                 c = cr.query(blockingUri, PROJECTION, null, null, null);
                 // This happens when original image/video doesn't exist.
                 if (c == null) return null;
@@ -354,7 +360,7 @@
                     }
                     if (isVideo) {
                         bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
-                        if (kind == MICRO_KIND) {
+                        if (kind == MICRO_KIND && bitmap != null) {
                             bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
                                     ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
                                     ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
@@ -669,7 +675,8 @@
              * @param origId original image id
              */
             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
-                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+                        InternalThumbnails.DEFAULT_GROUP_ID);
             }
 
             /**
@@ -685,7 +692,39 @@
              */
             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
                     BitmapFactory.Options options) {
-                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+                return InternalThumbnails.getThumbnail(cr, origId,
+                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+                        EXTERNAL_CONTENT_URI, false);
+            }
+
+            /**
+             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+             * interrupted and return immediately. Only the original process which made the getThumbnail
+             * requests can cancel their own requests.
+             *
+             * @param cr ContentResolver
+             * @param origId original image id
+             * @param groupId the same groupId used in getThumbnail.
+             */
+            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+            }
+
+            /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param groupId the id of group to which this request belongs
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image
+             *         associated with origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+                    int kind, BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
                         EXTERNAL_CONTENT_URI, false);
             }
 
@@ -1598,7 +1637,8 @@
              * @param origId original video id
              */
             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
-                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+                        InternalThumbnails.DEFAULT_GROUP_ID);
             }
 
             /**
@@ -1607,18 +1647,50 @@
              *
              * @param cr ContentResolver used to dispatch queries to MediaProvider.
              * @param origId Original image id associated with thumbnail of interest.
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image
+             *         associated with origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+                    BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId,
+                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+                        EXTERNAL_CONTENT_URI, true);
+            }
+
+            /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param groupId the id of group to which this request belongs
              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
              * @param options this is only used for MINI_KIND when decoding the Bitmap
              * @return A Bitmap instance. It could be null if the original image associated with
              *         origId doesn't exist or memory is not enough.
              */
-            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
-                    BitmapFactory.Options options) {
-                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+                    int kind, BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
                         EXTERNAL_CONTENT_URI, true);
             }
 
             /**
+             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+             * interrupted and return immediately. Only the original process which made the getThumbnail
+             * requests can cancel their own requests.
+             *
+             * @param cr ContentResolver
+             * @param origId original video id
+             * @param groupId the same groupId used in getThumbnail.
+             */
+            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+            }
+
+            /**
              * Get the content:// style URI for the image media table on the
              * given volume.
              *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb3dc16..9a8fd50 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -43,8 +43,10 @@
 import java.net.URISyntaxException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 
 
 /**
@@ -476,7 +478,10 @@
 
     private static class NameValueCache {
         private final String mVersionSystemProperty;
-        private final HashMap<String, String> mValues = Maps.newHashMap();
+        // the following needs synchronization because this structure is accessed from different
+        // threads and they could be performing clear(), get(), put() at the same time.
+        private final Map<String, String> mValues =
+            	Collections.synchronizedMap(new HashMap<String, String>());
         private long mValuesVersion = 0;
         private final Uri mUri;
 
@@ -491,8 +496,29 @@
                 mValues.clear();
                 mValuesVersion = newValuesVersion;
             }
-            if (!mValues.containsKey(name)) {
-                String value = null;
+            /*
+             *  don't look for the key using containsKey() method because (key, object) mapping
+             *  could be removed from mValues before get() is done like so:
+             *
+             *      say, mValues contains mapping for "foo"
+             *      Thread# 1
+             *          performs containsKey("foo")
+             *          receives true
+             *      Thread #2
+             *          triggers mValues.clear()
+             *      Thread#1
+             *          since containsKey("foo") = true, performs get("foo")
+             *          receives null
+             *          thats incorrect!
+             *
+             *    to avoid the above, thread#1 should do get("foo") instead of containsKey("foo")
+             *    since mValues is synchronized, get() will get a consistent value.
+             *
+             *    we don't want to make this method synchronized tho - because
+             *    holding mutex is not desirable while a call could be made to database.
+             */
+            String value = mValues.get(name);
+            if (value == null) {
                 Cursor c = null;
                 try {
                     c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
@@ -505,10 +531,8 @@
                 } finally {
                     if (c != null) c.close();
                 }
-                return value;
-            } else {
-                return mValues.get(name);
             }
+            return value;
         }
     }
 
@@ -2502,6 +2526,13 @@
         public static final String OVERRIDE_ACTION =
             "com.google.gservices.intent.action.GSERVICES_OVERRIDE";
 
+        /**
+         * Intent action to set Gservices with new values.  (Requires WRITE_GSERVICES permission.)
+         */
+        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+        public static final String UPDATE_ACTION =
+            "com.google.gservices.intent.action.GSERVICES_UPDATE";
+
         private static volatile NameValueCache mNameValueCache = null;
         private static final Object mNameValueCacheLock = new Object();
 
@@ -3110,6 +3141,13 @@
                 = "google_login_generic_auth_service";
 
         /**
+         * Duration in milliseconds after setup at which market does not reconcile applications
+         * which are installed during restore.
+         */
+        public static final String VENDING_RESTORE_WINDOW_MS = "vending_restore_window_ms";
+
+
+        /**
          * Frequency in milliseconds at which we should sync the locally installed Vending Machine
          * content with the server.
          */
@@ -3623,7 +3661,6 @@
          */
         public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
                 "search_per_source_concurrent_query_limit";
-
         /**
          * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
          * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
@@ -3638,6 +3675,32 @@
         public static final String LAST_KMSG_KB = "last_kmsg_kb";
 
         /**
+         * Maximum age of entries kept by {@link android.os.IDropBox}.
+         */
+        public static final String DROPBOX_AGE_SECONDS =
+                "dropbox_age_seconds";
+        /**
+         * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+         */
+        public static final String DROPBOX_QUOTA_KB =
+                "dropbox_quota_kb";
+        /**
+         * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+         */
+        public static final String DROPBOX_QUOTA_PERCENT =
+                "dropbox_quota_percent";
+        /**
+         * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+         */
+        public static final String DROPBOX_RESERVE_PERCENT =
+                "dropbox_reserve_percent";
+        /**
+         * Prefix for per-tag dropbox disable/enable settings.
+         */
+        public static final String DROPBOX_TAG_PREFIX =
+                "dropbox:";
+
+        /**
          * @deprecated
          * @hide
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d8c5a53..9a72d93 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -152,6 +152,12 @@
          * <P>Type: INTEGER (boolean)</P>
          */
         public static final String LOCKED = "locked";
+
+        /**
+         * Error code associated with sending or receiving this message
+         * <P>Type: INTEGER</P>
+         */
+        public static final String ERROR_CODE = "error_code";
 }
 
     /**
@@ -243,7 +249,7 @@
          * @return true if the operation succeeded
          */
         public static boolean moveMessageToFolder(Context context,
-                Uri uri, int folder) {
+                Uri uri, int folder, int error) {
             if (uri == null) {
                 return false;
             }
@@ -266,7 +272,7 @@
                 return false;
             }
 
-            ContentValues values = new ContentValues(2);
+            ContentValues values = new ContentValues(3);
 
             values.put(TYPE, folder);
             if (markAsUnread) {
@@ -274,6 +280,7 @@
             } else if (markAsRead) {
                 values.put(READ, Integer.valueOf(1));
             }
+            values.put(ERROR_CODE, error);
 
             return 1 == SqliteWrapper.update(context, context.getContentResolver(),
                             uri, values, null, null);
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index d1dd311..b32aacc 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -61,7 +61,7 @@
 
 public class BluetoothService extends IBluetooth.Stub {
     private static final String TAG = "BluetoothService";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     private int mNativeData;
     private BluetoothEventLoop mEventLoop;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a92800d..afc6864 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -294,7 +294,12 @@
                                                      lbaseline, lbottom, buf,
                                                      start, end, par, this);
 
-                            left += margin.getLeadingMargin(par);
+                            boolean useMargin = par;
+                            if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+                                int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
+                                useMargin = count > i;
+                            }
+                            left += margin.getLeadingMargin(useMargin);
                         }
                     }
                 }
@@ -1293,7 +1298,13 @@
                                                         LeadingMarginSpan.class);
 
                 for (int i = 0; i < spans.length; i++) {
-                    left += spans[i].getLeadingMargin(par);
+                    boolean margin = par;
+                    LeadingMarginSpan span = spans[i];
+                    if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+                        int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
+                        margin = count >= line;
+                    }
+                    left += span.getLeadingMargin(margin);
                 }
             }
         }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f0a5ffd..fbf1261 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -161,6 +161,7 @@
             else
                 end++;
 
+            int firstWidthLineCount = 1;
             int firstwidth = outerwidth;
             int restwidth = outerwidth;
 
@@ -171,8 +172,12 @@
 
                 sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
                 for (int i = 0; i < sp.length; i++) {
+                    LeadingMarginSpan lms = sp[i];
                     firstwidth -= sp[i].getLeadingMargin(true);
                     restwidth -= sp[i].getLeadingMargin(false);
+                    if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+                        firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+                    }
                 }
 
                 chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
@@ -750,7 +755,9 @@
                         fitascent = fitdescent = fittop = fitbottom = 0;
                         okascent = okdescent = oktop = okbottom = 0;
 
-                        width = restwidth;
+                        if (--firstWidthLineCount <= 0) {
+                            width = restwidth;
+                        }
                     }
                 }
             }
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 8e212e3..cb55329 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -33,6 +33,11 @@
                                   CharSequence text, int start, int end,
                                   boolean first, Layout layout);
 
+
+    public interface LeadingMarginSpan2 extends LeadingMarginSpan, WrapTogetherSpan {
+        public int getLeadingMarginLineCount();
+    };
+
     public static class Standard implements LeadingMarginSpan, ParcelableSpan {
         private final int mFirst, mRest;
         
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index f4593f5..e4f2b63 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -68,6 +68,7 @@
         View.AttachInfo.Callbacks {
     private static final String TAG = "ViewRoot";
     private static final boolean DBG = false;
+    private static final boolean SHOW_FPS = false;
     @SuppressWarnings({"ConstantConditionalExpression"})
     private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
     /** @noinspection PointlessBooleanExpression*/
@@ -1244,7 +1245,7 @@
                     mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
                     checkEglErrors();
 
-                    if (Config.DEBUG && ViewDebug.showFps) {
+                    if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                         int now = (int)SystemClock.elapsedRealtime();
                         if (sDrawTime != 0) {
                             nativeShowFPS(canvas, now - sDrawTime);
@@ -1356,7 +1357,7 @@
                     mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                 }
 
-                if (Config.DEBUG && ViewDebug.showFps) {
+                if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                     int now = (int)SystemClock.elapsedRealtime();
                     if (sDrawTime != 0) {
                         nativeShowFPS(canvas, now - sDrawTime);
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9456ae1..d1db35e 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -19,17 +19,21 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.ParseException;
+import android.net.Uri;
 import android.net.WebAddress;
 import android.net.http.SslCertificate;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.OpenableColumns;
 import android.util.Log;
 import android.util.TypedValue;
 
 import junit.framework.Assert;
 
+import java.io.InputStream;
 import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.Map;
@@ -463,6 +467,63 @@
     }
 
     /**
+     * Called by JNI.  Given a URI, find the associated file and return its size
+     * @param uri A String representing the URI of the desired file.
+     * @return int The size of the given file.
+     */
+    private int getFileSize(String uri) {
+        int size = 0;
+        Cursor cursor = mContext.getContentResolver().query(Uri.parse(uri),
+                new String[] { OpenableColumns.SIZE },
+                null,
+                null,
+                null);
+        if (cursor != null) {
+            try {
+                if (cursor.moveToNext()) {
+                    size = cursor.getInt(0);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
+     * copy the resource into buffer.
+     * @param uri A String representing the URI of the desired file.
+     * @param buffer The byte array to copy the data into.
+     * @param offset The offet into buffer to place the data.
+     * @param expectSize The size that the buffer has allocated for this file.
+     * @return int The size of the given file, or zero if it fails.
+     */
+    private int getFile(String uri, byte[] buffer, int offset,
+            int expectedSize) {
+        int size = 0;
+        try {
+            InputStream stream = mContext.getContentResolver()
+                            .openInputStream(Uri.parse(uri));
+            size = stream.available();
+            if (size <= expectedSize && buffer != null
+                    && buffer.length - offset >= size) {
+                stream.read(buffer, offset, size);
+            } else {
+                size = 0;
+            }
+            stream.close();
+        } catch (java.io.FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException:" + e);
+            size = 0;
+        } catch (java.io.IOException e2) {
+            Log.e(LOGTAG, "IOException: " + e2);
+            size = 0;
+        }
+        return size;
+    }
+
+    /**
      * Start loading a resource.
      * @param loaderHandle The native ResourceLoader that is the target of the
      *                     data.
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 145411c..334526b 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 import java.util.LinkedList;
 import java.util.ListIterator;
 
@@ -23,47 +25,37 @@
     them back out.  It does not optimize for returning the result in a
     single array, though this is supported in the API. It is fastest
     if the retrieval can be done via iterating through chunks.
-
-    Things to add:
-      - consider dynamically increasing our min_capacity,
-        as we see mTotalSize increase
 */
 class ByteArrayBuilder {
 
     private static final int DEFAULT_CAPACITY = 8192;
 
+    // Global pool of chunks to be used by other ByteArrayBuilders.
+    private static final LinkedList<SoftReference<Chunk>> sPool =
+            new LinkedList<SoftReference<Chunk>>();
+    // Reference queue for processing gc'd entries.
+    private static final ReferenceQueue<Chunk> sQueue =
+            new ReferenceQueue<Chunk>();
+
     private LinkedList<Chunk> mChunks;
 
-    /** free pool */
-    private LinkedList<Chunk> mPool;
-
-    private int mMinCapacity;
-
     public ByteArrayBuilder() {
-        init(0);
-    }
-
-    public ByteArrayBuilder(int minCapacity) {
-        init(minCapacity);
-    }
-
-    private void init(int minCapacity) {
         mChunks = new LinkedList<Chunk>();
-        mPool = new LinkedList<Chunk>();
-
-        if (minCapacity <= 0) {
-            minCapacity = DEFAULT_CAPACITY;
-        }
-        mMinCapacity = minCapacity;
-    }
-
-    public void append(byte[] array) {
-        append(array, 0, array.length);
     }
 
     public synchronized void append(byte[] array, int offset, int length) {
         while (length > 0) {
-            Chunk c = appendChunk(length);
+            Chunk c = null;
+            if (mChunks.isEmpty()) {
+                c = obtainChunk(length);
+                mChunks.addLast(c);
+            } else {
+                c = mChunks.getLast();
+                if (c.mLength == c.mArray.length) {
+                    c = obtainChunk(length);
+                    mChunks.addLast(c);
+                }
+            }
             int amount = Math.min(length, c.mArray.length - c.mLength);
             System.arraycopy(array, offset, c.mArray, c.mLength, amount);
             c.mLength += amount;
@@ -75,7 +67,7 @@
     /**
      * The fastest way to retrieve the data is to iterate through the
      * chunks.  This returns the first chunk.  Note: this pulls the
-     * chunk out of the queue.  The caller must call releaseChunk() to
+     * chunk out of the queue.  The caller must call Chunk.release() to
      * dispose of it.
      */
     public synchronized Chunk getFirstChunk() {
@@ -83,23 +75,11 @@
         return mChunks.removeFirst();
     }
 
-    /**
-     * recycles chunk
-     */
-    public synchronized void releaseChunk(Chunk c) {
-        c.mLength = 0;
-        mPool.addLast(c);
-    }
-
-    public boolean isEmpty() {
+    public synchronized boolean isEmpty() {
         return mChunks.isEmpty();
     }
 
-    public int size() {
-        return mChunks.size();
-    }
-
-    public int getByteSize() {
+    public synchronized int getByteSize() {
         int total = 0;
         ListIterator<Chunk> it = mChunks.listIterator(0);
         while (it.hasNext()) {
@@ -112,37 +92,40 @@
     public synchronized void clear() {
         Chunk c = getFirstChunk();
         while (c != null) {
-            releaseChunk(c);
+            c.release();
             c = getFirstChunk();
         }
     }
 
-    private Chunk appendChunk(int length) {
-        if (length < mMinCapacity) {
-            length = mMinCapacity;
-        }
-
-        Chunk c;
-        if (mChunks.isEmpty()) {
-            c = obtainChunk(length);
-        } else {
-            c = mChunks.getLast();
-            if (c.mLength == c.mArray.length) {
-                c = obtainChunk(length);
+    // Must be called with lock held on sPool.
+    private void processPoolLocked() {
+        while (true) {
+            SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
+            if (entry == null) {
+                break;
             }
+            sPool.remove(entry);
         }
-        return c;
     }
 
     private Chunk obtainChunk(int length) {
-        Chunk c;
-        if (mPool.isEmpty()) {
-            c = new Chunk(length);
-        } else {
-            c = mPool.removeFirst();
+        // Correct a small length.
+        if (length < DEFAULT_CAPACITY) {
+            length = DEFAULT_CAPACITY;
         }
-        mChunks.addLast(c);
-        return c;
+        synchronized (sPool) {
+            // Process any queued references and remove them from the pool.
+            processPoolLocked();
+            if (!sPool.isEmpty()) {
+                Chunk c = sPool.removeFirst().get();
+                // The first item may have been queued after processPoolLocked
+                // so check for null.
+                if (c != null) {
+                    return c;
+                }
+            }
+            return new Chunk(length);
+        }
     }
 
     public static class Chunk {
@@ -153,5 +136,19 @@
             mArray = new byte[length];
             mLength = 0;
         }
+
+        /**
+         * Release the chunk and make it available for reuse.
+         */
+        public void release() {
+            mLength = 0;
+            synchronized (sPool) {
+                // Add the chunk back to the pool as a SoftReference so it can
+                // be gc'd if needed.
+                sPool.offer(new SoftReference<Chunk>(this, sQueue));
+                sPool.notifyAll();
+            }
+        }
+
     }
 }
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 8d55247..4f8c227 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -107,6 +107,7 @@
     private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
     private static final int RECEIVED_TOUCH_ICON_URL             = 132;
     private static final int GET_VISITED_HISTORY                 = 133;
+    private static final int OPEN_FILE_CHOOSER                   = 134;
 
     // Message triggered by the client to resume execution
     private static final int NOTIFY                              = 200;
@@ -149,6 +150,16 @@
     }
 
     /**
+     * Get the WebViewClient.
+     * @return the current WebViewClient instance.
+     *
+     *@hide pending API council approval.
+     */
+    public WebViewClient getWebViewClient() {
+       return mWebViewClient;
+    }
+
+    /**
      * Set the WebChromeClient.
      * @param client An implementation of WebChromeClient.
      */
@@ -240,8 +251,10 @@
                 break;
 
             case PAGE_FINISHED:
+                String finishedUrl = (String) msg.obj;
+                mWebView.onPageFinished(finishedUrl);
                 if (mWebViewClient != null) {
-                    mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
+                    mWebViewClient.onPageFinished(mWebView, finishedUrl);
                 }
                 break;
                 
@@ -662,6 +675,12 @@
                     mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
                 }
                 break;
+
+            case OPEN_FILE_CHOOSER:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+                }
+                break;
         }
     }
 
@@ -760,11 +779,6 @@
     }
 
     public void onPageFinished(String url) {
-        // Do an unsynchronized quick check to avoid posting if no callback has
-        // been set.
-        if (mWebViewClient == null) {
-            return;
-        }
         // Performance probe
         if (PERF_PROBE) {
             // un-comment this if PERF_PROBE is true
@@ -1002,10 +1016,10 @@
     public void onProgressChanged(int newProgress) {
         // Synchronize so that mLatestProgress is up-to-date.
         synchronized (this) {
-            mLatestProgress = newProgress;
-            if (mWebChromeClient == null) {
+            if (mWebChromeClient == null || mLatestProgress == newProgress) {
                 return;
             }
+            mLatestProgress = newProgress;
             if (!mProgressUpdatePending) {
                 sendEmptyMessage(PROGRESS);
                 mProgressUpdatePending = true;
@@ -1348,4 +1362,40 @@
         msg.obj = callback;
         sendMessage(msg);
     }
+
+    private class UploadFile implements ValueCallback<Uri> {
+        private Uri mValue;
+        public void onReceiveValue(Uri value) {
+            mValue = value;
+            synchronized (CallbackProxy.this) {
+                CallbackProxy.this.notify();
+            }
+        }
+        public Uri getResult() {
+            return mValue;
+        }
+    }
+
+    /**
+     * Called by WebViewCore to open a file chooser.
+     */
+    /* package */ Uri openFileChooser() {
+        if (mWebChromeClient == null) {
+            return null;
+        }
+        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
+        UploadFile uploadFile = new UploadFile();
+        myMessage.obj = uploadFile;
+        synchronized (this) {
+            sendMessage(myMessage);
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                Log.e(LOGTAG,
+                        "Caught exception while waiting for openFileChooser");
+                Log.e(LOGTAG, Log.getStackTraceString(e));
+            }
+        }
+        return uploadFile.getResult();
+    }
 }
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index b7a9065..3e0be1c 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -199,6 +199,8 @@
     public void playbackEnded() {
         Message msg = Message.obtain(mWebCoreHandler, ENDED);
         mWebCoreHandler.sendMessage(msg);
+        // also send a message to ourselves to return to the WebView
+        sendMessage(obtainMessage(ENDED));
     }
 
     // Handler for the messages from WebCore thread to the UI thread.
@@ -224,6 +226,7 @@
                 VideoPlayer.pause(this);
                 break;
             }
+            case ENDED:
             case ERROR: {
                 WebChromeClient client = mWebView.getWebChromeClient();
                 if (client != null) {
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index 2f46f2b..042953c 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -50,13 +50,15 @@
      * Wdy Mon DD HH:MM:SS YYYY GMT
      * 
      * HH can be H if the first digit is zero.
+     * 
+     * Mon can be the full name of the month.
      */
     private static final String HTTP_DATE_RFC_REGEXP =
-            "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+            "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
             + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
 
     private static final String HTTP_DATE_ANSIC_REGEXP =
-            "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+            "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
             + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
 
     /**
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 4c17f99..5c0ce3c 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -78,7 +78,7 @@
 
     private static int sNativeLoaderCount;
 
-    private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+    private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
 
     private String   mUrl;
     private WebAddress mUri;
@@ -522,17 +522,18 @@
      * IMPORTANT: as this is called from network thread, can't call native
      * directly
      * XXX: Unlike the other network thread methods, this method can do the
-     * work of decoding the data and appending it to the data builder because
-     * mDataBuilder is a thread-safe structure.
+     * work of decoding the data and appending it to the data builder.
      */
     public void data(byte[] data, int length) {
         if (DebugFlags.LOAD_LISTENER) {
             Log.v(LOGTAG, "LoadListener.data(): url: " + url());
         }
 
-        // Synchronize on mData because commitLoad may write mData to WebCore
-        // and we don't want to replace mData or mDataLength at the same time
-        // as a write.
+        // The reason isEmpty() and append() need to synchronized together is
+        // because it is possible for getFirstChunk() to be called multiple
+        // times between isEmpty() and append(). This could cause commitLoad()
+        // to finish before processing the newly appended data and no message
+        // will be sent.
         boolean sendMessage = false;
         synchronized (mDataBuilder) {
             sendMessage = mDataBuilder.isEmpty();
@@ -1009,28 +1010,34 @@
         if (mIsMainPageLoader) {
             String type = sCertificateTypeMap.get(mMimeType);
             if (type != null) {
-                // In the case of downloading certificate, we will save it to
-                // the KeyStore and stop the current loading so that it will not
-                // generate a new history page
-                byte[] cert = new byte[mDataBuilder.getByteSize()];
-                int offset = 0;
-                while (true) {
-                    ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
-                    if (c == null) break;
+                // This must be synchronized so that no more data can be added
+                // after getByteSize returns.
+                synchronized (mDataBuilder) {
+                    // In the case of downloading certificate, we will save it
+                    // to the KeyStore and stop the current loading so that it
+                    // will not generate a new history page
+                    byte[] cert = new byte[mDataBuilder.getByteSize()];
+                    int offset = 0;
+                    while (true) {
+                        ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
+                        if (c == null) break;
 
-                    if (c.mLength != 0) {
-                        System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
-                        offset += c.mLength;
+                        if (c.mLength != 0) {
+                            System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
+                            offset += c.mLength;
+                        }
+                        c.release();
                     }
-                    mDataBuilder.releaseChunk(c);
+                    CertTool.addCertificate(mContext, type, cert);
+                    mBrowserFrame.stopLoading();
+                    return;
                 }
-                CertTool.addCertificate(mContext, type, cert);
-                mBrowserFrame.stopLoading();
-                return;
             }
         }
 
-        // Give the data to WebKit now
+        // Give the data to WebKit now. We don't have to synchronize on
+        // mDataBuilder here because pulling each chunk removes it from the
+        // internal list so it cannot be modified.
         PerfChecker checker = new PerfChecker();
         ByteArrayBuilder.Chunk c;
         while (true) {
@@ -1047,7 +1054,7 @@
                 }
                 nativeAddData(c.mArray, c.mLength);
             }
-            mDataBuilder.releaseChunk(c);
+            c.release();
             checker.responseAlert("res nativeAddData");
         }
     }
@@ -1210,8 +1217,17 @@
                 // mRequestHandle can be null when the request was satisfied
                 // by the cache, and the cache returned a redirect
                 if (mRequestHandle != null) {
-                    mRequestHandle.setupRedirect(mUrl, mStatusCode,
-                            mRequestHeaders);
+                    try {
+                        mRequestHandle.setupRedirect(mUrl, mStatusCode,
+                                mRequestHeaders);
+                    } catch(RuntimeException e) {
+                        Log.e(LOGTAG, e.getMessage());
+                        // Signal a bad url error if we could not load the
+                        // redirection.
+                        handleError(EventHandler.ERROR_BAD_URL,
+                                mContext.getString(R.string.httpErrorBadUrl));
+                        return;
+                    }
                 } else {
                     // If the original request came from the cache, there is no
                     // RequestHandle, we have to create a new one through
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index fffba1b..84a8a3c 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -431,6 +431,8 @@
             sMimeTypeMap.loadEntry("text/calendar", "icz");
             sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
             sMimeTypeMap.loadEntry("text/css", "css");
+            sMimeTypeMap.loadEntry("text/html", "htm");
+            sMimeTypeMap.loadEntry("text/html", "html");
             sMimeTypeMap.loadEntry("text/h323", "323");
             sMimeTypeMap.loadEntry("text/iuls", "uls");
             sMimeTypeMap.loadEntry("text/mathml", "mml");
@@ -481,6 +483,7 @@
             sMimeTypeMap.loadEntry("video/dv", "dif");
             sMimeTypeMap.loadEntry("video/dv", "dv");
             sMimeTypeMap.loadEntry("video/fli", "fli");
+            sMimeTypeMap.loadEntry("video/m4v", "m4v");
             sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
             sMimeTypeMap.loadEntry("video/mpeg", "mpg");
             sMimeTypeMap.loadEntry("video/mpeg", "mpe");
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index af0cb1e..b53e404 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -180,20 +180,24 @@
         }
 
         RequestQueue q = mRequestQueue;
+        RequestHandle handle = null;
         if (loader.isSynchronous()) {
-            q = new RequestQueue(loader.getContext(), 1);
-        }
-
-        RequestHandle handle = q.queueRequest(
-                url, loader.getWebAddress(), method, headers, loader,
-                bodyProvider, bodyLength);
-        loader.attachRequestHandle(handle);
-
-        if (loader.isSynchronous()) {
-            handle.waitUntilComplete();
+            handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
+                    method, headers, loader, bodyProvider, bodyLength);
+            loader.attachRequestHandle(handle);
+            handle.processRequest();
             loader.loadSynchronousMessages();
-            q.shutdown();
+        } else {
+            handle = q.queueRequest(url, loader.getWebAddress(), method,
+                    headers, loader, bodyProvider, bodyLength);
+            // FIXME: Although this is probably a rare condition, normal network
+            // requests are processed in a separate thread. This means that it
+            // is possible to process part of the request before setting the
+            // request handle on the loader. We should probably refactor this to
+            // ensure the handle is attached before processing begins.
+            loader.attachRequestHandle(handle);
         }
+
         return true;
     }
 
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 232ed36..211e5e4 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -367,19 +367,23 @@
 
     /** Regex used to parse content-disposition headers */
     private static final Pattern CONTENT_DISPOSITION_PATTERN =
-            Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+            Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+            Pattern.CASE_INSENSITIVE);
 
     /*
      * Parse the Content-Disposition HTTP Header. The format of the header
      * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
      * This header provides a filename for content that is going to be
      * downloaded to the file system. We only support the attachment type.
+     * Note that RFC 2616 specifies the filename value must be double-quoted.
+     * Unfortunately some servers do not quote the value so to maintain
+     * consistent behaviour with other browsers, we allow unquoted values too.
      */
     static String parseContentDisposition(String contentDisposition) {
         try {
             Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
             if (m.find()) {
-                return m.group(1);
+                return m.group(2);
             }
         } catch (IllegalStateException ex) {
              // This function is defined as returning null when it can't parse the header
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 7f5b862..ae4f7c2 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Message;
 import android.view.View;
 
@@ -302,4 +303,13 @@
     public void getVisitedHistory(ValueCallback<String[]> callback) {
     }
 
+    /**
+     * Tell the client to open a file chooser.
+     * @param uploadFile A ValueCallback to set the URI of the file to upload.
+     *      onReceiveValue must be called to wake up the thread.
+     * @hide
+     */
+    public void openFileChooser(ValueCallback<Uri> uploadFile) {
+        uploadFile.onReceiveValue(null);
+    }
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 4fedec9..79d8c03 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1007,7 +1007,8 @@
      *     should never be null.
      */
     public synchronized void setGeolocationDatabasePath(String databasePath) {
-        if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+        if (databasePath != null
+                && !databasePath.equals(mGeolocationDatabasePath)) {
             mGeolocationDatabasePath = databasePath;
             postSync();
         }
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index e0d41c2..71b1f9f 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -84,13 +84,24 @@
     // True if the most recent drag event has caused either the TextView to
     // scroll or the web page to scroll.  Gets reset after a touch down.
     private boolean         mScrolled;
-    // Gets set to true when the the IME jumps to the next textfield.  When this
-    // happens, the next time the user hits a key it is okay for the focus
-    // pointer to not match the WebTextView's node pointer
+    // Gets set to true any time the WebTextView has focus, but the navigation
+    // cache does not yet know that the focus has been changed.  This happens
+    // if the user presses "Next", if the user moves the cursor to a textfield
+    // and starts typing or clicks the trackball/center key, and when the user
+    // touches a textfield.
     boolean                 mOkayForFocusNotToMatch;
     // Whether or not a selection change was generated from webkit.  If it was,
     // we do not need to pass the selection back to webkit.
     private boolean         mFromWebKit;
+    // Whether or not a selection change was generated from the WebTextView
+    // gaining focus.  If it is, we do not want to pass it to webkit.  This
+    // selection comes from the MovementMethod, but we behave differently.  If
+    // WebTextView gained focus from a touch, webkit will determine the
+    // selection.
+    private boolean         mFromFocusChange;
+    // Whether or not a selection change was generated from setInputType.  We
+    // do not want to pass this change to webkit.
+    private boolean         mFromSetInputType;
     private boolean         mGotTouchDown;
     private boolean         mInSetTextAndKeepSelection;
     // Array to store the final character added in onTextChanged, so that its
@@ -136,20 +147,23 @@
                 isArrowKey = true;
                 break;
         }
-        if (!isArrowKey && !mOkayForFocusNotToMatch
-                && mWebView.nativeFocusNodePointer() != mNodePointer) {
-            mWebView.nativeClearCursor();
-            // Do not call remove() here, which hides the soft keyboard.  If
-            // the soft keyboard is being displayed, the user will still want
-            // it there.
-            mWebView.removeView(this);
-            mWebView.requestFocus();
-            return mWebView.dispatchKeyEvent(event);
-        }
-        // After a jump to next textfield and the first key press, the cursor
-        // and focus will once again match, so reset this value.
-        mOkayForFocusNotToMatch = false;
 
+        if (down) {
+            if (mOkayForFocusNotToMatch) {
+                if (mWebView.nativeFocusNodePointer() == mNodePointer) {
+                    mOkayForFocusNotToMatch = false;
+                }
+            } else if (mWebView.nativeFocusNodePointer() != mNodePointer
+                    && !isArrowKey) {
+                mWebView.nativeClearCursor();
+                // Do not call remove() here, which hides the soft keyboard.  If
+                // the soft keyboard is being displayed, the user will still want
+                // it there.
+                mWebView.removeView(this);
+                mWebView.requestFocus();
+                return mWebView.dispatchKeyEvent(event);
+            }
+        }
         Spannable text = (Spannable) getText();
         int oldLength = text.length();
         // Normally the delete key's dom events are sent via onTextChanged.
@@ -185,7 +199,7 @@
             }
             // Center key should be passed to a potential onClick
             if (!down) {
-                mWebView.shortPressOnTextField();
+                mWebView.centerKeyPressOnTextField();
             }
             // Pass to super to handle longpress.
             return super.dispatchKeyEvent(event);
@@ -303,15 +317,19 @@
     public void onEditorAction(int actionCode) {
         switch (actionCode) {
         case EditorInfo.IME_ACTION_NEXT:
-            mWebView.nativeMoveCursorToNextTextInput();
-            // Preemptively rebuild the WebTextView, so that the action will
-            // be set properly.
-            mWebView.rebuildWebTextView();
             // Since the cursor will no longer be in the same place as the
             // focus, set the focus controller back to inactive
             mWebView.setFocusControllerInactive();
-            mWebView.invalidate();
+            mWebView.nativeMoveCursorToNextTextInput();
             mOkayForFocusNotToMatch = true;
+            // Pass the click to set the focus to the textfield which will now
+            // have the cursor.
+            mWebView.centerKeyPressOnTextField();
+            // Preemptively rebuild the WebTextView, so that the action will
+            // be set properly.
+            mWebView.rebuildWebTextView();
+            setDefaultSelection();
+            mWebView.invalidate();
             break;
         case EditorInfo.IME_ACTION_DONE:
             super.onEditorAction(actionCode);
@@ -331,6 +349,14 @@
     }
 
     @Override
+    protected void onFocusChanged(boolean focused, int direction,
+            Rect previouslyFocusedRect) {
+        mFromFocusChange = true;
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+        mFromFocusChange = false;
+    }
+
+    @Override
     protected void onSelectionChanged(int selStart, int selEnd) {
         // This code is copied from TextView.onDraw().  That code does not get
         // executed, however, because the WebTextView does not draw, allowing
@@ -342,7 +368,8 @@
             int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
             imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
         }
-        if (!mFromWebKit && mWebView != null) {
+        if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
+                && mWebView != null) {
             if (DebugFlags.WEB_TEXT_VIEW) {
                 Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
                         + " selEnd=" + selEnd);
@@ -591,6 +618,17 @@
     }
 
     /**
+     * Sets the selection when the user clicks on a textfield or textarea with
+     * the trackball or center key, or starts typing into it without clicking on
+     * it.
+     */
+    /* package */ void setDefaultSelection() {
+        Spannable text = (Spannable) getText();
+        int selection = mSingle ? text.length() : 0;
+        Selection. setSelection(text, selection, selection);
+    }
+
+    /**
      * Determine whether to use the system-wide password disguising method,
      * or to use none.
      * @param   inPassword  True if the textfield is a password field.
@@ -660,6 +698,13 @@
         setTextColor(Color.BLACK);
     }
 
+    @Override
+    public void setInputType(int type) {
+        mFromSetInputType = true;
+        super.setInputType(type);
+        mFromSetInputType = false;
+    }
+
     /* package */ void setMaxLength(int maxLength) {
         mMaxLength = maxLength;
         if (-1 == maxLength) {
@@ -761,32 +806,6 @@
     }
 
     /**
-     * Set the text for this WebTextView, and set the selection to (start, end)
-     * @param   text    Text to go into this WebTextView.
-     * @param   start   Beginning of the selection.
-     * @param   end     End of the selection.
-     */
-    /* package */ void setText(CharSequence text, int start, int end) {
-        mPreChange = text.toString();
-        setText(text);
-        Spannable span = (Spannable) getText();
-        int length = span.length();
-        if (end > length) {
-            end = length;
-        }
-        if (start < 0) {
-            start = 0;
-        } else if (start > length) {
-            start = length;
-        }
-        if (DebugFlags.WEB_TEXT_VIEW) {
-            Log.v(LOGTAG, "setText start=" + start
-                    + " end=" + end);
-        }
-        Selection.setSelection(span, start, end);
-    }
-
-    /**
      * Set the text to the new string, but use the old selection, making sure
      * to keep it within the new string.
      * @param   text    The new text to place in the textfield.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 06c70ca..630450d 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -64,7 +64,9 @@
 import android.widget.Adapter;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.Scroller;
 import android.widget.Toast;
@@ -82,6 +84,8 @@
 import java.util.List;
 import java.util.Map;
 
+import junit.framework.Assert;
+
 /**
  * <p>A View that displays web pages. This class is the basis upon which you
  * can roll your own web browser or simply display some online content within your Activity.
@@ -390,6 +394,8 @@
     private static final int LONG_PRESS_TIMEOUT = 1000;
     // needed to avoid flinging after a pause of no movement
     private static final int MIN_FLING_TIME = 250;
+    // draw unfiltered after drag is held without movement
+    private static final int MOTIONLESS_TIME = 100;
     // The time that the Zoom Controls are visible before fading away
     private static final long ZOOM_CONTROLS_TIMEOUT =
             ViewConfiguration.getZoomControlsTimeout();
@@ -427,6 +433,10 @@
     private Scroller mScroller;
 
     private boolean mWrapContent;
+    private static final int MOTIONLESS_FALSE           = 0;
+    private static final int MOTIONLESS_PENDING         = 1;
+    private static final int MOTIONLESS_TRUE            = 2;
+    private int mHeldMotionless;
 
     /**
      * Private message ids
@@ -438,6 +448,8 @@
     private static final int RELEASE_SINGLE_TAP         = 5;
     private static final int REQUEST_FORM_DATA          = 6;
     private static final int RESUME_WEBCORE_UPDATE      = 7;
+    private static final int DRAG_HELD_MOTIONLESS       = 8;
+    private static final int AWAKEN_SCROLL_BARS         = 9;
 
     //! arg1=x, arg2=y
     static final int SCROLL_TO_MSG_ID                   = 10;
@@ -450,6 +462,7 @@
     static final int UPDATE_TEXT_ENTRY_MSG_ID           = 15;
     static final int WEBCORE_INITIALIZED_MSG_ID         = 16;
     static final int UPDATE_TEXTFIELD_TEXT_MSG_ID       = 17;
+    static final int FIND_AGAIN                         = 18;
     static final int MOVE_OUT_OF_PLUGIN                 = 19;
     static final int CLEAR_TEXT_ENTRY                   = 20;
     static final int UPDATE_TEXT_SELECTION_MSG_ID       = 21;
@@ -468,9 +481,9 @@
         "SWITCH_TO_LONGPRESS", //            = 4;
         "RELEASE_SINGLE_TAP", //             = 5;
         "REQUEST_FORM_DATA", //              = 6;
-        "SWITCH_TO_CLICK", //                = 7;
-        "RESUME_WEBCORE_UPDATE", //          = 8;
-        "9",
+        "RESUME_WEBCORE_UPDATE", //          = 7;
+        "DRAG_HELD_MOTIONLESS", //           = 8;
+        "AWAKEN_SCROLL_BARS", //             = 9;
         "SCROLL_TO_MSG_ID", //               = 10;
         "SCROLL_BY_MSG_ID", //               = 11;
         "SPAWN_SCROLL_TO_MSG_ID", //         = 12;
@@ -479,7 +492,7 @@
         "UPDATE_TEXT_ENTRY_MSG_ID", //       = 15;
         "WEBCORE_INITIALIZED_MSG_ID", //     = 16;
         "UPDATE_TEXTFIELD_TEXT_MSG_ID", //   = 17;
-        "18", //        = 18;
+        "FIND_AGAIN", //                     = 18;
         "MOVE_OUT_OF_PLUGIN", //             = 19;
         "CLEAR_TEXT_ENTRY", //               = 20;
         "UPDATE_TEXT_SELECTION_MSG_ID", //   = 21;
@@ -535,11 +548,10 @@
     private boolean mUserScroll = false;
 
     private int mSnapScrollMode = SNAP_NONE;
-    private static final int SNAP_NONE = 1;
-    private static final int SNAP_X = 2;
-    private static final int SNAP_Y = 3;
-    private static final int SNAP_X_LOCK = 4;
-    private static final int SNAP_Y_LOCK = 5;
+    private static final int SNAP_NONE = 0;
+    private static final int SNAP_LOCK = 1; // not a separate state
+    private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
+    private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
     private boolean mSnapPositive;
 
     // Used to match key downs and key ups
@@ -2351,6 +2363,7 @@
         }
         int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
         invalidate();
+        mLastFind = find;
         return result;
     }
 
@@ -2358,6 +2371,9 @@
     // or not we draw the highlights for matches.
     private boolean mFindIsUp;
     private int mFindHeight;
+    // Keep track of the last string sent, so we can search again after an
+    // orientation change or the dismissal of the soft keyboard.
+    private String mLastFind;
 
     /**
      * Return the first substring consisting of the address of a physical
@@ -2532,6 +2548,41 @@
         }
     }
 
+    /**
+     * Called by CallbackProxy when the page finishes loading.
+     * @param url The URL of the page which has finished loading.
+     */
+    /* package */ void onPageFinished(String url) {
+        if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
+            // If the user is now on a different page, or has scrolled the page
+            // past the point where the title bar is offscreen, ignore the
+            // scroll request.
+            if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
+                    && mScrollX == 0 && mScrollY == 0) {
+                pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
+                        SLIDE_TITLE_DURATION);
+            }
+            mPageThatNeedsToSlideTitleBarOffScreen = null;
+        }
+    }
+
+    /**
+     * The URL of a page that sent a message to scroll the title bar off screen.
+     *
+     * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
+     * title bar off the screen.  Sometimes, the scroll position is set before
+     * the page finishes loading.  Rather than scrolling while the page is still
+     * loading, keep track of the URL and new scroll position so we can perform
+     * the scroll once the page finishes loading.
+     */
+    private String mPageThatNeedsToSlideTitleBarOffScreen;
+
+    /**
+     * The destination Y scroll position to be used when the page finishes
+     * loading.  See mPageThatNeedsToSlideTitleBarOffScreen.
+     */
+    private int mYDistanceToSlideTitleOffScreen;
+
     // scale from content to view coordinates, and pin
     // return true if pin caused the final x/y different than the request cx/cy,
     // and a future scroll may reach the request cx/cy after our size has
@@ -2566,8 +2617,18 @@
         // page, assume this is an attempt to scroll off the title bar, and
         // animate the title bar off screen slowly enough that the user can see
         // it.
-        if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) {
-            pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+        if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0
+                && mTitleBar != null) {
+            // FIXME: 100 should be defined somewhere as our max progress.
+            if (getProgress() < 100) {
+                // Wait to scroll the title bar off screen until the page has
+                // finished loading.  Keep track of the URL and the destination
+                // Y position
+                mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
+                mYDistanceToSlideTitleOffScreen = vy;
+            } else {
+                pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+            }
             // Since we are animating, we have not yet reached the desired
             // scroll position.  Do not return true to request another attempt
             return false;
@@ -2608,12 +2669,12 @@
 
         if (mHeightCanMeasure) {
             if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
-                    && updateLayout) {
+                    || updateLayout) {
                 requestLayout();
             }
         } else if (mWidthCanMeasure) {
             if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
-                    && updateLayout) {
+                    || updateLayout) {
                 requestLayout();
             }
         } else {
@@ -2633,6 +2694,16 @@
     }
 
     /**
+     * Gets the WebViewClient
+     * @return the current WebViewClient instance.
+     *
+     *@hide pending API council approval.
+     */
+    public WebViewClient getWebViewClient() {
+        return mCallbackProxy.getWebViewClient();
+    }
+
+    /**
      * Register the interface to be used when content can not be handled by
      * the rendering engine, and should be downloaded instead. This will replace
      * the current handler.
@@ -2813,10 +2884,7 @@
     public boolean performLongClick() {
         if (mNativeClass != 0 && nativeCursorIsTextInput()) {
             // Send the click so that the textfield is in focus
-            // FIXME: When we start respecting changes to the native textfield's
-            // selection, need to make sure that this does not change it.
-            mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
-                    nativeCursorNodePointer());
+            centerKeyPressOnTextField();
             rebuildWebTextView();
         }
         if (inEditingMode()) {
@@ -2839,6 +2907,21 @@
      */
     private boolean mNeedToAdjustWebTextView;
 
+    private boolean didUpdateTextViewBounds(boolean allowIntersect) {
+        Rect contentBounds = nativeFocusCandidateNodeBounds();
+        Rect vBox = contentToViewRect(contentBounds);
+        Rect visibleRect = new Rect();
+        calcOurVisibleRect(visibleRect);
+        if (allowIntersect ? Rect.intersects(visibleRect, vBox) :
+                visibleRect.contains(vBox)) {
+            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
+                    vBox.height());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     private void drawCoreAndCursorRing(Canvas canvas, int color,
         boolean drawCursorRing) {
         if (mDrawHistory) {
@@ -2848,8 +2931,22 @@
         }
 
         boolean animateZoom = mZoomScale != 0;
-        boolean animateScroll = !mScroller.isFinished()
-                || mVelocityTracker != null;
+        boolean animateScroll = (!mScroller.isFinished()
+                || mVelocityTracker != null)
+                && (mTouchMode != TOUCH_DRAG_MODE ||
+                mHeldMotionless != MOTIONLESS_TRUE);
+        if (mTouchMode == TOUCH_DRAG_MODE) {
+            if (mHeldMotionless == MOTIONLESS_PENDING) {
+                mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+                mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+                mHeldMotionless = MOTIONLESS_FALSE;
+            }
+            if (mHeldMotionless == MOTIONLESS_FALSE) {
+                mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                        .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+                mHeldMotionless = MOTIONLESS_PENDING;
+            }
+        }
         if (animateZoom) {
             float zoomScale;
             int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
@@ -2866,19 +2963,13 @@
                 invalidate();
                 if (mNeedToAdjustWebTextView) {
                     mNeedToAdjustWebTextView = false;
-                    Rect contentBounds = nativeFocusCandidateNodeBounds();
-                    Rect vBox = contentToViewRect(contentBounds);
-                    Rect visibleRect = new Rect();
-                    calcOurVisibleRect(visibleRect);
-                    if (visibleRect.contains(vBox)) {
-                        // As a result of the zoom, the textfield is now on
-                        // screen.  Place the WebTextView in its new place,
-                        // accounting for our new scroll/zoom values.
+                    // As a result of the zoom, the textfield is now on
+                    // screen.  Place the WebTextView in its new place,
+                    // accounting for our new scroll/zoom values.
+                    if (didUpdateTextViewBounds(false)) {
                         mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                                 contentToViewDimension(
                                 nativeFocusCandidateTextSize()));
-                        mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
-                                vBox.height());
                         // If it is a password field, start drawing the
                         // WebTextView once again.
                         if (nativeFocusCandidateIsPassword()) {
@@ -2930,11 +3021,12 @@
 
         if (mNativeClass == 0) return;
         if (mShiftIsPressed && !animateZoom) {
-            if (mTouchSelection) {
+            if (mTouchSelection || mExtendSelection) {
                 nativeDrawSelectionRegion(canvas);
-            } else {
-                nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(),
-                        mSelectX, mSelectY, mExtendSelection);
+            }
+            if (!mTouchSelection) {
+                nativeDrawSelectionPointer(canvas, mInvActualScale, mSelectX,
+                        mSelectY - getTitleHeight(), mExtendSelection);
             }
         } else if (drawCursorRing) {
             if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
@@ -2954,6 +3046,10 @@
         if (mFindIsUp && !animateScroll) {
             nativeDrawMatches(canvas);
         }
+        if (mFocusSizeChanged) {
+            mFocusSizeChanged = false;
+            didUpdateTextViewBounds(true);
+        }
     }
 
     // draw history
@@ -3049,6 +3145,16 @@
         imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
     }
 
+    /**
+     * Only for calling from JNI.  Allows a click on an unfocused textfield to
+     * put the textfield in focus.
+     */
+    private void setOkayNotToMatch() {
+        if (inEditingMode()) {
+            mWebTextView.mOkayForFocusNotToMatch = true;
+        }
+    }
+
     /*
      * This method checks the current focus and cursor and potentially rebuilds
      * mWebTextView to have the appropriate properties, such as password,
@@ -3104,6 +3210,7 @@
                     && nativeTextGeneration() == mTextGeneration) {
                 mWebTextView.setTextAndKeepSelection(text);
             } else {
+                // FIXME: Determine whether this is necessary.
                 Selection.setSelection(spannable, start, end);
             }
         } else {
@@ -3136,34 +3243,12 @@
             mWebTextView.setSingleLine(isTextField);
             mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
             if (null == text) {
-                mWebTextView.setText("", 0, 0);
                 if (DebugFlags.WEB_VIEW) {
                     Log.v(LOGTAG, "rebuildWebTextView null == text");
                 }
-            } else {
-                // Change to true to enable the old style behavior, where
-                // entering a textfield/textarea always set the selection to the
-                // whole field.  This was desirable for the case where the user
-                // intends to scroll past the field using the trackball.
-                // However, it causes a problem when replying to emails - the
-                // user expects the cursor to be at the beginning of the
-                // textarea.  Testing out a new behavior, where textfields set
-                // selection at the end, and textareas at the beginning.
-                if (false) {
-                    mWebTextView.setText(text, 0, text.length());
-                } else if (isTextField) {
-                    int length = text.length();
-                    mWebTextView.setText(text, length, length);
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "rebuildWebTextView length=" + length);
-                    }
-                } else {
-                    mWebTextView.setText(text, 0, 0);
-                    if (DebugFlags.WEB_VIEW) {
-                        Log.v(LOGTAG, "rebuildWebTextView !isTextField");
-                    }
-                }
+                text = "";
             }
+            mWebTextView.setTextAndKeepSelection(text);
             mWebTextView.requestFocus();
         }
     }
@@ -3230,23 +3315,22 @@
         if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
                 && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
                 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
-            mExtendSelection = false;
-            mShiftIsPressed = true;
-            if (nativeHasCursorNode()) {
-                Rect rect = nativeCursorNodeBounds();
-                mSelectX = contentToViewX(rect.left);
-                mSelectY = contentToViewY(rect.top);
-            } else {
-                mSelectX = mScrollX + (int) mLastTouchX;
-                mSelectY = mScrollY + (int) mLastTouchY;
-            }
-            nativeHideCursor();
-       }
+            setUpSelectXY();
+        }
 
         if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
             // always handle the navigation keys in the UI thread
             switchOutDrawHistory();
+            if (mShiftIsPressed) {
+                int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                    ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
+                int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
+                    -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
+                int multiplier = event.getRepeatCount() + 1;
+                moveSelection(xRate * multiplier, yRate * multiplier);
+                return true;
+            }
             if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
                 playSoundEffect(keyCodeToSoundsEffect(keyCode));
                 return true;
@@ -3258,6 +3342,9 @@
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             switchOutDrawHistory();
             if (event.getRepeatCount() == 0) {
+                if (mShiftIsPressed) {
+                    return true; // discard press if copy in progress
+                }
                 mGotCenterDown = true;
                 mPrivateHandler.sendMessageDelayed(mPrivateHandler
                         .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
@@ -3307,10 +3394,7 @@
             }
         }
 
-        if (nativeCursorIsPlugin()) {
-            nativeUpdatePluginReceivesEvents();
-            invalidate();
-        } else if (nativeCursorIsTextInput()) {
+        if (nativeCursorIsTextInput()) {
             // This message will put the node in focus, for the DOM's notion
             // of focus, and make the focuscontroller active
             mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
@@ -3319,13 +3403,17 @@
             // our view system's notion of focus
             rebuildWebTextView();
             // Now we need to pass the event to it
-            return mWebTextView.onKeyDown(keyCode, event);
+            if (inEditingMode()) {
+                mWebTextView.setDefaultSelection();
+                mWebTextView.mOkayForFocusNotToMatch = true;
+                return mWebTextView.dispatchKeyEvent(event);
+            }
         } else if (nativeHasFocusNode()) {
             // In this case, the cursor is not on a text input, but the focus
             // might be.  Check it, and if so, hand over to the WebTextView.
             rebuildWebTextView();
             if (inEditingMode()) {
-                return mWebTextView.onKeyDown(keyCode, event);
+                return mWebTextView.dispatchKeyEvent(event);
             }
         }
 
@@ -3390,7 +3478,13 @@
             mGotCenterDown = false;
 
             if (mShiftIsPressed) {
-                return false;
+                if (mExtendSelection) {
+                    commitCopy();
+                } else {
+                    mExtendSelection = true;
+                    invalidate(); // draw the i-beam instead of the arrow
+                }
+                return true; // discard press if copy in progress
             }
 
             // perform the single click
@@ -3400,21 +3494,23 @@
             if (!nativeCursorIntersects(visibleRect)) {
                 return false;
             }
-            nativeSetFollowedLink(true);
-            nativeUpdatePluginReceivesEvents();
             WebViewCore.CursorData data = cursorData();
             mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
             playSoundEffect(SoundEffectConstants.CLICK);
-            boolean isTextInput = nativeCursorIsTextInput();
-            if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
-                        nativeCursorText())) {
+            if (nativeCursorIsTextInput()) {
+                rebuildWebTextView();
+                centerKeyPressOnTextField();
+                if (inEditingMode()) {
+                    mWebTextView.setDefaultSelection();
+                    mWebTextView.mOkayForFocusNotToMatch = true;
+                }
+                return true;
+            }
+            nativeSetFollowedLink(true);
+            if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
                 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
                         nativeCursorNodePointer());
             }
-            if (isTextInput) {
-                rebuildWebTextView();
-                displaySoftKeyboard(true);
-            }
             return true;
         }
 
@@ -3430,14 +3526,29 @@
         return false;
     }
 
+    private void setUpSelectXY() {
+        mExtendSelection = false;
+        mShiftIsPressed = true;
+        if (nativeHasCursorNode()) {
+            Rect rect = nativeCursorNodeBounds();
+            mSelectX = contentToViewX(rect.left);
+            mSelectY = contentToViewY(rect.top);
+        } else if (mLastTouchY > getVisibleTitleHeight()) {
+            mSelectX = mScrollX + (int) mLastTouchX;
+            mSelectY = mScrollY + (int) mLastTouchY;
+        } else {
+            mSelectX = mScrollX + getViewWidth() / 2;
+            mSelectY = mScrollY + getViewHeightWithTitle() / 2;
+        }
+        nativeHideCursor();
+    }
+
     /**
      * @hide
      */
     public void emulateShiftHeld() {
         if (0 == mNativeClass) return; // client isn't initialized
-        mExtendSelection = false;
-        mShiftIsPressed = true;
-        nativeHideCursor();
+        setUpSelectXY();
     }
 
     private boolean commitCopy() {
@@ -3455,6 +3566,7 @@
             mExtendSelection = false;
         }
         mShiftIsPressed = false;
+        invalidate(); // remove selection region and pointer
         if (mTouchMode == TOUCH_SELECT_MODE) {
             mTouchMode = TOUCH_INIT_MODE;
         }
@@ -3598,6 +3710,24 @@
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        boolean changed = super.setFrame(left, top, right, bottom);
+        if (!changed && mHeightCanMeasure) {
+            // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
+            // in WebViewCore after we get the first layout. We do call
+            // requestLayout() when we get contentSizeChanged(). But the View
+            // system won't call onSizeChanged if the dimension is not changed.
+            // In this case, we need to call sendViewSizeZoom() explicitly to
+            // notify the WebKit about the new dimensions.
+            sendViewSizeZoom();
+        }
+        return changed;
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int ow, int oh) {
         super.onSizeChanged(w, h, ow, oh);
@@ -3706,8 +3836,10 @@
             mLastSentTouchTime = eventTime;
         }
 
-        int deltaX = (int) (mLastTouchX - x);
-        int deltaY = (int) (mLastTouchY - y);
+        float fDeltaX = mLastTouchX - x;
+        float fDeltaY = mLastTouchY - y;
+        int deltaX = (int) fDeltaX;
+        int deltaY = (int) fDeltaY;
 
         switch (action) {
             case MotionEvent.ACTION_DOWN: {
@@ -3729,6 +3861,7 @@
                     nativeMoveSelection(viewToContentX(mSelectX),
                             viewToContentY(mSelectY), false);
                     mTouchSelection = mExtendSelection = true;
+                    invalidate(); // draw the i-beam instead of the arrow
                 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
                     mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
                     if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -3833,12 +3966,21 @@
 
                 // do pan
                 int newScrollX = pinLocX(mScrollX + deltaX);
-                deltaX = newScrollX - mScrollX;
+                int newDeltaX = newScrollX - mScrollX;
+                if (deltaX != newDeltaX) {
+                    deltaX = newDeltaX;
+                    fDeltaX = (float) newDeltaX;
+                }
                 int newScrollY = pinLocY(mScrollY + deltaY);
-                deltaY = newScrollY - mScrollY;
+                int newDeltaY = newScrollY - mScrollY;
+                if (deltaY != newDeltaY) {
+                    deltaY = newDeltaY;
+                    fDeltaY = (float) newDeltaY;
+                }
                 boolean done = false;
-                if (deltaX == 0 && deltaY == 0) {
-                    done = true;
+                boolean keepScrollBarsVisible = false;
+                if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
+                    keepScrollBarsVisible = done = true;
                 } else {
                     if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
                         int ax = Math.abs(deltaX);
@@ -3850,56 +3992,47 @@
                                 mSnapScrollMode = SNAP_NONE;
                             }
                             // reverse direction means lock in the snap mode
-                            if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
-                                    ((mSnapPositive &&
-                                    deltaX < -mMinLockSnapReverseDistance)
-                                    || (!mSnapPositive &&
-                                    deltaX > mMinLockSnapReverseDistance))) {
-                                mSnapScrollMode = SNAP_X_LOCK;
+                            if (ax > MAX_SLOPE_FOR_DIAG * ay &&
+                                    (mSnapPositive
+                                    ? deltaX < -mMinLockSnapReverseDistance
+                                    : deltaX > mMinLockSnapReverseDistance)) {
+                                mSnapScrollMode |= SNAP_LOCK;
                             }
                         } else {
                             // radical change means getting out of snap mode
-                            if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+                            if (ax > MAX_SLOPE_FOR_DIAG * ay
                                     && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
                                 mSnapScrollMode = SNAP_NONE;
                             }
                             // reverse direction means lock in the snap mode
-                            if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
-                                    ((mSnapPositive &&
-                                    deltaY < -mMinLockSnapReverseDistance)
-                                    || (!mSnapPositive &&
-                                    deltaY > mMinLockSnapReverseDistance))) {
-                                mSnapScrollMode = SNAP_Y_LOCK;
+                            if (ay > MAX_SLOPE_FOR_DIAG * ax &&
+                                    (mSnapPositive
+                                    ? deltaY < -mMinLockSnapReverseDistance
+                                    : deltaY > mMinLockSnapReverseDistance)) {
+                                mSnapScrollMode |= SNAP_LOCK;
                             }
                         }
                     }
-
-                    if (mSnapScrollMode == SNAP_X
-                            || mSnapScrollMode == SNAP_X_LOCK) {
-                        if (deltaX == 0) {
-                            // keep the scrollbar on the screen even there is no
-                            // scroll
-                            awakenScrollBars(ViewConfiguration
-                                    .getScrollDefaultDelay(), false);
+                    if (mSnapScrollMode != SNAP_NONE) {
+                        if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
+                            deltaY = 0;
                         } else {
-                            scrollBy(deltaX, 0);
+                            deltaX = 0;
                         }
-                        mLastTouchX = x;
-                    } else if (mSnapScrollMode == SNAP_Y
-                            || mSnapScrollMode == SNAP_Y_LOCK) {
-                        if (deltaY == 0) {
-                            // keep the scrollbar on the screen even there is no
-                            // scroll
-                            awakenScrollBars(ViewConfiguration
-                                    .getScrollDefaultDelay(), false);
-                        } else {
-                            scrollBy(0, deltaY);
-                        }
-                        mLastTouchY = y;
-                    } else {
+                    }
+                    if ((deltaX | deltaY) != 0) {
                         scrollBy(deltaX, deltaY);
-                        mLastTouchX = x;
-                        mLastTouchY = y;
+                        if (deltaX != 0) {
+                            mLastTouchX = x;
+                        }
+                        if (deltaY != 0) {
+                            mLastTouchY = y;
+                        }
+                        mHeldMotionless = MOTIONLESS_FALSE;
+                    } else {
+                        // keep the scrollbar on the screen even there is no
+                        // scroll
+                        keepScrollBarsVisible = true;
                     }
                     mLastTouchTime = eventTime;
                     mUserScroll = true;
@@ -3918,13 +4051,17 @@
                     }
                 }
 
-                if (done) {
+                if (keepScrollBarsVisible) {
+                    if (mHeldMotionless != MOTIONLESS_TRUE) {
+                        mHeldMotionless = MOTIONLESS_TRUE;
+                        invalidate();
+                    }
                     // keep the scrollbar on the screen even there is no scroll
                     awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
                             false);
                     // return false to indicate that we can't pan out of the
                     // view space
-                    return false;
+                    return !done;
                 }
                 break;
             }
@@ -3977,6 +4114,9 @@
                             break;
                         }
                     case TOUCH_DRAG_MODE:
+                        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+                        mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+                        mHeldMotionless = MOTIONLESS_TRUE;
                         // redraw in high-quality, as we're done dragging
                         invalidate();
                         // if the user waits a while w/o moving before the
@@ -4016,6 +4156,9 @@
                 }
                 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
                 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+                mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+                mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+                mHeldMotionless = MOTIONLESS_TRUE;
                 mTouchMode = TOUCH_DONE_MODE;
                 nativeHideCursor();
                 break;
@@ -4042,6 +4185,7 @@
     private static final int SELECT_CURSOR_OFFSET = 16;
     private int mSelectX = 0;
     private int mSelectY = 0;
+    private boolean mFocusSizeChanged = false;
     private boolean mShiftIsPressed = false;
     private boolean mTrackballDown = false;
     private long mTrackballUpTime = 0;
@@ -4100,6 +4244,7 @@
                     commitCopy();
                 } else {
                     mExtendSelection = true;
+                    invalidate(); // draw the i-beam instead of the arrow
                 }
                 return true; // discard press if copy in progress
             }
@@ -4147,8 +4292,8 @@
             return;
         int width = getViewWidth();
         int height = getViewHeight();
-        mSelectX += scaleTrackballX(xRate, width);
-        mSelectY += scaleTrackballY(yRate, height);
+        mSelectX += xRate;
+        mSelectY += yRate;
         int maxX = width + mScrollX;
         int maxY = height + mScrollY;
         mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
@@ -4230,8 +4375,11 @@
         }
         float xRate = mTrackballRemainsX * 1000 / elapsed;
         float yRate = mTrackballRemainsY * 1000 / elapsed;
+        int viewWidth = getViewWidth();
+        int viewHeight = getViewHeight();
         if (mShiftIsPressed) {
-            moveSelection(xRate, yRate);
+            moveSelection(scaleTrackballX(xRate, viewWidth),
+                    scaleTrackballY(yRate, viewHeight));
             mTrackballRemainsX = mTrackballRemainsY = 0;
             return;
         }
@@ -4245,8 +4393,8 @@
                     + " mTrackballRemainsX=" + mTrackballRemainsX
                     + " mTrackballRemainsY=" + mTrackballRemainsY);
         }
-        int width = mContentWidth - getViewWidth();
-        int height = mContentHeight - getViewHeight();
+        int width = mContentWidth - viewWidth;
+        int height = mContentHeight - viewHeight;
         if (width < 0) width = 0;
         if (height < 0) height = 0;
         ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
@@ -4321,7 +4469,7 @@
         int vy = (int) mVelocityTracker.getYVelocity();
 
         if (mSnapScrollMode != SNAP_NONE) {
-            if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+            if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
                 vy = 0;
             } else {
                 vx = 0;
@@ -4601,21 +4749,21 @@
         }
         int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
         int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
-        // In case the soft keyboard has been dismissed, bring it back up.
-        InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
-                0);
         if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
             nativeMotionUp(x, y, mNavSlop);
         }
         nativeTextInputMotionUp(x, y);
     }
 
-    /*package*/ void shortPressOnTextField() {
-        if (inEditingMode()) {
-            View v = mWebTextView;
-            int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
-            int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
-            nativeTextInputMotionUp(x, y);
+    /**
+     * Called when pressing the center key or trackball on a textfield.
+     */
+    /*package*/ void centerKeyPressOnTextField() {
+        mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
+                    nativeCursorNodePointer());
+        // Need to show the soft keyboard if it's not readonly.
+        if (!nativeCursorIsReadOnly()) {
+            displaySoftKeyboard(true);
         }
     }
 
@@ -4685,15 +4833,6 @@
         mCallbackProxy.uiOverrideUrlLoading(url);
     }
 
-    // called by JNI
-    private void sendPluginState(int state) {
-        WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
-        psd.mFrame = nativeCursorFramePointer();
-        psd.mNode = nativeCursorNodePointer();
-        psd.mState = state;
-        mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
-    }
-
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
         boolean result = false;
@@ -4842,14 +4981,6 @@
     }
 
     /* package */ void passToJavaScript(String currentText, KeyEvent event) {
-        if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) {
-            mWebViewCore.sendMessage(EventHub.CLICK);
-            if (mWebTextView.mOkayForFocusNotToMatch) {
-                int select = nativeFocusCandidateIsTextField() ?
-                        nativeFocusCandidateMaxLength() : 0;
-                setSelection(select, select);
-            }
-        }
         WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
         arg.mEvent = event;
         arg.mCurrentText = currentText;
@@ -4880,9 +5011,10 @@
     class PrivateHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
-            if (DebugFlags.WEB_VIEW) {
+            // exclude INVAL_RECT_MSG_ID since it is frequently output
+            if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
                 Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
-                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
+                        > REQUEST_KEYBOARD ? Integer.toString(msg.what)
                         : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
             }
             if (mWebViewCore == null) {
@@ -5044,6 +5176,9 @@
                                     / mZoomOverviewWidth, false);
                         }
                     }
+                    if (draw.mFocusSizeChanged && inEditingMode()) {
+                        mFocusSizeChanged = true;
+                    }
                     break;
                 }
                 case WEBCORE_INITIALIZED_MSG_ID:
@@ -5084,9 +5219,7 @@
                     }
                     break;
                 case MOVE_OUT_OF_PLUGIN:
-                    if (nativePluginEatsNavKey()) {
-                        navHandledKey(msg.arg1, 1, false, 0, true);
-                    }
+                    navHandledKey(msg.arg1, 1, false, 0, true);
                     break;
                 case UPDATE_TEXT_ENTRY_MSG_ID:
                     // this is sent after finishing resize in WebViewCore. Make
@@ -5170,9 +5303,36 @@
                         hideSoftKeyboard();
                     } else {
                         displaySoftKeyboard(false);
+                        if (DebugFlags.WEB_VIEW) {
+                            Log.v(LOGTAG, "REQUEST_KEYBOARD"
+                                    + " focusCandidateIsPlugin="
+                                    + nativeFocusCandidateIsPlugin());
+                        }
                     }
                     break;
 
+                case FIND_AGAIN:
+                    // Ignore if find has been dismissed.
+                    if (mFindIsUp) {
+                        findAll(mLastFind);
+                    }
+                    break;
+
+                case DRAG_HELD_MOTIONLESS:
+                    mHeldMotionless = MOTIONLESS_TRUE;
+                    invalidate();
+                    // fall through to keep scrollbars awake
+
+                case AWAKEN_SCROLL_BARS:
+                    if (mTouchMode == TOUCH_DRAG_MODE
+                            && mHeldMotionless == MOTIONLESS_TRUE) {
+                        awakenScrollBars(ViewConfiguration
+                                .getScrollDefaultDelay(), false);
+                        mPrivateHandler.sendMessageDelayed(mPrivateHandler
+                                .obtainMessage(AWAKEN_SCROLL_BARS),
+                                ViewConfiguration.getScrollDefaultDelay());
+                    }
+                    break;
                 default:
                     super.handleMessage(msg);
                     break;
@@ -5196,8 +5356,16 @@
         // Need these to provide stable ids to my ArrayAdapter,
         // which normally does not have stable ids. (Bug 1250098)
         private class Container extends Object {
+            /**
+             * Possible values for mEnabled.  Keep in sync with OptionStatus in
+             * WebViewCore.cpp
+             */
+            final static int OPTGROUP = -1;
+            final static int OPTION_DISABLED = 0;
+            final static int OPTION_ENABLED = 1;
+
             String  mString;
-            boolean mEnabled;
+            int     mEnabled;
             int     mId;
 
             public String toString() {
@@ -5218,6 +5386,54 @@
             }
 
             @Override
+            public View getView(int position, View convertView,
+                    ViewGroup parent) {
+                // Always pass in null so that we will get a new CheckedTextView
+                // Otherwise, an item which was previously used as an <optgroup>
+                // element (i.e. has no check), could get used as an <option>
+                // element, which needs a checkbox/radio, but it would not have
+                // one.
+                convertView = super.getView(position, null, parent);
+                Container c = item(position);
+                if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
+                    // ListView does not draw dividers between disabled and
+                    // enabled elements.  Use a LinearLayout to provide dividers
+                    LinearLayout layout = new LinearLayout(mContext);
+                    layout.setOrientation(LinearLayout.VERTICAL);
+                    if (position > 0) {
+                        View dividerTop = new View(mContext);
+                        dividerTop.setBackgroundResource(
+                                android.R.drawable.divider_horizontal_bright);
+                        layout.addView(dividerTop);
+                    }
+
+                    if (Container.OPTGROUP == c.mEnabled) {
+                        // Currently select_dialog_multichoice and
+                        // select_dialog_singlechoice are CheckedTextViews.  If
+                        // that changes, the class cast will no longer be valid.
+                        Assert.assertTrue(
+                                convertView instanceof CheckedTextView);
+                        ((CheckedTextView) convertView).setCheckMarkDrawable(
+                                null);
+                    } else {
+                        // c.mEnabled == Container.OPTION_DISABLED
+                        // Draw the disabled element in a disabled state.
+                        convertView.setEnabled(false);
+                    }
+
+                    layout.addView(convertView);
+                    if (position < getCount() - 1) {
+                        View dividerBottom = new View(mContext);
+                        dividerBottom.setBackgroundResource(
+                                android.R.drawable.divider_horizontal_bright);
+                        layout.addView(dividerBottom);
+                    }
+                    return layout;
+                }
+                return convertView;
+            }
+
+            @Override
             public boolean hasStableIds() {
                 // AdapterView's onChanged method uses this to determine whether
                 // to restore the old state.  Return false so that the old (out
@@ -5252,12 +5468,11 @@
                 if (item == null) {
                     return false;
                 }
-                return item.mEnabled;
+                return Container.OPTION_ENABLED == item.mEnabled;
             }
         }
 
-        private InvokeListBox(String[] array,
-                boolean[] enabled, int[] selected) {
+        private InvokeListBox(String[] array, int[] enabled, int[] selected) {
             mMultiple = true;
             mSelectedArray = selected;
 
@@ -5271,8 +5486,7 @@
             }
         }
 
-        private InvokeListBox(String[] array, boolean[] enabled, int
-                selection) {
+        private InvokeListBox(String[] array, int[] enabled, int selection) {
             mSelection = selection;
             mMultiple = false;
 
@@ -5403,10 +5617,11 @@
      * Request a dropdown menu for a listbox with multiple selection.
      *
      * @param array Labels for the listbox.
-     * @param enabledArray  Which positions are enabled.
+     * @param enabledArray  State for each element in the list.  See static
+     *      integers in Container class.
      * @param selectedArray Which positions are initally selected.
      */
-    void requestListBox(String[] array, boolean[]enabledArray, int[]
+    void requestListBox(String[] array, int[] enabledArray, int[]
             selectedArray) {
         mPrivateHandler.post(
                 new InvokeListBox(array, enabledArray, selectedArray));
@@ -5417,10 +5632,11 @@
      * <select> element.
      *
      * @param array Labels for the listbox.
-     * @param enabledArray  Which positions are enabled.
+     * @param enabledArray  State for each element in the list.  See static
+     *      integers in Container class.
      * @param selection Which position is initally selected.
      */
-    void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+    void requestListBox(String[] array, int[] enabledArray, int selection) {
         mPrivateHandler.post(
                 new InvokeListBox(array, enabledArray, selection));
     }
@@ -5504,7 +5720,7 @@
         if (mNativeClass == 0) {
             return false;
         }
-        if (ignorePlugin == false && nativePluginEatsNavKey()) {
+        if (ignorePlugin == false && nativeFocusIsPlugin()) {
             KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN
                 , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
                 | (false ? KeyEvent.META_ALT_ON : 0) // FIXME
@@ -5579,6 +5795,17 @@
     }
 
     /**
+     * Draw the HTML page into the specified canvas. This call ignores any
+     * view-specific zoom, scroll offset, or other changes. It does not draw
+     * any view-specific chrome, such as progress or URL bars.
+     *
+     * @hide only needs to be accessible to Browser and testing
+     */
+    public void drawPage(Canvas canvas) {
+        mWebViewCore.drawContentPicture(canvas, 0, false, false);
+    }
+
+    /**
      *  Update our cache with updatedText.
      *  @param updatedText  The new text to put in our cache.
      */
@@ -5596,7 +5823,7 @@
     /* package */ native boolean nativeCursorMatchesFocus();
     private native boolean  nativeCursorIntersects(Rect visibleRect);
     private native boolean  nativeCursorIsAnchor();
-    private native boolean  nativeCursorIsPlugin();
+    private native boolean  nativeCursorIsReadOnly();
     private native boolean  nativeCursorIsTextInput();
     private native Point    nativeCursorPosition();
     private native String   nativeCursorText();
@@ -5609,13 +5836,15 @@
     private native void     nativeDestroy();
     private native void     nativeDrawCursorRing(Canvas content);
     private native void     nativeDrawMatches(Canvas canvas);
-    private native void     nativeDrawSelection(Canvas content, float scale,
-            int offset, int x, int y, boolean extendSelection);
+    private native void     nativeDrawSelectionPointer(Canvas content,
+            float scale, int x, int y, boolean extendSelection);
     private native void     nativeDrawSelectionRegion(Canvas content);
     private native void     nativeDumpDisplayTree(String urlOrNull);
     private native int      nativeFindAll(String findLower, String findUpper);
     private native void     nativeFindNext(boolean forward);
+    private native int      nativeFocusCandidateFramePointer();
     private native boolean  nativeFocusCandidateIsPassword();
+    private native boolean  nativeFocusCandidateIsPlugin();
     private native boolean  nativeFocusCandidateIsRtlText();
     private native boolean  nativeFocusCandidateIsTextField();
     private native boolean  nativeFocusCandidateIsTextInput();
@@ -5625,6 +5854,7 @@
     /* package */ native int nativeFocusCandidatePointer();
     private native String   nativeFocusCandidateText();
     private native int      nativeFocusCandidateTextSize();
+    private native boolean  nativeFocusIsPlugin();
     /* package */ native int nativeFocusNodePointer();
     private native Rect     nativeGetCursorRingBounds();
     private native Region   nativeGetSelection();
@@ -5642,7 +5872,6 @@
     private native int      nativeMoveGeneration();
     private native void     nativeMoveSelection(int x, int y,
             boolean extendSelection);
-    private native boolean  nativePluginEatsNavKey();
     // Like many other of our native methods, you must make sure that
     // mNativeClass is not null before calling this method.
     private native void     nativeRecordButtons(boolean focused,
@@ -5664,7 +5893,6 @@
     // we always want to pass in our generation number.
     private native void     nativeUpdateCachedTextfield(String updatedText,
             int generation);
-    private native void     nativeUpdatePluginReceivesEvents();
     // return NO_LEFTEDGE means failure.
     private static final int NO_LEFTEDGE = -1;
     private native int      nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index a5a4852..bfaa353 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.graphics.Canvas;
 import android.graphics.DrawFilter;
 import android.graphics.Paint;
@@ -26,11 +27,13 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.provider.Browser;
+import android.provider.OpenableColumns;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
@@ -273,6 +276,39 @@
         mCallbackProxy.onJsAlert(url, message);
     }
 
+
+    /**
+     * Called by JNI.  Open a file chooser to upload a file.
+     * @return String version of the URI plus the name of the file.
+     * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+     * into Java to get the filename.
+     */
+    private String openFileChooser() {
+        Uri uri = mCallbackProxy.openFileChooser();
+        if (uri == null) return "";
+        // Find out the name, and append it to the URI.
+        // Webkit will treat the name as the filename, and
+        // the URI as the path.  The URI will be used
+        // in BrowserFrame to get the actual data.
+        Cursor cursor = mContext.getContentResolver().query(
+                uri,
+                new String[] { OpenableColumns.DISPLAY_NAME },
+                null,
+                null,
+                null);
+        String name = "";
+        if (cursor != null) {
+            try {
+                if (cursor.moveToNext()) {
+                    name = cursor.getString(0);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+        return uri.toString() + "/" + name;
+    }
+
     /**
      * Notify the browser that the origin has exceeded it's database quota.
      * @param url The URL that caused the overflow.
@@ -422,6 +458,8 @@
      */
     private native boolean nativeRecordContent(Region invalRegion, Point wh);
 
+    private native boolean nativeFocusBoundsChanged();
+
     /**
      * Splits slow parts of the picture set. Called from the webkit
      * thread after nativeDrawContent returns true.
@@ -522,8 +560,6 @@
      */
     private native void nativeSetNewStorageLimit(long limit);
 
-    private native void nativeUpdatePluginState(int framePtr, int nodePtr, int state);
-
     /**
      * Provide WebCore with a Geolocation permission state for the specified
      * origin.
@@ -678,12 +714,6 @@
         int mY;
     }
 
-    static class PluginStateData {
-        int mFrame;
-        int mNode;
-        int mState;
-    }
-
     static class GeolocationPermissionsData {
         String mOrigin;
         boolean mAllow;
@@ -720,7 +750,7 @@
             "SINGLE_LISTBOX_CHOICE", // = 124;
             "MESSAGE_RELAY", // = 125;
             "SET_BACKGROUND_COLOR", // = 126;
-            "PLUGIN_STATE", // = 127;
+            "127", // = 127
             "SAVE_DOCUMENT_STATE", // = 128;
             "GET_SELECTION", // = 129;
             "WEBKIT_DRAW", // = 130;
@@ -771,7 +801,6 @@
         static final int SINGLE_LISTBOX_CHOICE = 124;
         static final int MESSAGE_RELAY = 125;
         static final int SET_BACKGROUND_COLOR = 126;
-        static final int PLUGIN_STATE = 127; // plugin notifications
         static final int SAVE_DOCUMENT_STATE = 128;
         static final int GET_SELECTION = 129;
         static final int WEBKIT_DRAW = 130;
@@ -1031,11 +1060,6 @@
                             nativeFreeMemory();
                             break;
 
-                        case PLUGIN_STATE:
-                            PluginStateData psd = (PluginStateData) msg.obj;
-                            nativeUpdatePluginState(psd.mFrame, psd.mNode, psd.mState);
-                            break;
-
                         case SET_NETWORK_STATE:
                             if (BrowserFrame.sJavaBridge == null) {
                                 throw new IllegalStateException("No WebView " +
@@ -1593,6 +1617,7 @@
         int mMinPrefWidth;
         RestoreState mRestoreState; // only non-null if it is for the first
                                     // picture set after the first layout
+        boolean mFocusSizeChanged;
     }
 
     private void webkitDraw() {
@@ -1607,6 +1632,7 @@
         if (mWebView != null) {
             // Send the native view size that was used during the most recent
             // layout.
+            draw.mFocusSizeChanged = nativeFocusBoundsChanged();
             draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
             if (mSettings.getUseWideViewPort()) {
                 draw.mMinPrefWidth = Math.max(
@@ -1643,6 +1669,9 @@
 
     final DrawFilter mZoomFilter =
                     new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+    final DrawFilter mScrollFilter = null;
+    // If we need to trade more speed for less quality on slower devices
+    // use this: new PaintFlagsDrawFilter(SCROLL_BITS, 0);
 
     /* package */ void drawContentPicture(Canvas canvas, int color,
                                           boolean animatingZoom,
@@ -1651,7 +1680,7 @@
         if (animatingZoom) {
             df = mZoomFilter;
         } else if (animatingScroll) {
-            df = null;
+            df = mScrollFilter;
         }
         canvas.setDrawFilter(df);
         boolean tookTooLong = nativeDrawContent(canvas, color);
@@ -2013,9 +2042,16 @@
                 // know the exact scale. If mRestoredScale is non-zero, use it;
                 // otherwise just use mTextWrapScale as the initial scale.
                 data.mScale = mRestoreState.mViewScale == 0
-                        ? (mRestoredScale > 0 ? mRestoredScale
+                        ? (mRestoredScale > 0 ? mRestoredScale / 100.0f
                                 : mRestoreState.mTextWrapScale)
                         : mRestoreState.mViewScale;
+                if (DebugFlags.WEB_VIEW_CORE) {
+                    Log.v(LOGTAG, "setupViewport"
+                             + " mRestoredScale=" + mRestoredScale
+                             + " mViewScale=" + mRestoreState.mViewScale
+                             + " mTextWrapScale=" + mRestoreState.mTextWrapScale
+                             );
+                }
                 data.mWidth = Math.round(webViewWidth / data.mScale);
                 data.mHeight = mCurrentViewHeight * data.mWidth / viewportWidth;
                 data.mTextWrapWidth = Math.round(webViewWidth
@@ -2086,6 +2122,13 @@
                 WebView.CLEAR_TEXT_ENTRY).sendToTarget();
     }
 
+    // called by JNI
+    private void sendFindAgain() {
+        if (mWebView == null) return;
+        Message.obtain(mWebView.mPrivateHandler,
+                WebView.FIND_AGAIN).sendToTarget();
+    }
+
     private native void nativeUpdateFrameCacheIfLoading();
 
     /**
@@ -2099,7 +2142,7 @@
     private native void nativeSetGlobalBounds(int x, int y, int w, int h);
 
     // called by JNI
-    private void requestListBox(String[] array, boolean[] enabledArray,
+    private void requestListBox(String[] array, int[] enabledArray,
             int[] selectedArray) {
         if (mWebView != null) {
             mWebView.requestListBox(array, enabledArray, selectedArray);
@@ -2107,7 +2150,7 @@
     }
 
     // called by JNI
-    private void requestListBox(String[] array, boolean[] enabledArray,
+    private void requestListBox(String[] array, int[] enabledArray,
             int selection) {
         if (mWebView != null) {
             mWebView.requestListBox(array, enabledArray, selection);
@@ -2173,6 +2216,11 @@
         return view;
     }
     
+    private void updateSurface(ViewManager.ChildView childView, int x, int y,
+            int width, int height) {
+        childView.attachView(x, y, width, height);
+    }
+
     private void destroySurface(ViewManager.ChildView childView) {
         childView.removeView();
     }
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 6e10811..110e4f8 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -27,6 +27,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteStatement;
 import android.util.Log;
 import android.webkit.CookieManager.Cookie;
@@ -174,7 +175,16 @@
     public static synchronized WebViewDatabase getInstance(Context context) {
         if (mInstance == null) {
             mInstance = new WebViewDatabase();
-            mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null);
+            try {
+                mDatabase = context
+                        .openOrCreateDatabase(DATABASE_FILE, 0, null);
+            } catch (SQLiteException e) {
+                // try again by deleting the old db and create a new one
+                if (context.deleteDatabase(DATABASE_FILE)) {
+                    mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0,
+                            null);
+                }
+            }
 
             // mDatabase should not be null, 
             // the only case is RequestAPI test has problem to create db 
@@ -194,8 +204,16 @@
                 mDatabase.setLockingEnabled(false);
             }
 
-            mCacheDatabase = context.openOrCreateDatabase(CACHE_DATABASE_FILE,
-                    0, null);
+            try {
+                mCacheDatabase = context.openOrCreateDatabase(
+                        CACHE_DATABASE_FILE, 0, null);
+            } catch (SQLiteException e) {
+                // try again by deleting the old db and create a new one
+                if (context.deleteDatabase(CACHE_DATABASE_FILE)) {
+                    mCacheDatabase = context.openOrCreateDatabase(
+                            CACHE_DATABASE_FILE, 0, null);
+                }
+            }
 
             // mCacheDatabase should not be null, 
             // the only case is RequestAPI test has problem to create db 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 165794a..5991ad4 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3560,6 +3560,7 @@
             // into the scrap heap
             int viewType = lp.viewType;
             if (!shouldRecycleViewType(viewType)) {
+                removeDetachedView(scrap, false);
                 return;
             }
 
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index fd590ed..aa9062b 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -117,11 +117,11 @@
      * @param d The Drawable to use for the checkmark.
      */
     public void setCheckMarkDrawable(Drawable d) {
+        if (mCheckMarkDrawable != null) {
+            mCheckMarkDrawable.setCallback(null);
+            unscheduleDrawable(mCheckMarkDrawable);
+        }
         if (d != null) {
-            if (mCheckMarkDrawable != null) {
-                mCheckMarkDrawable.setCallback(null);
-                unscheduleDrawable(mCheckMarkDrawable);
-            }
             d.setCallback(this);
             d.setVisible(getVisibility() == VISIBLE, false);
             d.setState(CHECKED_STATE_SET);
@@ -130,10 +130,10 @@
             mCheckMarkWidth = d.getIntrinsicWidth();
             mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
             d.setState(getDrawableState());
-            mCheckMarkDrawable = d;
         } else {
             mPaddingRight = mBasePaddingRight;
         }
+        mCheckMarkDrawable = d;
         requestLayout();
     }
     
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8019f14..07c3e4b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -25,9 +25,9 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.util.AttributeSet;
@@ -55,21 +55,28 @@
     static final private int TOKEN_PHONE_LOOKUP = 1;
     static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
     static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+    static final private int TOKEN_CONTACT_LOOKUP_AND_TRIGGER = 4;
 
     static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
         RawContacts.CONTACT_ID,
         Contacts.LOOKUP_KEY,
     };
-    static int EMAIL_ID_COLUMN_INDEX = 0;
-    static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
+    static final int EMAIL_ID_COLUMN_INDEX = 0;
+    static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
 
     static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
         PhoneLookup._ID,
         PhoneLookup.LOOKUP_KEY,
     };
-    static int PHONE_ID_COLUMN_INDEX = 0;
-    static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+    static final int PHONE_ID_COLUMN_INDEX = 0;
+    static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
 
+    static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+        Contacts._ID,
+        Contacts.LOOKUP_KEY,
+    };
+    static final int CONTACT_ID_COLUMN_INDEX = 0;
+    static final int CONTACT_LOOKUPKEY_COLUMN_INDEX = 1;
 
 
     public QuickContactBadge(Context context) {
@@ -181,9 +188,9 @@
 
     public void onClick(View v) {
         if (mContactUri != null) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri);
-            trigger(lookupUri);
+            mQueryHandler.startQuery(TOKEN_CONTACT_LOOKUP_AND_TRIGGER, null,
+                    mContactUri,
+                    CONTACT_LOOKUP_PROJECTION, null, null, null);
         } else if (mContactEmail != null) {
             mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
                     Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -249,6 +256,17 @@
                             lookupUri = Contacts.getLookupUri(contactId, lookupKey);
                         }
                     }
+
+                    case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
+                        if (cursor != null && cursor.moveToFirst()) {
+                            long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+                            String lookupKey = cursor.getString(CONTACT_LOOKUPKEY_COLUMN_INDEX);
+                            lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+                            trigger = true;
+                        }
+
+                        break;
+                    }
                 }
             } finally {
                 if (cursor != null) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 596fd98..f55ca3f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5197,7 +5197,7 @@
             mDesiredHeightAtMeasure = desired;
 
             if (heightMode == MeasureSpec.AT_MOST) {
-                height = Math.min(desired, height);
+                height = Math.min(desired, heightSize);
             }
         }
 
diff --git a/core/java/com/android/internal/os/IDropBoxService.aidl b/core/java/com/android/internal/os/IDropBoxService.aidl
new file mode 100644
index 0000000..f940041
--- /dev/null
+++ b/core/java/com/android/internal/os/IDropBoxService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.os;
+
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * "Backend" interface used by {@link android.os.DropBox} to talk to the
+ * DropBoxService that actually implements the drop box functionality.
+ *
+ * @see DropBox
+ * @hide
+ */
+interface IDropBoxService {
+    /**
+     * @see DropBox#addText
+     * @see DropBox#addData
+     * @see DropBox#addFile
+     */
+    void add(in DropBox.Entry entry);
+
+    /** @see DropBox#getNextEntry */
+    boolean isTagEnabled(String tag);
+
+    /** @see DropBox#getNextEntry */
+    DropBox.Entry getNextEntry(String tag, long millis);
+}
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index dc72008..e1e9536 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -27,6 +27,9 @@
 #include "SkShader.h"
 #include "SkTemplates.h"
 
+#include "SkBoundaryPatch.h"
+#include "SkMeshUtils.h"
+
 #define TIME_DRAWx
 
 static uint32_t get_thread_msec() {
@@ -861,8 +864,6 @@
         *matrix = canvas->getTotalMatrix();
     }
 };
-    
-///////////////////////////////////////////////////////////////////////////////
 
 static JNINativeMethod gCanvasMethods[] = {
     {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
@@ -965,6 +966,42 @@
     {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}
 };
 
+///////////////////////////////////////////////////////////////////////////////
+
+static void BoundaryPatch_computeCubic(JNIEnv* env, jobject, jfloatArray jpts,
+                                   int texW, int texH, int rows, int cols,
+                                   jfloatArray jverts, jshortArray jidx) {
+    AutoJavaFloatArray ptsArray(env, jpts, 24, kRO_JNIAccess);
+
+    int vertCount = rows * cols;
+    AutoJavaFloatArray vertsArray(env, jverts, vertCount * 4, kRW_JNIAccess);
+    SkPoint* verts = (SkPoint*)vertsArray.ptr();
+    SkPoint* texs = verts + vertCount;
+
+    int idxCount = (rows - 1) * (cols - 1) * 6;
+    AutoJavaShortArray idxArray(env, jidx, idxCount, kRW_JNIAccess);
+    uint16_t* idx = (uint16_t*)idxArray.ptr();  // cast from int16_t*
+
+    SkCubicBoundary cubic;
+    memcpy(cubic.fPts, ptsArray.ptr(), 12 * sizeof(SkPoint));
+
+    SkBoundaryPatch patch;
+    patch.setBoundary(&cubic);
+    // generate our verts
+    patch.evalPatch(verts, rows, cols);
+
+    SkMeshIndices mesh;
+    // generate our texs and idx
+    mesh.init(texs, idx, texW, texH, rows, cols);
+}
+
+static JNINativeMethod gBoundaryPatchMethods[] = {
+    {"nativeComputeCubicPatch", "([FIIII[F[S)V",
+    (void*)BoundaryPatch_computeCubic },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
 #include <android_runtime/AndroidRuntime.h>
 
 #define REG(env, name, array) \
@@ -976,6 +1013,7 @@
     int result;
 
     REG(env, "android/graphics/Canvas", gCanvasMethods);
+    REG(env, "android/graphics/utils/BoundaryPatch", gBoundaryPatchMethods);
     
     return result;
 }
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 2e0caed..01aad93 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -56,7 +56,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
-                                       int minLength)
+                                       int minLength, JNIAccess access)
 : fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
     SkASSERT(env);
     if (array) {
@@ -66,11 +66,12 @@
         }
         fPtr = env->GetFloatArrayElements(array, NULL);
     }
+    fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
 }
 
 AutoJavaFloatArray::~AutoJavaFloatArray() {
     if (fPtr) {
-        fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
+        fEnv->ReleaseFloatArrayElements(fArray, fPtr, fReleaseMode);
     }
 }
 
@@ -94,7 +95,7 @@
 }
 
 AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
-                                       int minLength)
+                                       int minLength, JNIAccess access)
 : fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
     SkASSERT(env);
     if (array) {
@@ -104,11 +105,12 @@
         }
         fPtr = env->GetShortArrayElements(array, NULL);
     }
+    fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
 }
 
 AutoJavaShortArray::~AutoJavaShortArray() {
     if (fPtr) {
-        fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
+        fEnv->ReleaseShortArrayElements(fArray, fPtr, fReleaseMode);
     }
 }
 
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 7adadbc..fe24b05 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -80,9 +80,15 @@
     bool fReportSizeToVM;
 };
 
+enum JNIAccess {
+    kRO_JNIAccess,
+    kRW_JNIAccess
+};
+
 class AutoJavaFloatArray {
 public:
-    AutoJavaFloatArray(JNIEnv* env, jfloatArray array, int minLength = 0);
+    AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
+                       int minLength = 0, JNIAccess = kRW_JNIAccess);
     ~AutoJavaFloatArray();
     
     float* ptr() const { return fPtr; }
@@ -93,6 +99,7 @@
     jfloatArray fArray;
     float*      fPtr;
     int         fLen;
+    int         fReleaseMode;
 };
 
 class AutoJavaIntArray {
@@ -112,7 +119,8 @@
 
 class AutoJavaShortArray {
 public:
-    AutoJavaShortArray(JNIEnv* env, jshortArray array, int minLength = 0);
+    AutoJavaShortArray(JNIEnv* env, jshortArray array,
+                       int minLength = 0, JNIAccess = kRW_JNIAccess);
     ~AutoJavaShortArray();
     
     jshort* ptr() const { return fPtr; }
@@ -123,6 +131,7 @@
     jshortArray fArray;
     jshort*      fPtr;
     int         fLen;
+    int         fReleaseMode;
 };
 
 class AutoJavaByteArray {
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 38f3fda..46000c9 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -20,6 +20,7 @@
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
+#include <utils/String16.h>
 
 #include "wifi.h"
 
@@ -92,7 +93,8 @@
     if (doCommand(cmd, reply, sizeof(reply)) != 0) {
         return env->NewStringUTF(NULL);
     } else {
-        return env->NewStringUTF(reply);
+        String16 str((char *)reply);
+        return env->NewString((const jchar *)str.string(), str.size());
     }
 }
 
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
index 42fc0c5..fe220e4 100644
--- a/core/res/assets/images/combobox-disabled.png
+++ b/core/res/assets/images/combobox-disabled.png
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
index 838dc65..abcdf72 100644
--- a/core/res/assets/images/combobox-noHighlight.png
+++ b/core/res/assets/images/combobox-noHighlight.png
Binary files differ
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index 2aaaa15..45d9c5e 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -30,14 +30,8 @@
   </head>
   <body>
     <div id="bg">
-      <table height="100%" width="100%" border="0" cellpadding="0"
-        cellspacing="0">
-        <tr>
-          <td valign="middle">
-            <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg" width="100%"/>
-          </td>
-        </tr>
-      </table>
+      <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg"
+      style="width:100%; height:100%"/>
     </div>
     <div id="main">
       <table height="100%" width="100%">
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index cf2de05..40e3f42 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -50,11 +50,11 @@
                 android:paddingTop="6dip"
                 android:paddingRight="10dip"
                 android:src="@drawable/ic_dialog_info" />
-            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" 
+            <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
                 style="?android:attr/textAppearanceLarge"
                 android:singleLine="true"
                 android:ellipsize="end"
-                android:layout_width="fill_parent" 
+                android:layout_width="fill_parent"
                 android:layout_height="wrap_content" />
         </LinearLayout>
         <ImageView android:id="@+id/titleDivider"
@@ -63,9 +63,7 @@
             android:visibility="gone"
             android:scaleType="fitXY"
             android:gravity="fill_horizontal"
-            android:src="@android:drawable/dialog_divider_horizontal_light"
-            android:layout_marginLeft="10dip"
-            android:layout_marginRight="10dip"/>
+            android:src="@android:drawable/divider_horizontal_dark" />
         <!-- If the client uses a customTitle, it will be added here. -->
     </LinearLayout>
 
@@ -88,7 +86,7 @@
                 android:padding="5dip" />
         </ScrollView>
     </LinearLayout>
-        
+
     <FrameLayout android:id="@+id/customPanel"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
@@ -99,13 +97,13 @@
             android:paddingTop="5dip"
             android:paddingBottom="5dip" />
     </FrameLayout>
-        
+
     <LinearLayout android:id="@+id/buttonPanel"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:minHeight="54dip"
-        android:orientation="vertical" >     
-        <LinearLayout 
+        android:orientation="vertical" >
+        <LinearLayout
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
diff --git a/core/res/res/layout/grant_credentials_permission.xml b/core/res/res/layout/grant_credentials_permission.xml
index fe1c22e..84b6623 100644
--- a/core/res/res/layout/grant_credentials_permission.xml
+++ b/core/res/res/layout/grant_credentials_permission.xml
@@ -1,41 +1,163 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 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.
-*/
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * 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"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
     android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
+    android:layout_height="fill_parent">
+
+    <!-- The header -->
     <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:id="@+id/message" />
-    <Button android:id="@+id/allow"
+        android:id="@+id/header_text"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:text="@string/allow" />
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="@color/white"
+        android:textStyle="bold"
+        android:text="@string/grant_permissions_header_text"
+        android:shadowColor="@color/shadow"
+        android:shadowRadius="2"
+        android:singleLine="true"
+        android:background="@drawable/title_bar_medium"
+        android:gravity="left|center_vertical"
+	android:paddingLeft="19dip"
+        android:ellipsize="marquee" />
 
-    <Button android:id="@+id/deny"
+    <!-- The list of packages that correspond to the requesting UID
+    and the account/authtokenType that is being requested -->
+    <ScrollView
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:text="@string/deny" />
+        android:fillViewport="true"
+        android:layout_weight="1"
+        android:gravity="top|center_horizontal"
+        android:foreground="@drawable/title_bar_shadow">
 
-    <ListView android:id="@+id/packages_list"
-       android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:paddingTop="14dip"
+            android:orientation="vertical">
 
+            <TextView
+                android:id="@+id/grant_credentials_permission_message_header"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/grant_credentials_permission_message_header"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:paddingLeft="19dip"
+                android:paddingBottom="12dip" />
+
+            <LinearLayout
+                android:id="@+id/packages_list"
+                android:orientation="vertical"
+                android:paddingLeft="16dip"
+                android:paddingRight="12dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+            <RelativeLayout
+                android:paddingLeft="16dip"
+                android:paddingRight="12dip"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+
+                <ImageView
+                    android:id="@+id/permission_icon"
+                    android:layout_width="30dip"
+                    android:layout_height="30dip"
+                    android:src="@drawable/ic_bullet_key_permission"
+                    android:layout_alignParentLeft="true"
+                    android:scaleType="fitCenter" />
+
+                <TextView
+                    android:id="@+id/account_type"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:textStyle="bold"
+                    android:paddingLeft="6dip"
+                    android:layout_toRightOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <TextView
+                    android:id="@+id/account_name"
+                    android:textAppearance="?android:attr/textAppearanceSmall"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:layout_marginTop="-4dip"
+                    android:paddingBottom="8dip"
+                    android:paddingLeft="6dip"
+                    android:layout_below="@id/account_type"
+                    android:layout_toRightOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+                <TextView
+                    android:id="@+id/authtoken_type"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:textColor="@color/perms_dangerous_perm_color"
+                    android:textStyle="bold"
+                    android:layout_marginTop="-4dip"
+                    android:paddingBottom="8dip"
+                    android:paddingLeft="6dip"
+                    android:layout_below="@id/account_name"
+                    android:layout_toRightOf="@id/permission_icon"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+            </RelativeLayout>
+
+            <TextView
+                android:id="@+id/grant_credentials_permission_message_footer"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/grant_credentials_permission_message_footer"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:paddingLeft="19dip"
+                android:paddingBottom="12dip" />
+        </LinearLayout>
+    </ScrollView>
+
+    <!-- The buttons to allow or deny -->
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_width="fill_parent"
+        android:layout_height="52dip"
+        android:background="@drawable/bottom_bar"
+        android:paddingTop="4dip"
+        android:paddingLeft="2dip"
+        android:paddingRight="2dip">
+
+        <Button
+            android:id="@+id/allow_button"
+            android:text="@string/allow"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="2" />
+
+        <Button
+            android:id="@+id/deny_button"
+            android:text="@string/deny"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="2" />
+
+    </LinearLayout>
 </LinearLayout>
+
diff --git a/core/res/res/layout/permissions_account_and_authtokentype.xml b/core/res/res/layout/permissions_account_and_authtokentype.xml
new file mode 100644
index 0000000..4494a2c
--- /dev/null
+++ b/core/res/res/layout/permissions_account_and_authtokentype.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+
+<!--
+  Defines the layout of an account and authtoken type permission item.
+  Contains an icon, the account type and name and the authtoken type.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/permission_icon"
+        android:layout_width="30dip"
+        android:layout_height="30dip"
+        android:drawable="@drawable/ic_bullet_key_permission"
+        android:layout_alignParentLeft="true"
+        android:scaleType="fitCenter" />
+
+
+    <TextView
+        android:id="@+id/account_type"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:paddingLeft="6dip"
+        android:layout_toRightOf="@id/permission_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/account_name"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_marginTop="-4dip"
+        android:paddingBottom="8dip"
+        android:paddingLeft="6dip"
+        android:layout_below="@id/account_type"
+        android:layout_toRightOf="@id/permission_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <TextView
+        android:id="@+id/authtoken_type"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:layout_marginTop="-4dip"
+        android:paddingBottom="8dip"
+        android:paddingLeft="6dip"
+        android:layout_below="@id/account_name"
+        android:layout_toRightOf="@id/permission_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/permissions_package_list_item.xml b/core/res/res/layout/permissions_package_list_item.xml
new file mode 100644
index 0000000..1bffe51
--- /dev/null
+++ b/core/res/res/layout/permissions_package_list_item.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+
+<!--
+  Defines the layout of a single package item.
+  Contains a bullet point icon and the name of the package.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/package_icon"
+        android:layout_width="30dip"
+        android:layout_height="30dip"
+        android:layout_alignParentLeft="true"
+        android:src="@drawable/ic_text_dot"
+        android:scaleType="fitCenter" />
+
+
+    <TextView
+        android:id="@+id/package_label"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:paddingLeft="6dip"
+        android:layout_toRightOf="@id/package_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
index 4d7a6c8..5415909 100644
--- a/core/res/res/layout/screen_title_icons.xml
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -66,22 +66,25 @@
             android:layout_toLeftOf="@id/progress_circular"
             android:layout_toRightOf="@android:id/left_icon"
             >
+            <TextView android:id="@android:id/title"
+                style="?android:attr/windowTitleStyle"
+                android:layout_width="0dip"
+                android:layout_height="fill_parent"
+                android:layout_weight="1"
+                android:background="@null"
+                android:fadingEdge="horizontal"
+                android:scrollHorizontally="true"
+                android:gravity="center_vertical"
+                android:layout_marginRight="2dip"
+                />
             <!-- 2dip between the icon and the title text, if icon is present. -->
             <ImageView android:id="@android:id/right_icon"
                 android:visibility="gone"
                 android:layout_width="16dip"
                 android:layout_height="16dip"
+                android:layout_weight="0"
                 android:layout_gravity="center_vertical"
                 android:scaleType="fitCenter"
-                android:layout_marginRight="2dip" />
-            <TextView android:id="@android:id/title"
-                style="?android:attr/windowTitleStyle"
-                android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
-                android:background="@null"
-                android:fadingEdge="horizontal"
-                android:scrollHorizontally="true"
-                android:gravity="center_vertical"
                 />
             </LinearLayout>
     </RelativeLayout>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index f93fb01..6b5b168 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Vytvořit kontakt"\n"pro <xliff:g id="NUMBER">%s</xliff:g>."</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"Zaškrtnuto"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"Nezaškrtnuto"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Uvedené aplikace od <xliff:g id="APPLICATION">%2$s</xliff:g> požadují oprávnění přistupovat k přihlašovacím údajům účtu <xliff:g id="ACCOUNT">%1$s</xliff:g>. Chcete toto oprávnění udělit? Pokud je udělíte, vaše odpověď se uloží a tato výzva se již nebude zobrazovat."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Uvedené aplikace požadují od <xliff:g id="APPLICATION">%3$s</xliff:g> oprávnění přistupovat k přihlašovacím údajům (typ: <xliff:g id="TYPE">%1$s</xliff:g>) účtu <xliff:g id="ACCOUNT">%2$s</xliff:g>. Chcete toto oprávnění udělit? Pokud je udělíte, vaše odpověď se uloží a tato výzva se již nebude zobrazovat."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Povolit"</string>
     <string name="deny" msgid="2081879885755434506">"Odepřít"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Požadováno oprávnění"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokol L2TP (Layer 2 Tunneling Protocol)"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Síť VPN L2TP/IPSec s předsdíleným klíčem"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Síť VPN L2TP/IPSec s certifikátem"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index fa89fea..33c21e3 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Opret kontakt"\n"ved hjælp af <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"kontrolleret"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke kontrolleret"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De nævnte programmer beder om tilladelse til at få adgang til loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Ønsker du at give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De nævnte programmer beder om tilladelse til at få adgang til <xliff:g id="TYPE">%1$s</xliff:g>-loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Tillad"</string>
     <string name="deny" msgid="2081879885755434506">"Afvis"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Der er anmodet om tilladelse"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN baseret på forhåndsdelt nøglekodning"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatbaseret L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ebf3b66..167faa0 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Neuer Kontakt"\n"mit <xliff:g id="NUMBER">%s</xliff:g> erstellen"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aktiviert"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"nicht aktiviert"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die Anmeldeinformationen für das Konto <xliff:g id="ACCOUNT">%1$s</xliff:g> von <xliff:g id="APPLICATION">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die <xliff:g id="TYPE">%1$s</xliff:g>-Anmeldeinformationen für das Konto <xliff:g id="APPLICATION">%3$s</xliff:g> von <xliff:g id="ACCOUNT">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Zulassen"</string>
     <string name="deny" msgid="2081879885755434506">"Ablehnen"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Berechtigung angefordert"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Zertifikat mit vorinstalliertem Schlüssel"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 062658d..5b02a65 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Δημιουργία επαφής"\n"με τη χρήση του <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"επιλεγμένο"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"δεν ελέγχθηκε"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Οι εφαρμογές που παραθέτονται στη λίστα ζητούν άδεια για να αποκτήσουν πρόσβαση στα διαπιστευτήρια σύνδεσης για τον λογαριασμό <xliff:g id="ACCOUNT">%1$s</xliff:g> από <xliff:g id="APPLICATION">%2$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα ερωτηθείτε ξανά."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Οι εφαρμογές που παραθέτονται στη λίστα ζητούν άδεια για να αποκτήσουν πρόσβαση στα διαπιστευτήρια σύνδεσης <xliff:g id="TYPE">%1$s</xliff:g> για τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> από <xliff:g id="APPLICATION">%3$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα ερωτηθείτε ξανά."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Να επιτρέπεται"</string>
     <string name="deny" msgid="2081879885755434506">"Άρνηση"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Απαιτείται άδεια"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Πρωτόκολλο Layer 2 Tunneling Protocol (L2TP)"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Κλειδί pre-shared βάσει L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Πιστοποιητικό βάσει L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 88f16a9..e0408b3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Crear contacto "\n"con <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no verificado"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones enumeradas requieren permiso para acceder a las credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g> ¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones enumeradas requieren permiso para acceder a las <xliff:g id="TYPE">%1$s</xliff:g> credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>.¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Denegar"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clave previamente compartida según L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificado según L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 2757978..fa6160a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Crear un contacto"\n"a partir de <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seleccionado"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no seleccionado"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso de la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso (<xliff:g id="TYPE">%1$s</xliff:g>) de la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Denegar"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Red privada virtual L2TP/IPSec basada en clave compartida previamente"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Red privada virtual L2TP/IPSec basada en certificado"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7a07a7c..75f7406 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Ajouter un contact"\n"en utilisant <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"sélectionné"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non sélectionné"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification du compte <xliff:g id="ACCOUNT">%1$s</xliff:g> depuis <xliff:g id="APPLICATION">%2$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification des <xliff:g id="TYPE">%1$s</xliff:g> associés au compte <xliff:g id="ACCOUNT">%2$s</xliff:g> depuis <xliff:g id="APPLICATION">%3$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Autoriser"</string>
     <string name="deny" msgid="2081879885755434506">"Refuser"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Autorisation demandée"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocole de tunnelisation de niveau 2"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clé pré-partagée basée sur L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificat basé sur L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 8d2aefa..f89b9ad 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Crea contatto"\n"utilizzando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selezionato"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non selezionato"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso per l\'account <xliff:g id="ACCOUNT">%1$s</xliff:g> da <xliff:g id="APPLICATION">%2$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso <xliff:g id="TYPE">%1$s</xliff:g> per l\'account <xliff:g id="ACCOUNT">%2$s</xliff:g> da <xliff:g id="APPLICATION">%3$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Consenti"</string>
     <string name="deny" msgid="2081879885755434506">"Nega"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Autorizzazione richiesta"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocollo di tunneling livello 2"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basata su chiave precondivisa"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basata su certificato"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 8c3836c..e26ece2 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>を使って"\n"連絡先を新規登録"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"オン"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"オフ"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"リストされているアプリケーションが、アカウント<xliff:g id="ACCOUNT">%1$s</xliff:g>のログイン認証情報に<xliff:g id="APPLICATION">%2$s</xliff:g>からアクセスする権限をリクエストしています。この権限を許可しますか?許可すると、入力が記録され、次回以降はこのメッセージが表示されなくなります。"</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"リストされているアプリケーションが、アカウント<xliff:g id="ACCOUNT">%2$s</xliff:g>の<xliff:g id="TYPE">%1$s</xliff:g>ログイン認証情報に<xliff:g id="APPLICATION">%3$s</xliff:g>からアクセスする権限をリクエストしています。この権限を許可しますか?許可すると、入力は記録され、次回以降はこのメッセージが表示されなくなります。"</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"許可"</string>
     <string name="deny" msgid="2081879885755434506">"拒否"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"リクエスト済み権限"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"レイヤー2トンネリングプロトコル"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPNベースの事前共有鍵"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPNベースの証明書"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 840fa99..fc156b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"전화번호부에"\n"<xliff:g id="NUMBER">%s</xliff:g> 추가"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"선택함"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"선택 안함"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"나열된 응용프로그램이 <xliff:g id="APPLICATION">%2$s</xliff:g>에서 <xliff:g id="ACCOUNT">%1$s</xliff:g> 계정의 로그인 자격증명에 액세스할 수 있는 권한을 요청 중입니다. 권한을 부여하시겠습니까? 권한을 부여하면 응답 내용이 저장되며 메시지가 다시 표시되지 않습니다."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"나열된 응용프로그램이 <xliff:g id="APPLICATION">%3$s</xliff:g>에서 <xliff:g id="ACCOUNT">%2$s</xliff:g> 계정의 <xliff:g id="TYPE">%1$s</xliff:g> 로그인 자격증명에 액세스할 수 있는 권한을 요청 중입니다. 권한을 부여하시겠습니까? 권한을 부여하면 응답 내용이 저장되며 메시지가 다시 표시되지 않습니다."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"허용"</string>
     <string name="deny" msgid="2081879885755434506">"거부"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"권한 요청"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"L2TP(Layer 2 Tunneling Protocol)"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"사전 공유 키 기반 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"인증서 기반 L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index ced4f16..c0aae98 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Lag kontakt"\n"med nummeret <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"valgt"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke valgt"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Det nevnte programmet ber om tilgangstillatelse til påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Det nevnte programmet ber om tilgangstillatelse til <xliff:g id="TYPE">%1$s</xliff:g>-påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Tillat"</string>
     <string name="deny" msgid="2081879885755434506">"Avslå"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Tillatelse forespurt"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Lag 2-tunneleringsprotokoll"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Passordbasert L2TP/IPSec-VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sertifikatbasert L2TP/IPSec-VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index c5f5a4b..9b95c09 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -782,8 +782,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Contact maken"\n"met <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aangevinkt"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niet aangevinkt"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor account \'<xliff:g id="ACCOUNT">%1$s</xliff:g>\' van <xliff:g id="APPLICATION">%2$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor <xliff:g id="TYPE">%1$s</xliff:g> van het account \'<xliff:g id="ACCOUNT">%2$s</xliff:g>\' van <xliff:g id="APPLICATION">%3$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Toestaan"</string>
     <string name="deny" msgid="2081879885755434506">"Weigeren"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Toestemming gevraagd"</string>
@@ -801,4 +805,6 @@
     <skip />
     <!-- no translation found for l2tp_ipsec_crt_vpn_description (5382714073103653577) -->
     <skip />
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 388a9e7..6ee52ed 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Utwórz kontakt"\n"dla numeru <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"zaznaczone"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niezaznaczone"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dla konta <xliff:g id="ACCOUNT">%1$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%2$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dotyczących funkcji <xliff:g id="TYPE">%1$s</xliff:g> dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%3$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Zezwól"</string>
     <string name="deny" msgid="2081879885755434506">"Odmów"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Żądane pozwolenie"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokół L2TP"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Sieć VPN L2TP/IPSec z kluczem PSK"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sieć VPN L2TP/IPSec z certyfikatem"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index b2edaae..c86fbdb 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Criar contacto"\n"utilizando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não verificado"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão da conta <xliff:g id="ACCOUNT">%1$s</xliff:g> a partir de <xliff:g id="APPLICATION">%2$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> a partir de <xliff:g id="APPLICATION">%3$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Recusar"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização Solicitada"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de camada 2 (L2TP)"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec baseada em chave pré- partilhada"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec baseada em certificado"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 725053c..70f82ee 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Criar contato "\n"usando <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selecionado"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não selecionado"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login para a conta <xliff:g id="ACCOUNT">%1$s</xliff:g> do <xliff:g id="APPLICATION">%2$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> do <xliff:g id="APPLICATION">%3$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Permitir"</string>
     <string name="deny" msgid="2081879885755434506">"Negar"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização solicitada"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de encapsulamento de camada 2"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec com base em chave pré-compartilhada"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec com base em certificado"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 1e1500b..56b75fc 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Создать контакт"\n"с номером <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"отмечено"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"не проверено"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Перечисленные приложения запрашивают разрешение на доступ к регистрационном данным аккаунта <xliff:g id="ACCOUNT">%1$s</xliff:g> из <xliff:g id="APPLICATION">%2$s</xliff:g>. Разрешить доступ? Если да, ответ будет сохранен и это сообщение больше не будет выводиться."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Перечисленные приложения запрашивают разрешение на доступ к регистрационном данным <xliff:g id="TYPE">%1$s</xliff:g> аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g> из <xliff:g id="APPLICATION">%3$s</xliff:g>. Разрешить доступ? Если да, ответ будет сохранен и это сообщение больше не будет выводиться."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Разрешить"</string>
     <string name="deny" msgid="2081879885755434506">"Отклонить"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Разрешение запрошено"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Протокол L2TP"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на основе предв. общ. ключа)"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на основе сертификата)"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 823ba22..3e32b15 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"Skapa kontakt"\n"med <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"markerad"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"inte markerad"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Programmen begär åtkomst till inloggningsuppgifterna för kontot <xliff:g id="ACCOUNT">%1$s</xliff:g> från <xliff:g id="APPLICATION">%2$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Programmen begär åtkomst till inloggningsuppgifterna <xliff:g id="TYPE">%1$s</xliff:g> för kontot <xliff:g id="ACCOUNT">%2$s</xliff:g> från <xliff:g id="APPLICATION">%3$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"Tillåt"</string>
     <string name="deny" msgid="2081879885755434506">"Neka"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"Begärd behörighet"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"I förväg delad L2TP/IPSec VPN-nyckel"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatsbaserad L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f95fc0a..cae0c03 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>"\n" ile kişi oluştur"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seçildi"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"seçilmedi"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%2$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%1$s</xliff:g> hesabı için giriş bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%3$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabı için <xliff:g id="TYPE">%1$s</xliff:g> girişi bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"İzin Ver"</string>
     <string name="deny" msgid="2081879885755434506">"Reddet"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"İzin İstendi"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"Katman 2 Tünel Protokolü"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN temelli önceden paylaşılmış anahtar"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN temelli sertifika"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ab5554b..6620a60 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"创建电话号码为"\n"<xliff:g id="NUMBER">%s</xliff:g> 的联系人"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已选中"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未选中"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"所列应用程序正在请求相应的权限,以便从<xliff:g id="APPLICATION">%2$s</xliff:g>访问 <xliff:g id="ACCOUNT">%1$s</xliff:g> 帐户的登录凭据。是否要授予这种权限?如果授予,系统会记住您所做的选择,且不再提示。"</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"所列应用程序正在请求相应的权限,以便从<xliff:g id="APPLICATION">%3$s</xliff:g>访问 <xliff:g id="ACCOUNT">%2$s</xliff:g> 帐户的<xliff:g id="TYPE">%1$s</xliff:g>登录凭据。是否要授予这种权限?如果授予,系统会记住您所做的选择,且不再提示。"</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"允许"</string>
     <string name="deny" msgid="2081879885755434506">"拒绝"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"已请求权限"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"第 2 层隧道协议"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"基于预共享密钥的 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"基于证书的 L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index da02fe7..b9f344e 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -777,8 +777,12 @@
     <string name="create_contact_using" msgid="4947405226788104538">"建立手機號碼為 <xliff:g id="NUMBER">%s</xliff:g>"\n"的聯絡人"</string>
     <string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已勾選"</string>
     <string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未勾選"</string>
-    <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"這些應用程式要求存取「<xliff:g id="APPLICATION">%2$s</xliff:g>」的 <xliff:g id="ACCOUNT">%1$s</xliff:g> 帳戶登入認證的權限,您要授予此權限嗎?如果確定授予,系統會記住您的選擇,不會再次詢問您。"</string>
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"這些應用程式要求存取「<xliff:g id="APPLICATION">%3$s</xliff:g>」的 <xliff:g id="ACCOUNT">%2$s</xliff:g> 帳戶「<xliff:g id="TYPE">%1$s</xliff:g>」登入認證的權限,您要授予此權限嗎?如果確定授予,系統會記住您的選擇,不會再次詢問您。"</string>
+    <!-- no translation found for grant_credentials_permission_message_header (6824538733852821001) -->
+    <skip />
+    <!-- no translation found for grant_credentials_permission_message_footer (3125211343379376561) -->
+    <skip />
+    <!-- no translation found for grant_permissions_header_text (2722567482180797717) -->
+    <skip />
     <string name="allow" msgid="7225948811296386551">"允許"</string>
     <string name="deny" msgid="2081879885755434506">"拒絕"</string>
     <string name="permission_request_notification_title" msgid="5390555465778213840">"已要求權限"</string>
@@ -792,4 +796,6 @@
     <string name="l2tp_vpn_description" msgid="3750692169378923304">"第二層通道通訊協定"</string>
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
+    <!-- no translation found for upload_file (2897957172366730416) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5d3069b..a506c9a 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -78,6 +78,7 @@
     <!-- For security permissions -->
     <color name="perms_dangerous_grp_color">#dd6826</color>
     <color name="perms_dangerous_perm_color">#dd6826</color>
+    <color name="shadow">#cc222222</color>
 
     <!-- For search-related UIs -->
     <color name="search_url_text_normal">#7fa87f</color>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de30fe7..83a6dc5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2067,16 +2067,9 @@
     <!-- Title for the unselected state of a CompoundButton. -->
     <string name="accessibility_compound_button_unselected">not checked</string>
 
-    <string name="grant_credentials_permission_message_desc">The
-    listed applications are requesting permission to access the login credentials for account <xliff:g id="account" example="foo@gmail.com">%1$s</xliff:g> from
-    <xliff:g id="application" example="Google Apps">%2$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
-    again.</string>
-
-    <string name="grant_credentials_permission_message_with_authtokenlabel_desc">The
-    listed applications are requesting permission to access the <xliff:g id="type" example="Contacts">%1$s</xliff:g> login credentials for account <xliff:g id="account" example="foo@gmail.com">%2$s</xliff:g> from
-    <xliff:g id="application" example="Google Apps">%3$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
-    again.</string>
-
+    <string name="grant_credentials_permission_message_header">The following one or more applications request permission to access your account, now and in the future.</string>
+    <string name="grant_credentials_permission_message_footer">Do you want to allow this request?</string>
+    <string name="grant_permissions_header_text">Access Request</string>
     <string name="allow">Allow</string>
     <string name="deny">Deny</string>
     <string name="permission_request_notification_title">Permission Requested</string>
@@ -2100,4 +2093,6 @@
     <string name="l2tp_vpn_description">Layer 2 Tunneling Protocol</string>
     <string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string>
     <string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string>
+    <!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
+    <string name="upload_file">Choose file</string>
 </resources>
diff --git a/graphics/java/android/graphics/utils/BoundaryPatch.java b/graphics/java/android/graphics/utils/BoundaryPatch.java
new file mode 100644
index 0000000..1cd5e13
--- /dev/null
+++ b/graphics/java/android/graphics/utils/BoundaryPatch.java
@@ -0,0 +1,173 @@
+/*
+ * 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 android.graphics.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.Xfermode;
+
+/**
+ * @hide
+ */
+public class BoundaryPatch {
+    private Paint   mPaint;
+    private Bitmap  mTexture;
+    private int     mRows;
+    private int     mCols;
+    private float[] mCubicPoints;
+    private boolean mDirty;
+    // these are the computed output of the native code
+    private float[] mVerts;
+    private short[] mIndices;
+
+    public BoundaryPatch() {
+        mRows = mCols = 2;  // default minimum
+        mCubicPoints = new float[24];
+        mPaint = new Paint();
+        mPaint.setDither(true);
+        mPaint.setFilterBitmap(true);
+        mDirty = true;
+    }
+
+    /**
+     * Set the boundary to be 4 cubics. This takes a single array of floats,
+     * and picks up the 12 pairs starting at offset, and treats them as
+     * the x,y coordinates of the cubic control points. The points wrap around
+     * a patch, as follows. For documentation purposes, pts[i] will mean the
+     * x,y pair of floats, as if pts[] were an array of "points".
+     *
+     * Top: pts[0..3]
+     * Right: pts[3..6]
+     * Bottom: pts[6..9]
+     * Right: pts[9..11], pts[0]
+     *
+     * The coordinates are copied from the input array, so subsequent changes
+     * to pts[] will not be reflected in the boundary.
+     *
+     * @param pts The src array of x,y pairs for the boundary cubics
+     * @param offset The index into pts of the first pair
+     * @param rows The number of points across to approximate the boundary.
+     *             Must be >= 2, though very large values may slow down drawing
+     * @param cols The number of points down to approximate the boundary.
+     *             Must be >= 2, though very large values may slow down drawing
+     */
+    public void setCubicBoundary(float[] pts, int offset, int rows, int cols) {
+        if (rows < 2 || cols < 2) {
+            throw new RuntimeException("rows and cols must be >= 2");
+        }
+        System.arraycopy(pts, offset, mCubicPoints, 0, 24);
+        if (mRows != rows || mCols != cols) {
+            mRows = rows;
+            mCols = cols;
+        }
+        mDirty = true;
+    }
+
+    /**
+     * Reference a bitmap texture to be mapped onto the patch.
+     */
+    public void setTexture(Bitmap texture) {
+        if (mTexture != texture) {
+            if (mTexture == null ||
+                    mTexture.getWidth() != texture.getWidth() ||
+                    mTexture.getHeight() != texture.getHeight()) {
+                // need to recompute texture coordinates
+                mDirty = true;
+            }
+            mTexture = texture;
+            mPaint.setShader(new BitmapShader(texture,
+                                              Shader.TileMode.CLAMP,
+                                              Shader.TileMode.CLAMP));
+        }
+    }
+
+    /**
+     * Return the paint flags for the patch
+     */
+    public int getPaintFlags() {
+        return mPaint.getFlags();
+    }
+
+    /**
+     * Set the paint flags for the patch
+     */
+    public void setPaintFlags(int flags) {
+        mPaint.setFlags(flags);
+    }
+
+    /**
+     * Set the xfermode for the patch
+     */
+    public void setXfermode(Xfermode mode) {
+        mPaint.setXfermode(mode);
+    }
+
+    /**
+     * Set the alpha for the patch
+     */
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    /**
+     * Draw the patch onto the canvas.
+     *
+     * setCubicBoundary() and setTexture() must be called before drawing.
+     */
+    public void draw(Canvas canvas) {
+        if (mDirty) {
+            buildCache();
+            mDirty = false;
+        }
+
+        // cut the count in half, since mVerts.length is really the length of
+        // the verts[] and tex[] arrays combined
+        // (tex[] are stored after verts[])
+        int vertCount = mVerts.length >> 1;
+        canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertCount,
+                            mVerts, 0, mVerts, vertCount, null, 0,
+                            mIndices, 0, mIndices.length,
+                            mPaint);
+    }
+
+    private void buildCache() {
+        // we need mRows * mCols points, for verts and another set for textures
+        // so *2 for going from points -> floats, and *2 for verts and textures
+        int vertCount = mRows * mCols * 4;
+        if (mVerts == null || mVerts.length != vertCount) {
+            mVerts = new float[vertCount];
+        }
+
+        int indexCount = (mRows - 1) * (mCols - 1) * 6;
+        if (mIndices == null || mIndices.length != indexCount) {
+            mIndices = new short[indexCount];
+        }
+
+        nativeComputeCubicPatch(mCubicPoints,
+                                mTexture.getWidth(), mTexture.getHeight(),
+                                mRows, mCols, mVerts, mIndices);
+    }
+
+    private static native
+    void nativeComputeCubicPatch(float[] cubicPoints,
+                                 int texW, int texH, int rows, int cols,
+                                 float[] verts, short[] indices);
+}
+
diff --git a/include/media/stagefright/CachingDataSource.h b/include/media/stagefright/CachingDataSource.h
index e35e19e..b0fc4b2 100644
--- a/include/media/stagefright/CachingDataSource.h
+++ b/include/media/stagefright/CachingDataSource.h
@@ -29,9 +29,9 @@
     CachingDataSource(
             const sp<DataSource> &source, size_t pageSize, int numPages);
 
-    status_t InitCheck() const;
+    virtual status_t initCheck() const;
 
-    virtual ssize_t read_at(off_t offset, void *data, size_t size);
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
 
 protected:
     virtual ~CachingDataSource();
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index 7042e1a..ff3ea05 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -26,12 +26,11 @@
 
 namespace android {
 
-class ICamera;
-class ICameraClient;
 class IMemory;
+class ISurface;
+class Camera;
 
-class CameraSource : public MediaSource,
-                     public MediaBufferObserver {
+class CameraSource : public MediaSource {
 public:
     static CameraSource *Create();
 
@@ -45,24 +44,25 @@
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
 
-    virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2);
-    virtual void dataCallback(int32_t msgType, const sp<IMemory>& data);
-
-    virtual void signalBufferReturned(MediaBuffer *buffer);
-
 private:
-    CameraSource(const sp<ICamera> &camera, const sp<ICameraClient> &client);
+    friend class CameraSourceListener;
 
-    sp<ICamera> mCamera;
-    sp<ICameraClient> mCameraClient;
+    sp<Camera> mCamera;
 
     Mutex mLock;
     Condition mFrameAvailableCondition;
     List<sp<IMemory> > mFrames;
+    List<int64_t> mFrameTimes;
 
-    int mNumFrames;
+    int mWidth, mHeight;
+    int64_t mFirstFrameTimeUs;
+    int32_t mNumFrames;
     bool mStarted;
 
+    CameraSource(const sp<Camera> &camera);
+
+    void dataCallback(int32_t msgType, const sp<IMemory> &data);
+
     CameraSource(const CameraSource &);
     CameraSource &operator=(const CameraSource &);
 };
diff --git a/include/media/stagefright/ColorConverter.h b/include/media/stagefright/ColorConverter.h
new file mode 100644
index 0000000..1e341b9
--- /dev/null
+++ b/include/media/stagefright/ColorConverter.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef COLOR_CONVERTER_H_
+
+#define COLOR_CONVERTER_H_
+
+#include <sys/types.h>
+
+#include <stdint.h>
+
+#include <OMX_Video.h>
+
+namespace android {
+
+struct ColorConverter {
+    ColorConverter(OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to);
+    ~ColorConverter();
+
+    bool isValid() const;
+
+    void convert(
+            size_t width, size_t height,
+            const void *srcBits, size_t srcSkip,
+            void *dstBits, size_t dstSkip);
+
+private:
+    OMX_COLOR_FORMATTYPE mSrcFormat, mDstFormat;
+    uint8_t *mClip;
+
+    uint8_t *initClip();
+
+    void convertCbYCrY(
+            size_t width, size_t height,
+            const void *srcBits, size_t srcSkip,
+            void *dstBits, size_t dstSkip);
+
+    void convertYUV420Planar(
+            size_t width, size_t height,
+            const void *srcBits, size_t srcSkip,
+            void *dstBits, size_t dstSkip);
+
+    void convertQCOMYUV420SemiPlanar(
+            size_t width, size_t height,
+            const void *srcBits, size_t srcSkip,
+            void *dstBits, size_t dstSkip);
+
+    ColorConverter(const ColorConverter &);
+    ColorConverter &operator=(const ColorConverter &);
+};
+
+}  // namespace android
+
+#endif  // COLOR_CONVERTER_H_
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index f46f0af..b843cd9 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -33,7 +33,9 @@
 public:
     DataSource() {}
 
-    virtual ssize_t read_at(off_t offset, void *data, size_t size) = 0;
+    virtual status_t initCheck() const = 0;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size) = 0;
 
     // Convenience methods:
     bool getUInt16(off_t offset, uint16_t *x);
diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h
index ccbe0ef..d7b42c3 100644
--- a/include/media/stagefright/FileSource.h
+++ b/include/media/stagefright/FileSource.h
@@ -29,12 +29,14 @@
 class FileSource : public DataSource {
 public:
     FileSource(const char *filename);
+
+    virtual status_t initCheck() const;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+protected:
     virtual ~FileSource();
 
-    status_t InitCheck() const;
-
-    virtual ssize_t read_at(off_t offset, void *data, size_t size);
-
 private:
     FILE *mFile;
     Mutex mLock;
diff --git a/include/media/stagefright/HTTPDataSource.h b/include/media/stagefright/HTTPDataSource.h
index 0587c7c..d5dc9e6 100644
--- a/include/media/stagefright/HTTPDataSource.h
+++ b/include/media/stagefright/HTTPDataSource.h
@@ -19,28 +19,29 @@
 #define HTTP_DATASOURCE_H_
 
 #include <media/stagefright/DataSource.h>
-#include <media/stagefright/HTTPStream.h>
 
 namespace android {
 
+class HTTPStream;
+
 class HTTPDataSource : public DataSource {
 public:
     HTTPDataSource(const char *host, int port, const char *path);
     HTTPDataSource(const char *uri);
 
+    virtual status_t initCheck() const;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+protected:
     virtual ~HTTPDataSource();
 
-    // XXXandih
-    status_t InitCheck() const { return OK; }
-
-    virtual ssize_t read_at(off_t offset, void *data, size_t size);
-
 private:
     enum {
         kBufferSize = 64 * 1024
     };
 
-    HTTPStream mHttp;
+    HTTPStream *mHttp;
     char *mHost;
     int mPort;
     char *mPath;
@@ -49,6 +50,8 @@
     size_t mBufferLength;
     off_t mBufferOffset;
 
+    status_t mInitCheck;
+
     HTTPDataSource(const HTTPDataSource &);
     HTTPDataSource &operator=(const HTTPDataSource &);
 };
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index feb66e3..1efeb92 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -34,6 +34,7 @@
 extern const char *MEDIA_MIMETYPE_AUDIO_RAW;
 
 extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4;
+extern const char *MEDIA_MIMETYPE_CONTAINER_WAV;
 
 }  // namespace android
 
diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 2bb0ed6..73d0f77 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -36,6 +36,9 @@
     ERROR_BUFFER_TOO_SMALL  = MEDIA_ERROR_BASE - 9,
     ERROR_UNSUPPORTED       = MEDIA_ERROR_BASE - 10,
     ERROR_END_OF_STREAM     = MEDIA_ERROR_BASE - 11,
+
+    // Not technically an error.
+    INFO_FORMAT_CHANGED    = MEDIA_ERROR_BASE - 12,
 };
 
 }  // namespace android
diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h
index 67e45bd..d56d4b3 100644
--- a/include/media/stagefright/MediaExtractor.h
+++ b/include/media/stagefright/MediaExtractor.h
@@ -31,9 +31,17 @@
     static sp<MediaExtractor> Create(
             const sp<DataSource> &source, const char *mime = NULL);
 
+    static sp<MediaExtractor> CreateFromURI(
+            const char *uri, const char *mime = NULL);
+
     virtual size_t countTracks() = 0;
     virtual sp<MediaSource> getTrack(size_t index) = 0;
-    virtual sp<MetaData> getTrackMetaData(size_t index) = 0;
+
+    enum GetTrackMetaDataFlags {
+        kIncludeExtensiveMetaData = 1
+    };
+    virtual sp<MetaData> getTrackMetaData(
+            size_t index, uint32_t flags = 0) = 0;
 
 protected:
     MediaExtractor() {}
diff --git a/include/media/stagefright/MediaSource.h b/include/media/stagefright/MediaSource.h
index d1fa114..96d57e7 100644
--- a/include/media/stagefright/MediaSource.h
+++ b/include/media/stagefright/MediaSource.h
@@ -51,6 +51,9 @@
     // buffer is available, an error is encountered of the end of the stream
     // is reached.
     // End of stream is signalled by a result of ERROR_END_OF_STREAM.
+    // A result of INFO_FORMAT_CHANGED indicates that the format of this
+    // MediaSource has changed mid-stream, the client can continue reading
+    // but should be prepared for buffers of the new configuration.
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL) = 0;
 
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index abb45a9..c2d8f98 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -27,25 +27,26 @@
 
 namespace android {
 
+// The following keys map to int32_t data unless indicated otherwise.
 enum {
-    kKeyMIMEType          = 'mime',
+    kKeyMIMEType          = 'mime',  // cstring
     kKeyWidth             = 'widt',
     kKeyHeight            = 'heig',
     kKeyChannelCount      = '#chn',
     kKeySampleRate        = 'srte',
     kKeyBitRate           = 'brte',
-    kKeyESDS              = 'esds',
-    kKeyAVCC              = 'avcc',
-    kKeyTimeUnits         = '#tim',
-    kKeyTimeScale         = 'scal',
+    kKeyESDS              = 'esds',  // raw data
+    kKeyAVCC              = 'avcc',  // raw data
     kKeyWantsNALFragments = 'NALf',
     kKeyIsSyncFrame       = 'sync',
-    kKeyDuration          = 'dura',
+    kKeyTime              = 'time',  // int64_t (usecs)
+    kKeyDuration          = 'dura',  // int64_t (usecs)
     kKeyColorFormat       = 'colf',
-    kKeyPlatformPrivate   = 'priv',
-    kKeyDecoderComponent  = 'decC',
+    kKeyPlatformPrivate   = 'priv',  // pointer
+    kKeyDecoderComponent  = 'decC',  // cstring
     kKeyBufferID          = 'bfID',
     kKeyMaxInputSize      = 'inpS',
+    kKeyThumbnailTime     = 'thbT',  // int64_t (usecs)
 };
 
 enum {
@@ -62,6 +63,7 @@
         TYPE_NONE     = 'none',
         TYPE_C_STRING = 'cstr',
         TYPE_INT32    = 'in32',
+        TYPE_INT64    = 'in64',
         TYPE_FLOAT    = 'floa',
         TYPE_POINTER  = 'ptr ',
     };
@@ -71,11 +73,13 @@
 
     bool setCString(uint32_t key, const char *value);
     bool setInt32(uint32_t key, int32_t value);
+    bool setInt64(uint32_t key, int64_t value);
     bool setFloat(uint32_t key, float value);
     bool setPointer(uint32_t key, void *value);
 
     bool findCString(uint32_t key, const char **value);
     bool findInt32(uint32_t key, int32_t *value);
+    bool findInt64(uint32_t key, int64_t *value);
     bool findFloat(uint32_t key, float *value);
     bool findPointer(uint32_t key, void **value);
 
diff --git a/include/media/stagefright/MmapSource.h b/include/media/stagefright/MmapSource.h
index a8bd57f..1b39d53 100644
--- a/include/media/stagefright/MmapSource.h
+++ b/include/media/stagefright/MmapSource.h
@@ -30,13 +30,14 @@
     // Assumes ownership of "fd".
     MmapSource(int fd, int64_t offset, int64_t length);
 
-    virtual ~MmapSource();
+    virtual status_t initCheck() const;
 
-    status_t InitCheck() const;
-
-    virtual ssize_t read_at(off_t offset, void *data, size_t size);
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
     virtual status_t getSize(off_t *size);
 
+protected:
+    virtual ~MmapSource();
+
 private:
     int mFd;
     void *mBase;
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 3f3dcf9..7890883 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -30,11 +30,15 @@
 
 struct OMXCodec : public MediaSource,
                   public MediaBufferObserver {
+    enum CreationFlags {
+        kPreferSoftwareCodecs = 1,
+    };
     static sp<OMXCodec> Create(
             const sp<IOMX> &omx,
             const sp<MetaData> &meta, bool createEncoder,
             const sp<MediaSource> &source,
-            const char *matchComponentName = NULL);
+            const char *matchComponentName = NULL,
+            uint32_t flags = 0);
 
     static void setComponentRole(
             const sp<IOMX> &omx, IOMX::node_id node, bool isEncoder,
@@ -90,7 +94,6 @@
         kRequiresFlushCompleteEmulation      = 16,
         kRequiresAllocateBufferOnOutputPorts = 32,
         kRequiresFlushBeforeShutdown         = 64,
-        kOutputDimensionsAre16Aligned        = 128,
     };
 
     struct BufferInfo {
@@ -107,7 +110,6 @@
 
     sp<IOMX> mOMX;
     IOMX::node_id mNode;
-    sp<OMXCodecObserver> mObserver;
     uint32_t mQuirks;
     bool mIsEncoder;
     char *mMIME;
@@ -125,6 +127,7 @@
     bool mInitialBufferSubmit;
     bool mSignalledEOS;
     bool mNoMoreOutputData;
+    bool mOutputPortSettingsHaveChanged;
     int64_t mSeekTimeUs;
 
     Mutex mLock;
@@ -155,6 +158,8 @@
     void setVideoInputFormat(
             const char *mime, OMX_U32 width, OMX_U32 height);
 
+    status_t setupMPEG4EncoderParameters();
+
     void setVideoOutputFormat(
             const char *mime, OMX_U32 width, OMX_U32 height);
 
@@ -208,6 +213,14 @@
 
     void dumpPortStatus(OMX_U32 portIndex);
 
+    static uint32_t getComponentQuirks(const char *componentName);
+
+    static void findMatchingCodecs(
+            const char *mime,
+            bool createEncoder, const char *matchComponentName,
+            uint32_t flags,
+            Vector<String8> *matchingCodecs);
+
     OMXCodec(const OMXCodec &);
     OMXCodec &operator=(const OMXCodec &);
 };
diff --git a/include/media/stagefright/ShoutcastSource.h b/include/media/stagefright/ShoutcastSource.h
index 352857a..bc67156 100644
--- a/include/media/stagefright/ShoutcastSource.h
+++ b/include/media/stagefright/ShoutcastSource.h
@@ -31,7 +31,6 @@
 public:
     // Assumes ownership of "http".
     ShoutcastSource(HTTPStream *http);
-    virtual ~ShoutcastSource();
 
     virtual status_t start(MetaData *params = NULL);
     virtual status_t stop();
@@ -41,6 +40,9 @@
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
 
+protected:
+    virtual ~ShoutcastSource();
+
 private:
     HTTPStream *mHttp;
     size_t mMetaDataOffset;
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index e80d8aa..1713324 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -137,11 +137,17 @@
                     cursor = res.query(uri, MEDIA_COLUMNS, null, null, null);
                 }
                 
-                if (cursor != null && cursor.getCount() == 1) {
-                    cursor.moveToFirst();
-                    return cursor.getString(2);
-                } else {
-                    title = uri.getLastPathSegment();
+                try {
+                    if (cursor != null && cursor.getCount() == 1) {
+                        cursor.moveToFirst();
+                        return cursor.getString(2);
+                    } else {
+                        title = uri.getLastPathSegment();
+                    }
+                } finally {
+                    if (cursor != null) {
+                        cursor.close();
+                    }
                 }
             }
         }
diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java
index f9d69fb..0cf4e76 100644
--- a/media/java/android/media/ThumbnailUtil.java
+++ b/media/java/android/media/ThumbnailUtil.java
@@ -33,10 +33,9 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.media.MediaMetadataRetriever;
+import android.media.MediaFile.MediaFileType;
 
-import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
 
@@ -295,7 +294,7 @@
      * @param uri URI of original image
      * @param origId image id
      * @param kind either MINI_KIND or MICRO_KIND
-     * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method.
+     * @param saveMini Whether to save MINI_KIND thumbnail obtained in this method.
      * @return Bitmap
      */
     public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri,
@@ -305,16 +304,12 @@
                 ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE;
         int maxPixels = wantMini ?
                 ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS;
-        byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize);
+        SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
         Bitmap bitmap = null;
-
-        if (thumbData != null) {
-            BitmapFactory.Options options = new BitmapFactory.Options();
-            options.inSampleSize = computeSampleSize(options, targetSize, maxPixels);
-            options.inDither = false;
-            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-            options.inJustDecodeBounds = false;
-            bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
+        MediaFileType fileType = MediaFile.getFileType(filePath);
+        if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
+            createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
+            bitmap = sizedThumbnailBitmap.mBitmap;
         }
 
         if (bitmap == null) {
@@ -326,9 +321,11 @@
         }
 
         if (saveMini) {
-            if (thumbData != null) {
-                ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(),
-                        bitmap.getHeight());
+            if (sizedThumbnailBitmap.mThumbnailData != null) {
+                ThumbnailUtil.storeThumbnail(cr, origId,
+                        sizedThumbnailBitmap.mThumbnailData,
+                        sizedThumbnailBitmap.mThumbnailWidth,
+                        sizedThumbnailBitmap.mThumbnailHeight);
             } else {
                 ThumbnailUtil.storeThumbnail(cr, origId, bitmap);
             }
@@ -454,6 +451,7 @@
         Cursor c = cr.query(thumbUri, THUMB_PROJECTION,
               Thumbnails.IMAGE_ID + "=?",
               new String[]{String.valueOf(origId)}, null);
+        if (c == null) return null;
         try {
             if (c.moveToNext()) {
                 return ContentUris.withAppendedId(thumbUri, c.getLong(0));
@@ -482,6 +480,7 @@
         if (thumb == null) return false;
         try {
             Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight());
+            if (uri == null) return false;
             OutputStream thumbOut = cr.openOutputStream(uri);
             thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut);
             thumbOut.close();
@@ -514,31 +513,69 @@
         }
     }
 
-    // Extract thumbnail in image that meets the targetSize criteria.
-    static byte[] createThumbnailFromEXIF(String filePath, int targetSize) {
-        if (filePath == null) return null;
+    // SizedThumbnailBitmap contains the bitmap, which is downsampled either from
+    // the thumbnail in exif or the full image.
+    // mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail is not null.
+    // The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
+    private static class SizedThumbnailBitmap {
+        public byte[] mThumbnailData;
+        public Bitmap mBitmap;
+        public int mThumbnailWidth;
+        public int mThumbnailHeight;
+    }
 
+    // Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
+    // The functions returns a SizedThumbnailBitmap,
+    // which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
+    private static void createThumbnailFromEXIF(String filePath, int targetSize,
+            int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
+        if (filePath == null) return;
+
+        ExifInterface exif = null;
+        byte [] thumbData = null;
         try {
-            ExifInterface exif = new ExifInterface(filePath);
-            if (exif == null) return null;
-            byte [] thumbData = exif.getThumbnail();
-            if (thumbData == null) return null;
-            // Sniff the size of the EXIF thumbnail before decoding it. Photos
-            // from the device will pass, but images that are side loaded from
-            // other cameras may not.
-            BitmapFactory.Options options = new BitmapFactory.Options();
-            options.inJustDecodeBounds = true;
-            BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options);
-
-            int width = options.outWidth;
-            int height = options.outHeight;
-
-            if (width >= targetSize && height >= targetSize) {
-                return thumbData;
+            exif = new ExifInterface(filePath);
+            if (exif != null) {
+                thumbData = exif.getThumbnail();
             }
         } catch (IOException ex) {
             Log.w(TAG, ex);
         }
-        return null;
+
+        BitmapFactory.Options fullOptions = new BitmapFactory.Options();
+        BitmapFactory.Options exifOptions = new BitmapFactory.Options();
+        int exifThumbWidth = 0;
+        int fullThumbWidth = 0;
+
+        // Compute exifThumbWidth.
+        if (thumbData != null) {
+            exifOptions.inJustDecodeBounds = true;
+            BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
+            exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
+            exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
+        }
+
+        // Compute fullThumbWidth.
+        fullOptions.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(filePath, fullOptions);
+        fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
+        fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
+
+        // Choose the larger thumbnail as the returning sizedThumbBitmap.
+        if (exifThumbWidth >= fullThumbWidth) {
+            int width = exifOptions.outWidth;
+            int height = exifOptions.outHeight;
+            exifOptions.inJustDecodeBounds = false;
+            sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
+                    thumbData.length, exifOptions);
+            if (sizedThumbBitmap.mBitmap != null) {
+                sizedThumbBitmap.mThumbnailData = thumbData;
+                sizedThumbBitmap.mThumbnailWidth = width;
+                sizedThumbBitmap.mThumbnailHeight = height;
+            }
+        } else {
+            fullOptions.inJustDecodeBounds = false;
+            sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
+        }
     }
 }
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 88a7064..76a9e7d 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -284,7 +284,7 @@
         data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
         data.writeIntPtr((intptr_t)node);
         data.writeIntPtr((intptr_t)buffer);
-        remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+        remote()->transact(FILL_BUFFER, data, &reply);
 
         return reply.readInt32();
     }
@@ -302,7 +302,7 @@
         data.writeInt32(range_length);
         data.writeInt32(flags);
         data.writeInt64(timestamp);
-        remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+        remote()->transact(EMPTY_BUFFER, data, &reply);
 
         return reply.readInt32();
     }
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index fb569da..6fc9fa2 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -18,8 +18,9 @@
 
 ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
 
-LOCAL_SRC_FILES +=              \
-    StagefrightPlayer.cpp
+LOCAL_SRC_FILES +=                      \
+    StagefrightPlayer.cpp               \
+    StagefrightMetadataRetriever.cpp
 
 LOCAL_CFLAGS += -DBUILD_WITH_FULL_STAGEFRIGHT=1
 
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 0a6c365..b81684b 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -366,11 +366,44 @@
         size_t* infoSize, size_t* totalMemory, size_t* backtraceSize);
 extern "C" void free_malloc_leak_info(uint8_t* info);
 
+// Use the String-class below instead of String8 to allocate all memory
+// beforehand and not reenter the heap while we are examining it...
+struct MyString8 {
+    static const size_t MAX_SIZE = 256 * 1024;
+
+    MyString8()
+        : mPtr((char *)malloc(MAX_SIZE)) {
+        *mPtr = '\0';
+    }
+
+    ~MyString8() {
+        free(mPtr);
+    }
+
+    void append(const char *s) {
+        strcat(mPtr, s);
+    }
+
+    const char *string() const {
+        return mPtr;
+    }
+
+    size_t size() const {
+        return strlen(mPtr);
+    }
+
+private:
+    char *mPtr;
+
+    MyString8(const MyString8 &);
+    MyString8 &operator=(const MyString8 &);
+};
+
 void memStatus(int fd, const Vector<String16>& args)
 {
     const size_t SIZE = 256;
     char buffer[SIZE];
-    String8 result;
+    MyString8 result;
 
     typedef struct {
         size_t size;
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index 2cdc351..866c7bd 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -38,6 +38,7 @@
 #include "VorbisMetadataRetriever.h"
 #include "MidiMetadataRetriever.h"
 #include "MetadataRetrieverClient.h"
+#include "StagefrightMetadataRetriever.h"
 
 /* desktop Linux needs a little help with gettid() */
 #if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
@@ -118,9 +119,15 @@
             LOGV("create midi metadata retriever");
             p = new MidiMetadataRetriever();
             break;
+#if BUILD_WITH_FULL_STAGEFRIGHT
+        case STAGEFRIGHT_PLAYER:
+            LOGV("create StagefrightMetadataRetriever");
+            p = new StagefrightMetadataRetriever;
+            break;
+#endif
         default:
             // TODO:
-            // support for STAGEFRIGHT_PLAYER and TEST_PLAYER
+            // support for TEST_PLAYER
             LOGE("player type %d is not supported",  playerType);
             break;
     }
@@ -138,12 +145,6 @@
         return UNKNOWN_ERROR;
     }
     player_type playerType = getPlayerType(url);
-#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT)
-    if (playerType == STAGEFRIGHT_PLAYER) {
-        // Stagefright doesn't support metadata in this branch yet.
-        playerType = PV_PLAYER;
-    }
-#endif
     LOGV("player type = %d", playerType);
     sp<MediaMetadataRetrieverBase> p = createRetriever(playerType);
     if (p == NULL) return NO_INIT;
@@ -182,12 +183,6 @@
     }
 
     player_type playerType = getPlayerType(fd, offset, length);
-#if !defined(NO_OPENCORE) && defined(BUILD_WITH_FULL_STAGEFRIGHT)
-    if (playerType == STAGEFRIGHT_PLAYER) {
-        // Stagefright doesn't support metadata in this branch yet.
-        playerType = PV_PLAYER;
-    }
-#endif
     LOGV("player type = %d", playerType);
     sp<MediaMetadataRetrieverBase> p = createRetriever(playerType);
     if (p == NULL) {
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
new file mode 100644
index 0000000..7a3aee8
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
@@ -0,0 +1,197 @@
+/*
+**
+** Copyright 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "StagefrightMetadataRetriever"
+#include <utils/Log.h>
+
+#include "StagefrightMetadataRetriever.h"
+
+#include <media/stagefright/CachingDataSource.h>
+#include <media/stagefright/ColorConverter.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MmapSource.h>
+#include <media/stagefright/OMXCodec.h>
+
+namespace android {
+
+StagefrightMetadataRetriever::StagefrightMetadataRetriever() {
+    LOGV("StagefrightMetadataRetriever()");
+
+    DataSource::RegisterDefaultSniffers();
+    CHECK_EQ(mClient.connect(), OK);
+}
+
+StagefrightMetadataRetriever::~StagefrightMetadataRetriever() {
+    LOGV("~StagefrightMetadataRetriever()");
+    mClient.disconnect();
+}
+
+status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {
+    LOGV("setDataSource(%s)", uri);
+
+    mExtractor = MediaExtractor::CreateFromURI(uri);
+
+    return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR;
+}
+
+status_t StagefrightMetadataRetriever::setDataSource(
+        int fd, int64_t offset, int64_t length) {
+    LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
+
+    mExtractor = MediaExtractor::Create(
+            new MmapSource(fd, offset, length));
+
+    return OK;
+}
+
+VideoFrame *StagefrightMetadataRetriever::captureFrame() {
+    LOGV("captureFrame");
+
+    if (mExtractor.get() == NULL) {
+        LOGV("no extractor.");
+        return NULL;
+    }
+
+    size_t n = mExtractor->countTracks();
+    size_t i;
+    for (i = 0; i < n; ++i) {
+        sp<MetaData> meta = mExtractor->getTrackMetaData(i);
+
+        const char *mime;
+        CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+        if (!strncasecmp(mime, "video/", 6)) {
+            break;
+        }
+    }
+
+    if (i == n) {
+        LOGV("no video track found.");
+        return NULL;
+    }
+
+    sp<MetaData> trackMeta = mExtractor->getTrackMetaData(
+            i, MediaExtractor::kIncludeExtensiveMetaData);
+
+    sp<MediaSource> source = mExtractor->getTrack(i);
+
+    if (source.get() == NULL) {
+        LOGV("unable to instantiate video track.");
+        return NULL;
+    }
+
+    sp<MetaData> meta = source->getFormat();
+
+    sp<MediaSource> decoder =
+        OMXCodec::Create(
+                mClient.interface(), meta, false, source,
+                NULL, OMXCodec::kPreferSoftwareCodecs);
+
+    if (decoder.get() == NULL) {
+        LOGV("unable to instantiate video decoder.");
+
+        return NULL;
+    }
+
+    decoder->start();
+
+    // Read one output buffer, ignore format change notifications
+    // and spurious empty buffers.
+
+    MediaSource::ReadOptions options;
+    int64_t thumbNailTime;
+    if (trackMeta->findInt64(kKeyThumbnailTime, &thumbNailTime)) {
+        options.setSeekTo(thumbNailTime);
+    }
+
+    MediaBuffer *buffer = NULL;
+    status_t err;
+    do {
+        if (buffer != NULL) {
+            buffer->release();
+            buffer = NULL;
+        }
+        err = decoder->read(&buffer, &options);
+        options.clearSeekTo();
+    } while (err == INFO_FORMAT_CHANGED
+             || (buffer != NULL && buffer->range_length() == 0));
+
+    if (err != OK) {
+        CHECK_EQ(buffer, NULL);
+
+        LOGV("decoding frame failed.");
+        decoder->stop();
+
+        return NULL;
+    }
+
+    LOGV("successfully decoded video frame.");
+
+    meta = decoder->getFormat();
+
+    int32_t width, height;
+    CHECK(meta->findInt32(kKeyWidth, &width));
+    CHECK(meta->findInt32(kKeyHeight, &height));
+
+    VideoFrame *frame = new VideoFrame;
+    frame->mWidth = width;
+    frame->mHeight = height;
+    frame->mDisplayWidth = width;
+    frame->mDisplayHeight = height;
+    frame->mSize = width * height * 2;
+    frame->mData = new uint8_t[frame->mSize];
+
+    int32_t srcFormat;
+    CHECK(meta->findInt32(kKeyColorFormat, &srcFormat));
+
+    ColorConverter converter(
+            (OMX_COLOR_FORMATTYPE)srcFormat, OMX_COLOR_Format16bitRGB565);
+    CHECK(converter.isValid());
+
+    converter.convert(
+            width, height,
+            (const uint8_t *)buffer->data() + buffer->range_offset(),
+            0,
+            frame->mData, width * 2);
+
+    buffer->release();
+    buffer = NULL;
+
+    decoder->stop();
+
+    return frame;
+}
+
+MediaAlbumArt *StagefrightMetadataRetriever::extractAlbumArt() {
+    LOGV("extractAlbumArt (extractor: %s)", mExtractor.get() != NULL ? "YES" : "NO");
+
+    return NULL;
+}
+
+const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
+    LOGV("extractMetadata %d (extractor: %s)",
+         keyCode, mExtractor.get() != NULL ? "YES" : "NO");
+
+    return NULL;
+}
+
+}  // namespace android
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.h b/media/libmediaplayerservice/StagefrightMetadataRetriever.h
new file mode 100644
index 0000000..16127d7
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.h
@@ -0,0 +1,53 @@
+/*
+**
+** Copyright 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.
+*/
+
+#ifndef STAGEFRIGHT_METADATA_RETRIEVER_H_
+
+#define STAGEFRIGHT_METADATA_RETRIEVER_H_
+
+#include <media/MediaMetadataRetrieverInterface.h>
+
+#include <media/stagefright/OMXClient.h>
+
+namespace android {
+
+class MediaExtractor;
+
+struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface {
+    StagefrightMetadataRetriever();
+    virtual ~StagefrightMetadataRetriever();
+
+    virtual status_t setDataSource(const char *url);
+    virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+
+    virtual VideoFrame *captureFrame();
+    virtual MediaAlbumArt *extractAlbumArt();
+    virtual const char *extractMetadata(int keyCode);
+
+private:
+    OMXClient mClient;
+    sp<MediaExtractor> mExtractor;
+
+    StagefrightMetadataRetriever(const StagefrightMetadataRetriever &);
+
+    StagefrightMetadataRetriever &operator=(
+            const StagefrightMetadataRetriever &);
+};
+
+}  // namespace android
+
+#endif  // STAGEFRIGHT_METADATA_RETRIEVER_H_
diff --git a/media/libmediaplayerservice/TestPlayerStub.cpp b/media/libmediaplayerservice/TestPlayerStub.cpp
index 8627708..aa49429 100644
--- a/media/libmediaplayerservice/TestPlayerStub.cpp
+++ b/media/libmediaplayerservice/TestPlayerStub.cpp
@@ -176,7 +176,7 @@
     mContentUrl = NULL;
 
     if (mPlayer) {
-        LOG_ASSERT(mDeletePlayer != NULL);
+        LOG_ASSERT(mDeletePlayer != NULL, "mDeletePlayer is null");
         (*mDeletePlayer)(mPlayer);
         mPlayer = NULL;
     }
diff --git a/media/libstagefright/AMRExtractor.cpp b/media/libstagefright/AMRExtractor.cpp
index 8d85ce2..1e3c5a4 100644
--- a/media/libstagefright/AMRExtractor.cpp
+++ b/media/libstagefright/AMRExtractor.cpp
@@ -18,7 +18,8 @@
 #define LOG_TAG "AMRExtractor"
 #include <utils/Log.h>
 
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaBufferGroup.h>
 #include <media/stagefright/MediaDebug.h>
@@ -86,7 +87,7 @@
     return new AMRSource(mDataSource, mIsWide);
 }
 
-sp<MetaData> AMRExtractor::getTrackMetaData(size_t index) {
+sp<MetaData> AMRExtractor::getTrackMetaData(size_t index, uint32_t flags) {
     if (mInitCheck != OK || index != 0) {
         return NULL;
     }
@@ -155,7 +156,7 @@
     *out = NULL;
 
     uint8_t header;
-    ssize_t n = mDataSource->read_at(mOffset, &header, 1);
+    ssize_t n = mDataSource->readAt(mOffset, &header, 1);
 
     if (n < 1) {
         return ERROR_IO;
@@ -191,7 +192,7 @@
     // Round up bits to bytes and add 1 for the header byte.
     frameSize = (frameSize + 7) / 8 + 1;
 
-    n = mDataSource->read_at(mOffset, buffer->data(), frameSize);
+    n = mDataSource->readAt(mOffset, buffer->data(), frameSize);
 
     if (n != (ssize_t)frameSize) {
         buffer->release();
@@ -201,10 +202,7 @@
     }
 
     buffer->set_range(0, frameSize);
-    buffer->meta_data()->setInt32(
-            kKeyTimeUnits, (mCurrentTimeUs + 500) / 1000);
-    buffer->meta_data()->setInt32(
-            kKeyTimeScale, 1000);
+    buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
 
     mOffset += frameSize;
     mCurrentTimeUs += 20000;  // Each frame is 20ms
@@ -220,7 +218,7 @@
         const sp<DataSource> &source, String8 *mimeType, float *confidence) {
     char header[9];
 
-    if (source->read_at(0, header, sizeof(header)) != sizeof(header)) {
+    if (source->readAt(0, header, sizeof(header)) != sizeof(header)) {
         return false;
     }
 
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 9f71dae..c36e769 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -16,24 +16,26 @@
 
 LOCAL_SRC_FILES +=                \
         AMRExtractor.cpp          \
+        AudioPlayer.cpp           \
         CachingDataSource.cpp     \
+        CameraSource.cpp          \
         DataSource.cpp            \
         FileSource.cpp            \
         HTTPDataSource.cpp        \
         HTTPStream.cpp            \
         JPEGSource.cpp            \
-        MediaExtractor.cpp        \
         MP3Extractor.cpp          \
         MPEG4Extractor.cpp        \
         MPEG4Writer.cpp           \
+        MediaExtractor.cpp        \
         MediaPlayerImpl.cpp       \
         MmapSource.cpp            \
         SampleTable.cpp           \
         ShoutcastSource.cpp       \
         TimeSource.cpp            \
         TimedEventQueue.cpp       \
-        AudioPlayer.cpp           \
-        stagefright_string.cpp
+        WAVExtractor.cpp          \
+        string.cpp
 
 endif
 
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
index 538facb..d7e3f66 100644
--- a/media/libstagefright/AudioPlayer.cpp
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -210,15 +210,9 @@
                 break;
             }
 
-            int32_t units, scale;
-            bool success =
-                mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units);
-            success = success &&
-                mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale);
-            CHECK(success);
-
             Mutex::Autolock autoLock(mLock);
-            mPositionTimeMediaUs = (int64_t)units * 1000000 / scale;
+            CHECK(mInputBuffer->meta_data()->findInt64(
+                        kKeyTime, &mPositionTimeMediaUs));
 
             mPositionTimeRealUs =
                 ((mNumFramesPlayed + size_done / mFrameSize) * 1000000)
diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp
index fd00576..23f4897 100644
--- a/media/libstagefright/CachingDataSource.cpp
+++ b/media/libstagefright/CachingDataSource.cpp
@@ -61,11 +61,11 @@
     mData = NULL;
 }
 
-status_t CachingDataSource::InitCheck() const {
-    return OK;
+status_t CachingDataSource::initCheck() const {
+    return mSource->initCheck();
 }
 
-ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) {
+ssize_t CachingDataSource::readAt(off_t offset, void *data, size_t size) {
     Mutex::Autolock autoLock(mLock);
 
     size_t total = 0;
@@ -82,7 +82,7 @@
         if (page == NULL) {
             page = allocate_page();
             page->mOffset = offset - offset % mPageSize;
-            ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize);
+            ssize_t n = mSource->readAt(page->mOffset, page->mData, mPageSize);
             if (n < 0) {
                 page->mLength = 0;
             } else {
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 596ab67..e9d8557 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -21,120 +21,142 @@
 #include <binder/IServiceManager.h>
 #include <media/stagefright/CameraSource.h>
 #include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
-#include <ui/ICameraClient.h>
-#include <ui/ICameraService.h>
+#include <ui/Camera.h>
+#include <ui/CameraParameters.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/ISurface.h>
 #include <ui/Overlay.h>
-#include <utils/String16.h>
+#include <utils/String8.h>
 
 namespace android {
 
-class CameraBuffer : public MediaBuffer {
-public:
-    CameraBuffer(const sp<IMemory> &frame)
-        : MediaBuffer(frame->pointer(), frame->size()),
-          mFrame(frame) {
-    }
+static int64_t getNowUs() {
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
 
-    sp<IMemory> releaseFrame() {
-        sp<IMemory> frame = mFrame;
-        mFrame.clear();
-        return frame;
-    }
+    return (int64_t)tv.tv_usec + tv.tv_sec * 1000000;
+}
 
-private:
-    sp<IMemory> mFrame;
-};
-
-class CameraSourceClient : public BnCameraClient {
-public:
-    CameraSourceClient()
-        : mSource(NULL) {
-    }
-
-    virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
-        CHECK(mSource != NULL);
-        mSource->notifyCallback(msgType, ext1, ext2);
-    }
-
-    virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) {
-        CHECK(mSource != NULL);
-        mSource->dataCallback(msgType, data);
-    }
-
-    void setCameraSource(CameraSource *source) {
-        mSource = source;
-    }
-
-private:
-    CameraSource *mSource;
-};
-
-class DummySurface : public BnSurface {
-public:
+struct DummySurface : public BnSurface {
     DummySurface() {}
 
+    virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage) {
+        return NULL;
+    }
+
     virtual status_t registerBuffers(const BufferHeap &buffers) {
         return OK;
     }
 
-    virtual void postBuffer(ssize_t offset) {
-    }
+    virtual void postBuffer(ssize_t offset) {}
+    virtual void unregisterBuffers() {}
 
-    virtual void unregisterBuffers() {
-    }
-    
     virtual sp<OverlayRef> createOverlay(
             uint32_t w, uint32_t h, int32_t format) {
         return NULL;
     }
+
+protected:
+    virtual ~DummySurface() {}
+
+    DummySurface(const DummySurface &);
+    DummySurface &operator=(const DummySurface &);
 };
 
+struct CameraSourceListener : public CameraListener {
+    CameraSourceListener(const sp<CameraSource> &source);
+
+    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
+    virtual void postData(int32_t msgType, const sp<IMemory> &dataPtr);
+
+    virtual void postDataTimestamp(
+            nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
+
+protected:
+    virtual ~CameraSourceListener();
+
+private:
+    wp<CameraSource> mSource;
+
+    CameraSourceListener(const CameraSourceListener &);
+    CameraSourceListener &operator=(const CameraSourceListener &);
+};
+
+CameraSourceListener::CameraSourceListener(const sp<CameraSource> &source)
+    : mSource(source) {
+}
+
+CameraSourceListener::~CameraSourceListener() {
+}
+
+void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) {
+    LOGV("notify(%d, %d, %d)", msgType, ext1, ext2);
+}
+
+void CameraSourceListener::postData(int32_t msgType, const sp<IMemory> &dataPtr) {
+    LOGV("postData(%d, ptr:%p, size:%d)",
+         msgType, dataPtr->pointer(), dataPtr->size());
+
+    sp<CameraSource> source = mSource.promote();
+    if (source.get() != NULL) {
+        source->dataCallback(msgType, dataPtr);
+    }
+}
+
+void CameraSourceListener::postDataTimestamp(
+        nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
+    LOGV("postDataTimestamp(%lld, %d, ptr:%p, size:%d)",
+         timestamp, msgType, dataPtr->pointer(), dataPtr->size());
+}
+
 // static
 CameraSource *CameraSource::Create() {
-    sp<IServiceManager> sm = defaultServiceManager();
+    sp<Camera> camera = Camera::connect();
 
-    sp<ICameraService> service =
-        interface_cast<ICameraService>(
-                sm->getService(String16("media.camera")));
+    if (camera.get() == NULL) {
+        return NULL;
+    }
 
-    sp<CameraSourceClient> client = new CameraSourceClient;
-    sp<ICamera> camera = service->connect(client);
-
-    CameraSource *source = new CameraSource(camera, client);
-    client->setCameraSource(source);
-
-    return source;
+    return new CameraSource(camera);
 }
 
-CameraSource::CameraSource(
-        const sp<ICamera> &camera, const sp<ICameraClient> &client)
+CameraSource::CameraSource(const sp<Camera> &camera)
     : mCamera(camera),
-      mCameraClient(client),
+      mWidth(0),
+      mHeight(0),
+      mFirstFrameTimeUs(0),
       mNumFrames(0),
       mStarted(false) {
-    printf("params: \"%s\"\n", mCamera->getParameters().string());
+    String8 s = mCamera->getParameters();
+    printf("params: \"%s\"\n", s.string());
+
+    CameraParameters params(s);
+    params.getPreviewSize(&mWidth, &mHeight);
 }
 
 CameraSource::~CameraSource() {
     if (mStarted) {
         stop();
     }
-
-    mCamera->disconnect();
 }
 
 status_t CameraSource::start(MetaData *) {
     CHECK(!mStarted);
 
-    status_t err = mCamera->lock();
+    mCamera->setListener(new CameraSourceListener(this));
+
+    sp<ISurface> dummy = new DummySurface;
+    status_t err = mCamera->setPreviewDisplay(dummy);
     CHECK_EQ(err, OK);
 
-    err = mCamera->setPreviewDisplay(new DummySurface);
-    CHECK_EQ(err, OK);
-    mCamera->setPreviewCallbackFlag(1);
-    mCamera->startPreview();
+    mCamera->setPreviewCallbackFlags(
+            FRAME_CALLBACK_FLAG_ENABLE_MASK
+            | FRAME_CALLBACK_FLAG_COPY_OUT_MASK);
+
+    err = mCamera->startPreview();
     CHECK_EQ(err, OK);
 
     mStarted = true;
@@ -146,7 +168,6 @@
     CHECK(mStarted);
 
     mCamera->stopPreview();
-    mCamera->unlock();
 
     mStarted = false;
 
@@ -157,8 +178,8 @@
     sp<MetaData> meta = new MetaData;
     meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW);
     meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar);
-    meta->setInt32(kKeyWidth, 480);
-    meta->setInt32(kKeyHeight, 320);
+    meta->setInt32(kKeyWidth, mWidth);
+    meta->setInt32(kKeyHeight, mHeight);
 
     return meta;
 }
@@ -175,6 +196,7 @@
     }
 
     sp<IMemory> frame;
+    int64_t frameTime;
 
     {
         Mutex::Autolock autoLock(mLock);
@@ -184,41 +206,33 @@
 
         frame = *mFrames.begin();
         mFrames.erase(mFrames.begin());
+
+        frameTime = *mFrameTimes.begin();
+        mFrameTimes.erase(mFrameTimes.begin());
     }
 
-    int count = mNumFrames++;
-
-    *buffer = new CameraBuffer(frame);
+    *buffer = new MediaBuffer(frame->size());
+    memcpy((*buffer)->data(), frame->pointer(), frame->size());
+    (*buffer)->set_range(0, frame->size());
 
     (*buffer)->meta_data()->clear();
-    (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15);
-    (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count);
-
-    (*buffer)->add_ref();
-    (*buffer)->setObserver(this);
+    (*buffer)->meta_data()->setInt64(kKeyTime, frameTime);
 
     return OK;
 }
 
-void CameraSource::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
-    printf("notifyCallback %d, %d, %d\n", msgType, ext1, ext2);
-}
-
 void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) {
     Mutex::Autolock autoLock(mLock);
 
+    int64_t nowUs = getNowUs();
+    if (mNumFrames == 0) {
+        mFirstFrameTimeUs = nowUs;
+    }
+    ++mNumFrames;
+
     mFrames.push_back(data);
+    mFrameTimes.push_back(nowUs - mFirstFrameTimeUs);
     mFrameAvailableCondition.signal();
 }
 
-void CameraSource::signalBufferReturned(MediaBuffer *_buffer) {
-    CameraBuffer *buffer = static_cast<CameraBuffer *>(_buffer);
-
-    mCamera->releaseRecordingFrame(buffer->releaseFrame());
-
-    buffer->setObserver(NULL);
-    buffer->release();
-    buffer = NULL;
-}
-
 }  // namespace android
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
index daac539..2a6dbc4 100644
--- a/media/libstagefright/DataSource.cpp
+++ b/media/libstagefright/DataSource.cpp
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+#include "include/MP3Extractor.h"
+#include "include/MPEG4Extractor.h"
+#include "include/WAVExtractor.h"
+
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MP3Extractor.h>
-#include <media/stagefright/MPEG4Extractor.h>
 #include <utils/String8.h>
 
 namespace android {
@@ -27,7 +29,7 @@
     *x = 0;
 
     uint8_t byte[2];
-    if (read_at(offset, byte, 2) != 2) {
+    if (readAt(offset, byte, 2) != 2) {
         return false;
     }
 
@@ -86,6 +88,7 @@
     RegisterSniffer(SniffMP3);
     RegisterSniffer(SniffMPEG4);
     RegisterSniffer(SniffAMR);
+    RegisterSniffer(SniffWAV);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp
index 53b92a0..28d338c 100644
--- a/media/libstagefright/ESDS.cpp
+++ b/media/libstagefright/ESDS.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <media/stagefright/ESDS.h>
+#include "include/ESDS.h"
 
 #include <string.h>
 
diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp
index f6b90b2..f318ee3 100644
--- a/media/libstagefright/FileSource.cpp
+++ b/media/libstagefright/FileSource.cpp
@@ -30,11 +30,11 @@
     }
 }
 
-status_t FileSource::InitCheck() const {
+status_t FileSource::initCheck() const {
     return mFile != NULL ? OK : NO_INIT;
 }
 
-ssize_t FileSource::read_at(off_t offset, void *data, size_t size) {
+ssize_t FileSource::readAt(off_t offset, void *data, size_t size) {
     Mutex::Autolock autoLock(mLock);
 
     int err = fseeko(mFile, offset, SEEK_SET);
diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp
index 4dedebd..5536801 100644
--- a/media/libstagefright/HTTPDataSource.cpp
+++ b/media/libstagefright/HTTPDataSource.cpp
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
 #include <stdlib.h>
 
 #include <media/stagefright/HTTPDataSource.h>
-#include <media/stagefright/HTTPStream.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/stagefright_string.h>
 
 namespace android {
 
 HTTPDataSource::HTTPDataSource(const char *uri)
-    : mHost(NULL),
+    : mHttp(new HTTPStream),
+      mHost(NULL),
       mPort(0),
       mPath(NULL),
       mBuffer(malloc(kBufferSize)),
@@ -65,33 +67,40 @@
     mPort = port;
     mPath = strdup(path.c_str());
 
-    status_t err = mHttp.connect(mHost, mPort);
-    CHECK_EQ(err, OK);
+    mInitCheck = mHttp->connect(mHost, mPort);
 }
 
 HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path)
-    : mHost(strdup(host)),
+    : mHttp(new HTTPStream),
+      mHost(strdup(host)),
       mPort(port),
       mPath(strdup(path)),
       mBuffer(malloc(kBufferSize)),
       mBufferLength(0),
       mBufferOffset(0) {
-    status_t err = mHttp.connect(mHost, mPort);
-    CHECK_EQ(err, OK);
+    mInitCheck = mHttp->connect(mHost, mPort);
+}
+
+status_t HTTPDataSource::initCheck() const {
+    return mInitCheck;
 }
 
 HTTPDataSource::~HTTPDataSource() {
-    mHttp.disconnect();
+    mHttp->disconnect();
 
     free(mBuffer);
     mBuffer = NULL;
 
     free(mPath);
     mPath = NULL;
+
+    delete mHttp;
+    mHttp = NULL;
 }
 
-ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) {
-    if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) {
+ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
+    if (offset >= mBufferOffset
+            && offset < (off_t)(mBufferOffset + mBufferLength)) {
         size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
 
         size_t copy = num_bytes_available;
@@ -119,19 +128,19 @@
     status_t err;
     int attempt = 1;
     for (;;) {
-        if ((err = mHttp.send("GET ")) != OK
-            || (err = mHttp.send(mPath)) != OK
-            || (err = mHttp.send(" HTTP/1.1\r\n")) != OK
-            || (err = mHttp.send(host)) != OK
-            || (err = mHttp.send(range)) != OK
-            || (err = mHttp.send("\r\n")) != OK
-            || (err = mHttp.receive_header(&http_status)) != OK) {
+        if ((err = mHttp->send("GET ")) != OK
+            || (err = mHttp->send(mPath)) != OK
+            || (err = mHttp->send(" HTTP/1.1\r\n")) != OK
+            || (err = mHttp->send(host)) != OK
+            || (err = mHttp->send(range)) != OK
+            || (err = mHttp->send("\r\n")) != OK
+            || (err = mHttp->receive_header(&http_status)) != OK) {
 
             if (attempt == 3) {
                 return err;
             }
 
-            mHttp.connect(mHost, mPort);
+            mHttp->connect(mHost, mPort);
             ++attempt;
         } else {
             break;
@@ -143,14 +152,14 @@
     }
 
     string value;
-    if (!mHttp.find_header_value("Content-Length", &value)) {
+    if (!mHttp->find_header_value("Content-Length", &value)) {
         return UNKNOWN_ERROR;
     }
 
     char *end;
     unsigned long contentLength = strtoul(value.c_str(), &end, 10);
 
-    ssize_t num_bytes_received = mHttp.receive(mBuffer, contentLength);
+    ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);
 
     if (num_bytes_received <= 0) {
         return num_bytes_received;
diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp
index 6af7df9..02f9439 100644
--- a/media/libstagefright/HTTPStream.cpp
+++ b/media/libstagefright/HTTPStream.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "include/HTTPStream.h"
+
 #include <sys/socket.h>
 
 #include <arpa/inet.h>
@@ -25,7 +27,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <media/stagefright/HTTPStream.h>
 #include <media/stagefright/MediaDebug.h>
 
 namespace android {
diff --git a/media/libstagefright/JPEGSource.cpp b/media/libstagefright/JPEGSource.cpp
index d1dfd83..a4be2dd 100644
--- a/media/libstagefright/JPEGSource.cpp
+++ b/media/libstagefright/JPEGSource.cpp
@@ -119,7 +119,7 @@
     MediaBuffer *buffer;
     mGroup->acquire_buffer(&buffer);
 
-    ssize_t n = mSource->read_at(mOffset, buffer->data(), mSize - mOffset);
+    ssize_t n = mSource->readAt(mOffset, buffer->data(), mSize - mOffset);
 
     if (n <= 0) {
         buffer->release();
@@ -156,13 +156,13 @@
 
     for (;;) {
         uint8_t marker;
-        if (mSource->read_at(i++, &marker, 1) != 1) {
+        if (mSource->readAt(i++, &marker, 1) != 1) {
             return ERROR_IO;
         }
 
         CHECK_EQ(marker, 0xff);
 
-        if (mSource->read_at(i++, &marker, 1) != 1) {
+        if (mSource->readAt(i++, &marker, 1) != 1) {
             return ERROR_IO;
         }
 
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 7fd699f..8dd8ea9 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -18,8 +18,9 @@
 #define LOG_TAG "MP3Extractor"
 #include <utils/Log.h>
 
+#include "include/MP3Extractor.h"
+
 #include <media/stagefright/DataSource.h>
-#include <media/stagefright/MP3Extractor.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaBufferGroup.h>
 #include <media/stagefright/MediaDebug.h>
@@ -147,7 +148,12 @@
             *out_bitrate = bitrate;
         }
 
-        *frame_size = 144000 * bitrate / sampling_rate + padding;
+        if (version == 3 /* V1 */) {
+            *frame_size = 144000 * bitrate / sampling_rate + padding;
+        } else {
+            // V2 or V2.5
+            *frame_size = 72000 * bitrate / sampling_rate + padding;
+        }
     }
 
     if (out_sampling_rate) {
@@ -166,6 +172,33 @@
 static bool Resync(
         const sp<DataSource> &source, uint32_t match_header,
         off_t *inout_pos, uint32_t *out_header) {
+    if (*inout_pos == 0) {
+        // Skip an optional ID3 header if syncing at the very beginning
+        // of the datasource.
+
+        uint8_t id3header[10];
+        if (source->readAt(0, id3header, sizeof(id3header))
+                < (ssize_t)sizeof(id3header)) {
+            // If we can't even read these 10 bytes, we might as well bail out,
+            // even if there _were_ 10 bytes of valid mp3 audio data...
+            return false;
+        }
+
+        if (id3header[0] == 'I' && id3header[1] == 'D' && id3header[2] == '3') {
+            // Skip the ID3v2 header.
+
+            size_t len =
+                ((id3header[6] & 0x7f) << 21)
+                | ((id3header[7] & 0x7f) << 14)
+                | ((id3header[8] & 0x7f) << 7)
+                | (id3header[9] & 0x7f);
+
+            len += 10;
+
+            *inout_pos += len;
+        }
+    }
+
     // Everything must match except for
     // protection, bitrate, padding, private bits and mode extension.
     const uint32_t kMask = 0xfffe0ccf;
@@ -195,7 +228,7 @@
             buffer_length = buffer_length - buffer_offset;
             buffer_offset = 0;
 
-            ssize_t n = source->read_at(
+            ssize_t n = source->readAt(
                     pos, &buffer[buffer_length], kMaxFrameSize - buffer_length);
 
             if (n <= 0) {
@@ -232,7 +265,7 @@
         valid = true;
         for (int j = 0; j < 3; ++j) {
             uint8_t tmp[4];
-            if (source->read_at(test_pos, tmp, 4) < 4) {
+            if (source->readAt(test_pos, tmp, 4) < 4) {
                 valid = false;
                 break;
             }
@@ -338,10 +371,9 @@
 
         off_t fileSize;
         if (mDataSource->getSize(&fileSize) == OK) {
-            mMeta->setInt32(
+            mMeta->setInt64(
                     kKeyDuration,
-                    8 * (fileSize - mFirstFramePos) / bitrate);
-            mMeta->setInt32(kKeyTimeScale, 1000);
+                    8000LL * (fileSize - mFirstFramePos) / bitrate);
         }
     }
 }
@@ -362,7 +394,7 @@
             mMeta, mDataSource, mFirstFramePos, mFixedHeader);
 }
 
-sp<MetaData> MP3Extractor::getTrackMetaData(size_t index) {
+sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) {
     if (mFirstFramePos < 0 || index != 0) {
         return NULL;
     }
@@ -448,7 +480,7 @@
 
     size_t frame_size;
     for (;;) {
-        ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4);
+        ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), 4);
         if (n < 4) {
             buffer->release();
             buffer = NULL;
@@ -482,7 +514,7 @@
 
     CHECK(frame_size <= buffer->size());
 
-    ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size);
+    ssize_t n = mDataSource->readAt(mCurrentPos, buffer->data(), frame_size);
     if (n < (ssize_t)frame_size) {
         buffer->release();
         buffer = NULL;
@@ -492,8 +524,7 @@
 
     buffer->set_range(0, frame_size);
 
-    buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000);
-    buffer->meta_data()->setInt32(kKeyTimeScale, 1000);
+    buffer->meta_data()->setInt64(kKeyTime, mCurrentTimeUs);
 
     mCurrentPos += frame_size;
     mCurrentTimeUs += 1152 * 1000000 / 44100;
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 9174d19..9d17064 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -17,6 +17,9 @@
 #define LOG_TAG "MPEG4Extractor"
 #include <utils/Log.h>
 
+#include "include/MPEG4Extractor.h"
+#include "include/SampleTable.h"
+
 #include <arpa/inet.h>
 
 #include <ctype.h>
@@ -25,14 +28,12 @@
 #include <string.h>
 
 #include <media/stagefright/DataSource.h>
-#include <media/stagefright/MPEG4Extractor.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaBufferGroup.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaSource.h>
 #include <media/stagefright/MetaData.h>
-#include <media/stagefright/SampleTable.h>
 #include <media/stagefright/Utils.h>
 #include <utils/String8.h>
 
@@ -43,6 +44,7 @@
     // Caller retains ownership of both "dataSource" and "sampleTable".
     MPEG4Source(const sp<MetaData> &format,
                 const sp<DataSource> &dataSource,
+                int32_t timeScale,
                 const sp<SampleTable> &sampleTable);
 
     virtual status_t start(MetaData *params = NULL);
@@ -177,7 +179,8 @@
     return n;
 }
 
-sp<MetaData> MPEG4Extractor::getTrackMetaData(size_t index) {
+sp<MetaData> MPEG4Extractor::getTrackMetaData(
+        size_t index, uint32_t flags) {
     status_t err;
     if ((err = readMetaData()) != OK) {
         return NULL;
@@ -197,6 +200,25 @@
         return NULL;
     }
 
+    if ((flags & kIncludeExtensiveMetaData)
+            && !track->includes_expensive_metadata) {
+        track->includes_expensive_metadata = true;
+
+        const char *mime;
+        CHECK(track->meta->findCString(kKeyMIMEType, &mime));
+        if (!strncasecmp("video/", mime, 6)) {
+            uint32_t sampleIndex;
+            uint32_t sampleTime;
+            if (track->sampleTable->findThumbnailSample(&sampleIndex) == OK
+                    && track->sampleTable->getDecodingTime(
+                        sampleIndex, &sampleTime) == OK) {
+                track->meta->setInt64(
+                        kKeyThumbnailTime,
+                        ((int64_t)sampleTime * 1000000) / track->timescale);
+            }
+        }
+    }
+
     return track->meta;
 }
 
@@ -227,7 +249,7 @@
 
 status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
     uint32_t hdr[2];
-    if (mDataSource->read_at(*offset, hdr, 8) < 8) {
+    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
         return ERROR_IO;
     }
     uint64_t chunk_size = ntohl(hdr[0]);
@@ -235,7 +257,7 @@
     off_t data_offset = *offset + 8;
 
     if (chunk_size == 1) {
-        if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) {
+        if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
             return ERROR_IO;
         }
         chunk_size = ntoh64(chunk_size);
@@ -252,7 +274,7 @@
 
     char buffer[256];
     if (chunk_size <= sizeof(buffer)) {
-        if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) {
+        if (mDataSource->readAt(*offset, buffer, chunk_size) < chunk_size) {
             return ERROR_IO;
         }
 
@@ -298,7 +320,7 @@
             CHECK(chunk_data_size >= 4);
 
             uint8_t version;
-            if (mDataSource->read_at(data_offset, &version, 1) < 1) {
+            if (mDataSource->readAt(data_offset, &version, 1) < 1) {
                 return ERROR_IO;
             }
 
@@ -312,7 +334,7 @@
                 }
 
                 uint8_t buffer[36 + 60];
-                if (mDataSource->read_at(
+                if (mDataSource->readAt(
                             data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
                     return ERROR_IO;
                 }
@@ -329,7 +351,7 @@
                 }
 
                 uint8_t buffer[24 + 60];
-                if (mDataSource->read_at(
+                if (mDataSource->readAt(
                             data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
                     return ERROR_IO;
                 }
@@ -351,6 +373,7 @@
             mLastTrack = track;
 
             track->meta = new MetaData;
+            track->includes_expensive_metadata = false;
             track->timescale = 0;
             track->sampleTable = new SampleTable(mDataSource);
             track->meta->setCString(kKeyMIMEType, "application/octet-stream");
@@ -366,7 +389,7 @@
             }
 
             uint8_t version;
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, &version, sizeof(version))
                     < (ssize_t)sizeof(version)) {
                 return ERROR_IO;
@@ -383,18 +406,17 @@
             }
 
             uint32_t timescale;
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         timescale_offset, &timescale, sizeof(timescale))
                     < (ssize_t)sizeof(timescale)) {
                 return ERROR_IO;
             }
 
             mLastTrack->timescale = ntohl(timescale);
-            mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale);
 
             int64_t duration;
             if (version == 1) {
-                if (mDataSource->read_at(
+                if (mDataSource->readAt(
                             timescale_offset + 4, &duration, sizeof(duration))
                         < (ssize_t)sizeof(duration)) {
                     return ERROR_IO;
@@ -402,14 +424,15 @@
                 duration = ntoh64(duration);
             } else {
                 int32_t duration32;
-                if (mDataSource->read_at(
+                if (mDataSource->readAt(
                             timescale_offset + 4, &duration32, sizeof(duration32))
                         < (ssize_t)sizeof(duration32)) {
                     return ERROR_IO;
                 }
                 duration = ntohl(duration32);
             }
-            mLastTrack->meta->setInt32(kKeyDuration, duration);
+            mLastTrack->meta->setInt64(
+                    kKeyDuration, (duration * 1000000) / mLastTrack->timescale);
 
             *offset += chunk_size;
             break;
@@ -422,7 +445,7 @@
             }
 
             uint8_t buffer[24];
-            if (mDataSource->read_at(data_offset, buffer, 24) < 24) {
+            if (mDataSource->readAt(data_offset, buffer, 24) < 24) {
                 return ERROR_IO;
             }
 
@@ -449,7 +472,7 @@
 
             uint8_t buffer[8];
             CHECK(chunk_data_size >= (off_t)sizeof(buffer));
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, buffer, 8) < 8) {
                 return ERROR_IO;
             }
@@ -492,7 +515,7 @@
                 return ERROR_MALFORMED;
             }
 
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
                 return ERROR_IO;
             }
@@ -544,7 +567,7 @@
                 return ERROR_MALFORMED;
             }
 
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
                 return ERROR_IO;
             }
@@ -655,7 +678,7 @@
                 return ERROR_BUFFER_TOO_SMALL;
             }
 
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, buffer, chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
@@ -679,7 +702,7 @@
                 return ERROR_BUFFER_TOO_SMALL;
             }
 
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         data_offset, buffer, chunk_data_size) < chunk_data_size) {
                 return ERROR_IO;
             }
@@ -722,7 +745,7 @@
     }
 
     return new MPEG4Source(
-            track->meta, mDataSource, track->sampleTable);
+            track->meta, mDataSource, track->timescale, track->sampleTable);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -730,10 +753,11 @@
 MPEG4Source::MPEG4Source(
         const sp<MetaData> &format,
         const sp<DataSource> &dataSource,
+        int32_t timeScale,
         const sp<SampleTable> &sampleTable)
     : mFormat(format),
       mDataSource(dataSource),
-      mTimescale(0),
+      mTimescale(timeScale),
       mSampleTable(sampleTable),
       mCurrentSampleIndex(0),
       mIsAVC(false),
@@ -746,9 +770,6 @@
     bool success = mFormat->findCString(kKeyMIMEType, &mime);
     CHECK(success);
 
-    success = mFormat->findInt32(kKeyTimeScale, &mTimescale);
-    CHECK(success);
-
     mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
 }
 
@@ -868,7 +889,7 @@
     if (!mIsAVC || mWantsNALFragments) {
         if (newBuffer) {
             ssize_t num_bytes_read =
-                mDataSource->read_at(offset, (uint8_t *)mBuffer->data(), size);
+                mDataSource->readAt(offset, (uint8_t *)mBuffer->data(), size);
 
             if (num_bytes_read < (ssize_t)size) {
                 mBuffer->release();
@@ -879,8 +900,8 @@
 
             mBuffer->set_range(0, size);
             mBuffer->meta_data()->clear();
-            mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts);
-            mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale);
+            mBuffer->meta_data()->setInt64(
+                    kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
             ++mCurrentSampleIndex;
         }
 
@@ -923,7 +944,7 @@
         // the start code (0x00 00 00 01).
 
         ssize_t num_bytes_read =
-            mDataSource->read_at(offset, mSrcBuffer, size);
+            mDataSource->readAt(offset, mSrcBuffer, size);
 
         if (num_bytes_read < (ssize_t)size) {
             mBuffer->release();
@@ -959,8 +980,8 @@
 
         mBuffer->set_range(0, dstOffset);
         mBuffer->meta_data()->clear();
-        mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts);
-        mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale);
+        mBuffer->meta_data()->setInt64(
+                kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
         ++mCurrentSampleIndex;
 
         *out = mBuffer;
@@ -974,13 +995,14 @@
         const sp<DataSource> &source, String8 *mimeType, float *confidence) {
     uint8_t header[8];
 
-    ssize_t n = source->read_at(4, header, sizeof(header));
+    ssize_t n = source->readAt(4, header, sizeof(header));
     if (n < (ssize_t)sizeof(header)) {
         return false;
     }
 
     if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8)
-        || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)) {
+        || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)
+        || !memcmp(header, "ftypM4A ", 8)) {
         *mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
         *confidence = 0.1;
 
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index fa35768..9a7a873 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -399,15 +399,11 @@
         info.size = buffer->range_length();
         info.offset = offset;
 
-        int32_t units, scale;
-        bool success =
-            buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
-        CHECK(success);
-        success =
-            buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
-        CHECK(success);
+        int64_t timestampUs;
+        CHECK(buffer->meta_data()->findInt64(kKeyTime, &timestampUs));
 
-        info.timestamp = (int64_t)units * 1000 / scale;
+        // Our timestamp is in ms.
+        info.timestamp = (timestampUs + 500) / 1000;
 
         mSampleInfos.push_back(info);
 
diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp
index f3c0e73..b973745 100644
--- a/media/libstagefright/MediaBuffer.cpp
+++ b/media/libstagefright/MediaBuffer.cpp
@@ -108,10 +108,10 @@
 }
 
 void MediaBuffer::set_range(size_t offset, size_t length) {
-    if (offset < 0 || offset + length > mSize) {
+    if (offset + length > mSize) {
         LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize);
     }
-    CHECK(offset >= 0 && offset + length <= mSize);
+    CHECK(offset + length <= mSize);
 
     mRangeOffset = offset;
     mRangeLength = length;
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 87b5b24..04b1454 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -32,5 +32,6 @@
 const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw";
 
 const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
+const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
 
 }  // namespace android
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index 8535f52..19a1f85 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -18,12 +18,17 @@
 #define LOG_TAG "MediaExtractor"
 #include <utils/Log.h>
 
-#include <media/stagefright/AMRExtractor.h>
+#include "include/AMRExtractor.h"
+#include "include/MP3Extractor.h"
+#include "include/MPEG4Extractor.h"
+#include "include/WAVExtractor.h"
+
+#include <media/stagefright/CachingDataSource.h>
 #include <media/stagefright/DataSource.h>
+#include <media/stagefright/HTTPDataSource.h>
 #include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MP3Extractor.h>
-#include <media/stagefright/MPEG4Extractor.h>
 #include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MmapSource.h>
 #include <utils/String8.h>
 
 namespace android {
@@ -41,7 +46,7 @@
         }
 
         mime = tmp.string();
-        LOGI("Autodetected media content as '%s' with confidence %.2f",
+        LOGV("Autodetected media content as '%s' with confidence %.2f",
              mime, confidence);
     }
 
@@ -53,9 +58,32 @@
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
             || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
         return new AMRExtractor(source);
+    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
+        return new WAVExtractor(source);
     }
 
     return NULL;
 }
 
+// static
+sp<MediaExtractor> MediaExtractor::CreateFromURI(
+        const char *uri, const char *mime) {
+    sp<DataSource> source;
+    if (!strncasecmp("file://", uri, 7)) {
+        source = new MmapSource(uri + 7);
+    } else if (!strncasecmp("http://", uri, 7)) {
+        source = new HTTPDataSource(uri);
+        source = new CachingDataSource(source, 64 * 1024, 10);
+    } else {
+        // Assume it's a filename.
+        source = new MmapSource(uri);
+    }
+
+    if (source == NULL || source->initCheck() != OK) {
+        return NULL;
+    }
+
+    return Create(source, mime);
+}
+
 }  // namespace android
diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp
index 622ea7e..eb9fe7f 100644
--- a/media/libstagefright/MediaPlayerImpl.cpp
+++ b/media/libstagefright/MediaPlayerImpl.cpp
@@ -18,16 +18,17 @@
 #define LOG_TAG "MediaPlayerImpl"
 #include "utils/Log.h"
 
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
 #include <OMX_Component.h>
 
 #include <unistd.h>
 
 #include <media/stagefright/AudioPlayer.h>
-#include <media/stagefright/CachingDataSource.h>
 // #include <media/stagefright/CameraSource.h>
-#include <media/stagefright/HTTPDataSource.h>
-#include <media/stagefright/HTTPStream.h>
 #include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaExtractor.h>
 #include <media/stagefright/MediaPlayerImpl.h>
 #include <media/stagefright/MetaData.h>
@@ -69,18 +70,7 @@
         mVideoDecoder = CameraSource::Create();
 #endif
     } else {
-        sp<DataSource> source;
-        if (!strncasecmp("file://", uri, 7)) {
-            source = new MmapSource(uri + 7);
-        } else if (!strncasecmp("http://", uri, 7)) {
-            source = new HTTPDataSource(uri);
-            source = new CachingDataSource(source, 64 * 1024, 10);
-        } else {
-            // Assume it's a filename.
-            source = new MmapSource(uri);
-        }
-
-        mExtractor = MediaExtractor::Create(source);
+        mExtractor = MediaExtractor::CreateFromURI(uri);
 
         if (mExtractor == NULL) {
             return;
@@ -258,6 +248,13 @@
         status_t err = mVideoDecoder->read(&buffer, &options);
         CHECK((err == OK && buffer != NULL) || (err != OK && buffer == NULL));
 
+        if (err == INFO_FORMAT_CHANGED) {
+            LOGI("format changed.");
+            depopulateISurface();
+            populateISurface();
+            continue;
+        }
+
         if (err == ERROR_END_OF_STREAM || err != OK) {
             eof = true;
             continue;
@@ -269,15 +266,9 @@
             continue;
         }
 
-        int32_t units, scale;
-        bool success =
-            buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
-        CHECK(success);
-        success =
-            buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
-        CHECK(success);
+        int64_t pts_us;
+        CHECK(buffer->meta_data()->findInt64(kKeyTime, &pts_us));
 
-        int64_t pts_us = (int64_t)units * 1000000 / scale;
         {
             Mutex::Autolock autoLock(mLock);
             mVideoPosition = pts_us;
@@ -391,12 +382,10 @@
 
             sp<MediaSource> source = mExtractor->getTrack(i);
 
-            int32_t units, scale;
-            if (meta->findInt32(kKeyDuration, &units)
-                && meta->findInt32(kKeyTimeScale, &scale)) {
-                int64_t duration_us = (int64_t)units * 1000000 / scale;
-                if (duration_us > mDuration) {
-                    mDuration = duration_us;
+            int64_t durationUs;
+            if (meta->findInt64(kKeyDuration, &durationUs)) {
+                if (durationUs > mDuration) {
+                    mDuration = durationUs;
                 }
             }
 
@@ -415,8 +404,15 @@
 
     sp<MetaData> meta = source->getFormat();
 
-    mAudioDecoder = OMXCodec::Create(
-            mClient.interface(), meta, false /* createEncoder */, source);
+    const char *mime;
+    CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+    if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) {
+        mAudioDecoder = source;
+    } else {
+        mAudioDecoder = OMXCodec::Create(
+                mClient.interface(), meta, false /* createEncoder */, source);
+    }
 }
 
 void MediaPlayerImpl::setVideoSource(const sp<MediaSource> &source) {
@@ -621,6 +617,9 @@
     success = success && meta->findInt32(kKeyHeight, &decodedHeight);
     CHECK(success);
 
+    LOGI("mVideoWidth=%d, mVideoHeight=%d, decodedWidth=%d, decodedHeight=%d",
+         mVideoWidth, mVideoHeight, decodedWidth, decodedHeight);
+
     if (mSurface.get() != NULL) {
         mVideoRenderer =
             mClient.interface()->createRenderer(
diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp
index 6b067cb..63b476e 100644
--- a/media/libstagefright/MetaData.cpp
+++ b/media/libstagefright/MetaData.cpp
@@ -58,6 +58,10 @@
     return setData(key, TYPE_INT32, &value, sizeof(value));
 }
 
+bool MetaData::setInt64(uint32_t key, int64_t value) {
+    return setData(key, TYPE_INT64, &value, sizeof(value));
+}
+
 bool MetaData::setFloat(uint32_t key, float value) {
     return setData(key, TYPE_FLOAT, &value, sizeof(value));
 }
@@ -94,6 +98,21 @@
     return true;
 }
 
+bool MetaData::findInt64(uint32_t key, int64_t *value) {
+    uint32_t type;
+    const void *data;
+    size_t size;
+    if (!findData(key, &type, &data, &size) || type != TYPE_INT64) {
+        return false;
+    }
+
+    CHECK_EQ(size, sizeof(*value));
+
+    *value = *(int64_t *)data;
+
+    return true;
+}
+
 bool MetaData::findFloat(uint32_t key, float *value) {
     uint32_t type;
     const void *data;
diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp
index 47d95f9..42749cf 100644
--- a/media/libstagefright/MmapSource.cpp
+++ b/media/libstagefright/MmapSource.cpp
@@ -34,7 +34,10 @@
       mBase(NULL),
       mSize(0) {
     LOGV("MmapSource '%s'", filename);
-    CHECK(mFd >= 0);
+
+    if (mFd < 0) {
+        return;
+    }
 
     off_t size = lseek(mFd, 0, SEEK_END);
     mSize = (size_t)size;
@@ -78,12 +81,12 @@
     }
 }
 
-status_t MmapSource::InitCheck() const {
+status_t MmapSource::initCheck() const {
     return mFd == -1 ? NO_INIT : OK;
 }
 
-ssize_t MmapSource::read_at(off_t offset, void *data, size_t size) {
-    LOGV("read_at offset:%ld data:%p size:%d", offset, data, size);
+ssize_t MmapSource::readAt(off_t offset, void *data, size_t size) {
+    LOGV("readAt offset:%ld data:%p size:%d", offset, data, size);
     CHECK(offset >= 0);
 
     size_t avail = 0;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index ebf1e0c..6c0367a 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -18,11 +18,12 @@
 #define LOG_TAG "OMXCodec"
 #include <utils/Log.h>
 
+#include "include/ESDS.h"
+
 #include <binder/IServiceManager.h>
 #include <binder/MemoryDealer.h>
 #include <binder/ProcessState.h>
 #include <media/IMediaPlayerService.h>
-#include <media/stagefright/ESDS.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaBufferGroup.h>
 #include <media/stagefright/MediaDebug.h>
@@ -39,6 +40,8 @@
 
 namespace android {
 
+static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
+
 struct CodecInfo {
     const char *mime;
     const char *codec;
@@ -169,52 +172,37 @@
     params->nVersion.s.nStep = 0;
 }
 
-// static
-sp<OMXCodec> OMXCodec::Create(
-        const sp<IOMX> &omx,
-        const sp<MetaData> &meta, bool createEncoder,
-        const sp<MediaSource> &source,
-        const char *matchComponentName) {
-    const char *mime;
-    bool success = meta->findCString(kKeyMIMEType, &mime);
-    CHECK(success);
-
-    const char *componentName = NULL;
-    sp<OMXCodecObserver> observer = new OMXCodecObserver;
-    IOMX::node_id node = 0;
-    for (int index = 0;; ++index) {
-        if (createEncoder) {
-            componentName = GetCodec(
-                    kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
-                    mime, index);
-        } else {
-            componentName = GetCodec(
-                    kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
-                    mime, index);
-        }
-
-        if (!componentName) {
-            return NULL;
-        }
-
-        // If a specific codec is requested, skip the non-matching ones.
-        if (matchComponentName && strcmp(componentName, matchComponentName)) {
-            continue;
-        }
-
-        LOGV("Attempting to allocate OMX node '%s'", componentName);
-
-        status_t err = omx->allocateNode(componentName, observer, &node);
-        if (err == OK) {
-            LOGI("Successfully allocated OMX node '%s'", componentName);
-            break;
-        }
+static bool IsSoftwareCodec(const char *componentName) {
+    if (!strncmp("OMX.PV.", componentName, 7)) {
+        return true;
     }
 
+    return false;
+}
+
+static int CompareSoftwareCodecsFirst(
+        const String8 *elem1, const String8 *elem2) {
+    bool isSoftwareCodec1 = IsSoftwareCodec(elem1->string());
+    bool isSoftwareCodec2 = IsSoftwareCodec(elem2->string());
+
+    if (isSoftwareCodec1) {
+        if (isSoftwareCodec2) { return 0; }
+        return -1;
+    }
+
+    if (isSoftwareCodec2) {
+        return 1;
+    }
+
+    return 0;
+}
+
+// static
+uint32_t OMXCodec::getComponentQuirks(const char *componentName) {
     uint32_t quirks = 0;
+
     if (!strcmp(componentName, "OMX.PV.avcdec")) {
         quirks |= kWantsNALFragments;
-        quirks |= kOutputDimensionsAre16Aligned;
     }
     if (!strcmp(componentName, "OMX.TI.MP3.decode")) {
         quirks |= kNeedsFlushBeforeDisable;
@@ -222,20 +210,15 @@
     if (!strcmp(componentName, "OMX.TI.AAC.decode")) {
         quirks |= kNeedsFlushBeforeDisable;
         quirks |= kRequiresFlushCompleteEmulation;
-
-        // The following is currently necessary for proper shutdown
-        // behaviour, but NOT enabled by default in order to make the
-        // bug reproducible...
-        // quirks |= kRequiresFlushBeforeShutdown;
     }
     if (!strncmp(componentName, "OMX.qcom.video.encoder.", 23)) {
         quirks |= kRequiresLoadedToIdleAfterAllocation;
         quirks |= kRequiresAllocateBufferOnInputPorts;
+        quirks |= kRequiresAllocateBufferOnOutputPorts;
     }
     if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) {
         // XXX Required on P....on only.
         quirks |= kRequiresAllocateBufferOnOutputPorts;
-        quirks |= kOutputDimensionsAre16Aligned;
     }
 
     if (!strncmp(componentName, "OMX.TI.", 7)) {
@@ -248,8 +231,94 @@
         quirks |= kRequiresAllocateBufferOnOutputPorts;
     }
 
+    return quirks;
+}
+
+// static
+void OMXCodec::findMatchingCodecs(
+        const char *mime,
+        bool createEncoder, const char *matchComponentName,
+        uint32_t flags,
+        Vector<String8> *matchingCodecs) {
+    matchingCodecs->clear();
+
+    for (int index = 0;; ++index) {
+        const char *componentName;
+
+        if (createEncoder) {
+            componentName = GetCodec(
+                    kEncoderInfo,
+                    sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
+                    mime, index);
+        } else {
+            componentName = GetCodec(
+                    kDecoderInfo,
+                    sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
+                    mime, index);
+        }
+
+        if (!componentName) {
+            break;
+        }
+
+        // If a specific codec is requested, skip the non-matching ones.
+        if (matchComponentName && strcmp(componentName, matchComponentName)) {
+            continue;
+        }
+
+        matchingCodecs->push(String8(componentName));
+    }
+
+    if (flags & kPreferSoftwareCodecs) {
+        matchingCodecs->sort(CompareSoftwareCodecsFirst);
+    }
+}
+
+// static
+sp<OMXCodec> OMXCodec::Create(
+        const sp<IOMX> &omx,
+        const sp<MetaData> &meta, bool createEncoder,
+        const sp<MediaSource> &source,
+        const char *matchComponentName,
+        uint32_t flags) {
+    const char *mime;
+    bool success = meta->findCString(kKeyMIMEType, &mime);
+    CHECK(success);
+
+    Vector<String8> matchingCodecs;
+    findMatchingCodecs(
+            mime, createEncoder, matchComponentName, flags, &matchingCodecs);
+
+    if (matchingCodecs.isEmpty()) {
+        return NULL;
+    }
+
+    sp<OMXCodecObserver> observer = new OMXCodecObserver;
+    IOMX::node_id node = 0;
+    success = false;
+
+    const char *componentName;
+    for (size_t i = 0; i < matchingCodecs.size(); ++i) {
+        componentName = matchingCodecs[i].string();
+
+        LOGV("Attempting to allocate OMX node '%s'", componentName);
+
+        status_t err = omx->allocateNode(componentName, observer, &node);
+        if (err == OK) {
+            LOGV("Successfully allocated OMX node '%s'", componentName);
+
+            success = true;
+            break;
+        }
+    }
+
+    if (!success) {
+        return NULL;
+    }
+
     sp<OMXCodec> codec = new OMXCodec(
-            omx, node, quirks, createEncoder, mime, componentName,
+            omx, node, getComponentQuirks(componentName),
+            createEncoder, mime, componentName,
             source);
 
     observer->setCodec(codec);
@@ -331,7 +400,7 @@
             size -= length;
         }
 
-        LOGI("AVC profile = %d (%s), level = %d",
+        LOGV("AVC profile = %d (%s), level = %d",
              (int)profile, AVCProfileToString(profile), (int)level / 10);
 
         if (!strcmp(componentName, "OMX.TI.Video.Decoder")
@@ -452,7 +521,7 @@
         // CHECK_EQ(format.nIndex, index);
 
 #if 1
-        CODEC_LOGI("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d",
+        CODEC_LOGV("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d",
              portIndex,
              index, format.eCompressionFormat, format.eColorFormat);
 #endif
@@ -493,9 +562,25 @@
     return err;
 }
 
+static size_t getFrameSize(
+        OMX_COLOR_FORMATTYPE colorFormat, int32_t width, int32_t height) {
+    switch (colorFormat) {
+        case OMX_COLOR_FormatYCbYCr:
+        case OMX_COLOR_FormatCbYCrY:
+            return width * height * 2;
+
+        case OMX_COLOR_FormatYUV420SemiPlanar:
+            return (width * height * 3) / 2;
+
+        default:
+            CHECK(!"Should not be here. Unsupported color format.");
+            break;
+    }
+}
+
 void OMXCodec::setVideoInputFormat(
         const char *mime, OMX_U32 width, OMX_U32 height) {
-    CODEC_LOGI("setVideoInputFormat width=%ld, height=%ld", width, height);
+    CODEC_LOGV("setVideoInputFormat width=%ld, height=%ld", width, height);
 
     OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
     if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
@@ -516,12 +601,13 @@
         colorFormat = OMX_COLOR_FormatYUV420SemiPlanar;
     }
 
-    setVideoPortFormatType(
+    CHECK_EQ(setVideoPortFormatType(
             kPortIndexInput, OMX_VIDEO_CodingUnused,
-            colorFormat);
+            colorFormat), OK);
 
-    setVideoPortFormatType(
-            kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused);
+    CHECK_EQ(setVideoPortFormatType(
+            kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused),
+            OK);
 
     OMX_PARAM_PORTDEFINITIONTYPE def;
     InitOMXParams(&def);
@@ -554,8 +640,8 @@
             mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
     CHECK_EQ(err, OK);
 
-    def.nBufferSize = (width * height * 2); // (width * height * 3) / 2;
-    CODEC_LOGI("Setting nBufferSize = %ld", def.nBufferSize);
+    def.nBufferSize = getFrameSize(colorFormat, width, height);
+    CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize);
 
     CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
 
@@ -564,14 +650,108 @@
     video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
     video_def->eColorFormat = colorFormat;
 
+    video_def->xFramerate = 24 << 16;  // XXX crucial!
+
     err = mOMX->setParameter(
             mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
     CHECK_EQ(err, OK);
+
+    switch (compressionFormat) {
+        case OMX_VIDEO_CodingMPEG4:
+        {
+            CHECK_EQ(setupMPEG4EncoderParameters(), OK);
+            break;
+        }
+
+        case OMX_VIDEO_CodingH263:
+            break;
+
+        default:
+            CHECK(!"Support for this compressionFormat to be implemented.");
+            break;
+    }
+}
+
+status_t OMXCodec::setupMPEG4EncoderParameters() {
+    OMX_VIDEO_PARAM_MPEG4TYPE mpeg4type;
+    InitOMXParams(&mpeg4type);
+    mpeg4type.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+    CHECK_EQ(err, OK);
+
+    mpeg4type.nSliceHeaderSpacing = 0;
+    mpeg4type.bSVH = OMX_FALSE;
+    mpeg4type.bGov = OMX_FALSE;
+
+    mpeg4type.nAllowedPictureTypes =
+        OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+    mpeg4type.nPFrames = 23;
+    mpeg4type.nBFrames = 0;
+
+    mpeg4type.nIDCVLCThreshold = 0;
+    mpeg4type.bACPred = OMX_TRUE;
+    mpeg4type.nMaxPacketSize = 256;
+    mpeg4type.nTimeIncRes = 1000;
+    mpeg4type.nHeaderExtension = 0;
+    mpeg4type.bReversibleVLC = OMX_FALSE;
+
+    mpeg4type.eProfile = OMX_VIDEO_MPEG4ProfileCore;
+    mpeg4type.eLevel = OMX_VIDEO_MPEG4Level2;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoMpeg4, &mpeg4type, sizeof(mpeg4type));
+    CHECK_EQ(err, OK);
+
+    // ----------------
+
+    OMX_VIDEO_PARAM_BITRATETYPE bitrateType;
+    InitOMXParams(&bitrateType);
+    bitrateType.nPortIndex = kPortIndexOutput;
+
+    err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoBitrate,
+            &bitrateType, sizeof(bitrateType));
+    CHECK_EQ(err, OK);
+
+    bitrateType.eControlRate = OMX_Video_ControlRateVariable;
+    bitrateType.nTargetBitrate = 1000000;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoBitrate,
+            &bitrateType, sizeof(bitrateType));
+    CHECK_EQ(err, OK);
+
+    // ----------------
+
+    OMX_VIDEO_PARAM_ERRORCORRECTIONTYPE errorCorrectionType;
+    InitOMXParams(&errorCorrectionType);
+    errorCorrectionType.nPortIndex = kPortIndexOutput;
+
+    err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoErrorCorrection,
+            &errorCorrectionType, sizeof(errorCorrectionType));
+    CHECK_EQ(err, OK);
+
+    errorCorrectionType.bEnableHEC = OMX_FALSE;
+    errorCorrectionType.bEnableResync = OMX_TRUE;
+    errorCorrectionType.nResynchMarkerSpacing = 256;
+    errorCorrectionType.bEnableDataPartitioning = OMX_FALSE;
+    errorCorrectionType.bEnableRVLC = OMX_FALSE;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoErrorCorrection,
+            &errorCorrectionType, sizeof(errorCorrectionType));
+    CHECK_EQ(err, OK);
+
+    return OK;
 }
 
 void OMXCodec::setVideoOutputFormat(
         const char *mime, OMX_U32 width, OMX_U32 height) {
-    CODEC_LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
+    CODEC_LOGV("setVideoOutputFormat width=%ld, height=%ld", width, height);
 
     OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
     if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
@@ -639,6 +819,7 @@
     video_def->nFrameWidth = width;
     video_def->nFrameHeight = height;
 
+    video_def->eCompressionFormat = compressionFormat;
     video_def->eColorFormat = OMX_COLOR_FormatUnused;
 
     err = mOMX->setParameter(
@@ -687,6 +868,7 @@
       mInitialBufferSubmit(true),
       mSignalledEOS(false),
       mNoMoreOutputData(false),
+      mOutputPortSettingsHaveChanged(false),
       mSeekTimeUs(-1) {
     mPortStatus[kPortIndexInput] = ENABLED;
     mPortStatus[kPortIndexOutput] = ENABLED;
@@ -990,12 +1172,8 @@
 
                 buffer->meta_data()->clear();
 
-                buffer->meta_data()->setInt32(
-                        kKeyTimeUnits,
-                        (msg.u.extended_buffer_data.timestamp + 500) / 1000);
-
-                buffer->meta_data()->setInt32(
-                        kKeyTimeScale, 1000);
+                buffer->meta_data()->setInt64(
+                        kKeyTime, msg.u.extended_buffer_data.timestamp);
 
                 if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) {
                     buffer->meta_data()->setInt32(kKeyIsSyncFrame, true);
@@ -1064,6 +1242,71 @@
     }
 }
 
+// Has the format changed in any way that the client would have to be aware of?
+static bool formatHasNotablyChanged(
+        const sp<MetaData> &from, const sp<MetaData> &to) {
+    if (from.get() == NULL && to.get() == NULL) {
+        return false;
+    }
+
+    if ((from.get() == NULL && to.get() != NULL)
+        || (from.get() != NULL && to.get() == NULL)) {
+        return true;
+    }
+
+    const char *mime_from, *mime_to;
+    CHECK(from->findCString(kKeyMIMEType, &mime_from));
+    CHECK(to->findCString(kKeyMIMEType, &mime_to));
+
+    if (strcasecmp(mime_from, mime_to)) {
+        return true;
+    }
+
+    if (!strcasecmp(mime_from, MEDIA_MIMETYPE_VIDEO_RAW)) {
+        int32_t colorFormat_from, colorFormat_to;
+        CHECK(from->findInt32(kKeyColorFormat, &colorFormat_from));
+        CHECK(to->findInt32(kKeyColorFormat, &colorFormat_to));
+
+        if (colorFormat_from != colorFormat_to) {
+            return true;
+        }
+
+        int32_t width_from, width_to;
+        CHECK(from->findInt32(kKeyWidth, &width_from));
+        CHECK(to->findInt32(kKeyWidth, &width_to));
+
+        if (width_from != width_to) {
+            return true;
+        }
+
+        int32_t height_from, height_to;
+        CHECK(from->findInt32(kKeyHeight, &height_from));
+        CHECK(to->findInt32(kKeyHeight, &height_to));
+
+        if (height_from != height_to) {
+            return true;
+        }
+    } else if (!strcasecmp(mime_from, MEDIA_MIMETYPE_AUDIO_RAW)) {
+        int32_t numChannels_from, numChannels_to;
+        CHECK(from->findInt32(kKeyChannelCount, &numChannels_from));
+        CHECK(to->findInt32(kKeyChannelCount, &numChannels_to));
+
+        if (numChannels_from != numChannels_to) {
+            return true;
+        }
+
+        int32_t sampleRate_from, sampleRate_to;
+        CHECK(from->findInt32(kKeySampleRate, &sampleRate_from));
+        CHECK(to->findInt32(kKeySampleRate, &sampleRate_to));
+
+        if (sampleRate_from != sampleRate_to) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) {
     switch (cmd) {
         case OMX_CommandStateSet:
@@ -1086,6 +1329,15 @@
             if (mState == RECONFIGURING) {
                 CHECK_EQ(portIndex, kPortIndexOutput);
 
+                sp<MetaData> oldOutputFormat = mOutputFormat;
+                initOutputFormat(mSource->getFormat());
+
+                // Don't notify clients if the output port settings change
+                // wasn't of importance to them, i.e. it may be that just the
+                // number of buffers has changed and nothing else.
+                mOutputPortSettingsHaveChanged =
+                    formatHasNotablyChanged(oldOutputFormat, mOutputFormat);
+
                 enablePortAsync(portIndex);
 
                 status_t err = allocateBuffersOnPort(portIndex);
@@ -1443,7 +1695,7 @@
     }
 
     OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
-    OMX_TICKS timestamp = 0;
+    OMX_TICKS timestampUs = 0;
     size_t srcLength = 0;
 
     if (err != OK) {
@@ -1463,15 +1715,11 @@
                (const uint8_t *)srcBuffer->data() + srcBuffer->range_offset(),
                srcLength);
 
-        int32_t units, scale;
-        if (srcBuffer->meta_data()->findInt32(kKeyTimeUnits, &units)
-            && srcBuffer->meta_data()->findInt32(kKeyTimeScale, &scale)) {
-            timestamp = ((OMX_TICKS)units * 1000000) / scale;
-
-            CODEC_LOGV("Calling empty_buffer on buffer %p (length %d)",
+        if (srcBuffer->meta_data()->findInt64(kKeyTime, &timestampUs)) {
+            CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d)",
                  info->mBuffer, srcLength);
-            CODEC_LOGV("Calling empty_buffer with timestamp %lld us (%.2f secs)",
-                 timestamp, timestamp / 1E6);
+            CODEC_LOGV("Calling emptyBuffer with timestamp %lld us (%.2f secs)",
+                 timestampUs, timestampUs / 1E6);
         }
     }
 
@@ -1482,7 +1730,7 @@
 
     err = mOMX->emptyBuffer(
             mNode, info->mBuffer, 0, srcLength,
-            flags, timestamp);
+            flags, timestampUs);
 
     if (err != OK) {
         setState(ERROR);
@@ -1794,6 +2042,7 @@
     mInitialBufferSubmit = true;
     mSignalledEOS = false;
     mNoMoreOutputData = false;
+    mOutputPortSettingsHaveChanged = false;
     mSeekTimeUs = -1;
     mFilledBuffers.clear();
 
@@ -1864,6 +2113,8 @@
 }
 
 sp<MetaData> OMXCodec::getFormat() {
+    Mutex::Autolock autoLock(mLock);
+
     return mOutputFormat;
 }
 
@@ -1941,6 +2192,12 @@
         return ERROR_END_OF_STREAM;
     }
 
+    if (mOutputPortSettingsHaveChanged) {
+        mOutputPortSettingsHaveChanged = false;
+
+        return INFO_FORMAT_CHANGED;
+    }
+
     size_t index = *mFilledBuffers.begin();
     mFilledBuffers.erase(mFilledBuffers.begin());
 
@@ -2041,8 +2298,6 @@
 
     size_t numNames = sizeof(kNames) / sizeof(kNames[0]);
 
-    static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
-
     if (type == OMX_QCOM_COLOR_FormatYVU420SemiPlanar) {
         return "OMX_QCOM_COLOR_FormatYVU420SemiPlanar";
     } else if (type < 0 || (size_t)type >= numNames) {
@@ -2410,7 +2665,7 @@
                 CHECK(!"Unknown compression format.");
             }
 
-            if (mQuirks & kOutputDimensionsAre16Aligned) {
+            if (!strcmp(mComponentName, "OMX.PV.avcdec")) {
                 // This component appears to be lying to me.
                 mOutputFormat->setInt32(
                         kKeyWidth, (video_def->nFrameWidth + 15) & -16);
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 8efa7c7..4aec0e9 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -17,11 +17,12 @@
 #define LOG_TAG "SampleTable"
 #include <utils/Log.h>
 
+#include "include/SampleTable.h"
+
 #include <arpa/inet.h>
 
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/SampleTable.h>
 #include <media/stagefright/Utils.h>
 
 namespace android {
@@ -54,7 +55,7 @@
 }
 
 status_t SampleTable::setChunkOffsetParams(
-        uint32_t type, off_t data_offset, off_t data_size) {
+        uint32_t type, off_t data_offset, size_t data_size) {
     if (mChunkOffsetOffset >= 0) {
         return ERROR_MALFORMED;
     }
@@ -69,7 +70,7 @@
     }
 
     uint8_t header[8];
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return ERROR_IO;
     }
@@ -95,7 +96,7 @@
 }
 
 status_t SampleTable::setSampleToChunkParams(
-        off_t data_offset, off_t data_size) {
+        off_t data_offset, size_t data_size) {
     if (mSampleToChunkOffset >= 0) {
         return ERROR_MALFORMED;
     }
@@ -107,7 +108,7 @@
     }
 
     uint8_t header[8];
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return ERROR_IO;
     }
@@ -127,7 +128,7 @@
 }
 
 status_t SampleTable::setSampleSizeParams(
-        uint32_t type, off_t data_offset, off_t data_size) {
+        uint32_t type, off_t data_offset, size_t data_size) {
     if (mSampleSizeOffset >= 0) {
         return ERROR_MALFORMED;
     }
@@ -141,7 +142,7 @@
     }
 
     uint8_t header[12];
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return ERROR_IO;
     }
@@ -187,13 +188,13 @@
 }
 
 status_t SampleTable::setTimeToSampleParams(
-        off_t data_offset, off_t data_size) {
+        off_t data_offset, size_t data_size) {
     if (mTimeToSample != NULL || data_size < 8) {
         return ERROR_MALFORMED;
     }
 
     uint8_t header[8];
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return ERROR_IO;
     }
@@ -207,7 +208,7 @@
     mTimeToSample = new uint32_t[mTimeToSampleCount * 2];
 
     size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2;
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset + 8, mTimeToSample, size) < (ssize_t)size) {
         return ERROR_IO;
     }
@@ -219,7 +220,7 @@
     return OK;
 }
 
-status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) {
+status_t SampleTable::setSyncSampleParams(off_t data_offset, size_t data_size) {
     if (mSyncSampleOffset >= 0 || data_size < 8) {
         return ERROR_MALFORMED;
     }
@@ -227,7 +228,7 @@
     mSyncSampleOffset = data_offset;
 
     uint8_t header[8];
-    if (mDataSource->read_at(
+    if (mDataSource->readAt(
                 data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
         return ERROR_IO;
     }
@@ -263,7 +264,7 @@
     if (mChunkOffsetType == kChunkOffsetType32) {
         uint32_t offset32;
 
-        if (mDataSource->read_at(
+        if (mDataSource->readAt(
                     mChunkOffsetOffset + 8 + 4 * chunk_index,
                     &offset32,
                     sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
@@ -275,7 +276,7 @@
         CHECK_EQ(mChunkOffsetType, kChunkOffsetType64);
 
         uint64_t offset64;
-        if (mDataSource->read_at(
+        if (mDataSource->readAt(
                     mChunkOffsetOffset + 8 + 8 * chunk_index,
                     &offset64,
                     sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
@@ -312,7 +313,7 @@
     uint32_t index = 0;
     while (index < mNumSampleToChunkOffsets) {
         uint8_t buffer[12];
-        if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12,
+        if (mDataSource->readAt(mSampleToChunkOffset + 8 + index * 12,
                                  buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
             return ERROR_IO;
         }
@@ -361,7 +362,7 @@
     switch (mSampleSizeFieldSize) {
         case 32:
         {
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         mSampleSizeOffset + 12 + 4 * sample_index,
                         sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) {
                 return ERROR_IO;
@@ -374,7 +375,7 @@
         case 16:
         {
             uint16_t x;
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         mSampleSizeOffset + 12 + 2 * sample_index,
                         &x, sizeof(x)) < (ssize_t)sizeof(x)) {
                 return ERROR_IO;
@@ -387,7 +388,7 @@
         case 8:
         {
             uint8_t x;
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         mSampleSizeOffset + 12 + sample_index,
                         &x, sizeof(x)) < (ssize_t)sizeof(x)) {
                 return ERROR_IO;
@@ -402,7 +403,7 @@
             CHECK_EQ(mSampleSizeFieldSize, 4);
 
             uint8_t x;
-            if (mDataSource->read_at(
+            if (mDataSource->readAt(
                         mSampleSizeOffset + 12 + sample_index / 2,
                         &x, sizeof(x)) < (ssize_t)sizeof(x)) {
                 return ERROR_IO;
@@ -553,7 +554,7 @@
     uint32_t right = mNumSyncSamples;
     while (left < right) {
         uint32_t mid = (left + right) / 2;
-        if (mDataSource->read_at(
+        if (mDataSource->readAt(
                     mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) {
             return ERROR_IO;
         }
@@ -574,5 +575,52 @@
     return OK;
 }
 
+status_t SampleTable::findThumbnailSample(uint32_t *sample_index) {
+    if (mSyncSampleOffset < 0) {
+        // All samples are sync-samples.
+        *sample_index = 0;
+        return OK;
+    }
+
+    uint32_t bestSampleIndex = 0;
+    size_t maxSampleSize = 0;
+
+    static const size_t kMaxNumSyncSamplesToScan = 20;
+
+    // Consider the first kMaxNumSyncSamplesToScan sync samples and
+    // pick the one with the largest (compressed) size as the thumbnail.
+
+    size_t numSamplesToScan = mNumSyncSamples;
+    if (numSamplesToScan > kMaxNumSyncSamplesToScan) {
+        numSamplesToScan = kMaxNumSyncSamplesToScan;
+    }
+
+    for (size_t i = 0; i < numSamplesToScan; ++i) {
+        uint32_t x;
+        if (mDataSource->readAt(
+                    mSyncSampleOffset + 8 + i * 4, &x, 4) != 4) {
+            return ERROR_IO;
+        }
+        x = ntohl(x);
+        --x;
+
+        // Now x is a sample index.
+        size_t sampleSize;
+        status_t err = getSampleSize(x, &sampleSize);
+        if (err != OK) {
+            return err;
+        }
+
+        if (i == 0 || sampleSize > maxSampleSize) {
+            bestSampleIndex = x;
+            maxSampleSize = sampleSize;
+        }
+    }
+
+    *sample_index = bestSampleIndex;
+
+    return OK;
+}
+
 }  // namespace android
 
diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp
index 346b5aa..ec25430 100644
--- a/media/libstagefright/ShoutcastSource.cpp
+++ b/media/libstagefright/ShoutcastSource.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
+#include "include/stagefright_string.h"
+#include "include/HTTPStream.h"
+
 #include <stdlib.h>
 
-#include <media/stagefright/HTTPStream.h>
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaBufferGroup.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/ShoutcastSource.h>
-#include <media/stagefright/stagefright_string.h>
 
 namespace android {
 
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index 3d85f75..dd8005c 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -22,10 +22,11 @@
 #define LOG_TAG "TimedEventQueue"
 #include <utils/Log.h>
 
+#include "include/TimedEventQueue.h"
+
 #include <sys/time.h>
 
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/TimedEventQueue.h>
 
 namespace android {
 
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
new file mode 100644
index 0000000..542c764
--- /dev/null
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -0,0 +1,317 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "WAVExtractor"
+#include <utils/Log.h>
+
+#include "include/WAVExtractor.h"
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/String8.h>
+
+namespace android {
+
+static uint16_t WAVE_FORMAT_PCM = 1;
+
+static uint32_t U32_LE_AT(const uint8_t *ptr) {
+    return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0];
+}
+
+static uint16_t U16_LE_AT(const uint8_t *ptr) {
+    return ptr[1] << 8 | ptr[0];
+}
+
+struct WAVSource : public MediaSource {
+    WAVSource(
+            const sp<DataSource> &dataSource,
+            int32_t sampleRate, int32_t numChannels,
+            off_t offset, size_t size);
+
+    virtual status_t start(MetaData *params = NULL);
+    virtual status_t stop();
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+protected:
+    virtual ~WAVSource();
+
+private:
+    static const size_t kMaxFrameSize;
+
+    sp<DataSource> mDataSource;
+    int32_t mSampleRate;
+    int32_t mNumChannels;
+    off_t mOffset;
+    size_t mSize;
+    bool mStarted;
+    MediaBufferGroup *mGroup;
+    off_t mCurrentPos;
+
+    WAVSource(const WAVSource &);
+    WAVSource &operator=(const WAVSource &);
+};
+
+WAVExtractor::WAVExtractor(const sp<DataSource> &source)
+    : mDataSource(source),
+      mValidFormat(false) {
+    mInitCheck = init();
+}
+
+WAVExtractor::~WAVExtractor() {
+}
+
+size_t WAVExtractor::countTracks() {
+    return mInitCheck == OK ? 1 : 0;
+}
+
+sp<MediaSource> WAVExtractor::getTrack(size_t index) {
+    if (mInitCheck != OK || index > 0) {
+        return NULL;
+    }
+
+    return new WAVSource(
+            mDataSource, mSampleRate, mNumChannels, mDataOffset, mDataSize);
+}
+
+sp<MetaData> WAVExtractor::getTrackMetaData(
+        size_t index, uint32_t flags) {
+    if (mInitCheck != OK || index > 0) {
+        return NULL;
+    }
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+    meta->setInt32(kKeyChannelCount, mNumChannels);
+    meta->setInt32(kKeySampleRate, mSampleRate);
+
+    int64_t durationUs =
+        1000000LL * (mDataSize / (mNumChannels * 2)) / mSampleRate;
+
+    meta->setInt64(kKeyDuration, durationUs);
+
+    return meta;
+}
+
+status_t WAVExtractor::init() {
+    uint8_t header[12];
+    if (mDataSource->readAt(
+                0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+        return NO_INIT;
+    }
+
+    if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
+        return NO_INIT;
+    }
+
+    size_t totalSize = U32_LE_AT(&header[4]);
+
+    off_t offset = 12;
+    size_t remainingSize = totalSize;
+    while (remainingSize >= 8) {
+        uint8_t chunkHeader[8];
+        if (mDataSource->readAt(offset, chunkHeader, 8) < 8) {
+            return NO_INIT;
+        }
+
+        remainingSize -= 8;
+        offset += 8;
+        
+        uint32_t chunkSize = U32_LE_AT(&chunkHeader[4]);
+
+        if (chunkSize > remainingSize) {
+            return NO_INIT;
+        }
+
+        if (!memcmp(chunkHeader, "fmt ", 4)) {
+            if (chunkSize < 16) {
+                return NO_INIT;
+            }
+
+            uint8_t formatSpec[16];
+            if (mDataSource->readAt(offset, formatSpec, 16) < 16) {
+                return NO_INIT;
+            }
+
+            uint16_t format = U16_LE_AT(formatSpec);
+            if (format != WAVE_FORMAT_PCM) {
+                return ERROR_UNSUPPORTED;
+            }
+
+            mNumChannels = U16_LE_AT(&formatSpec[2]);
+            if (mNumChannels != 1 && mNumChannels != 2) {
+                return ERROR_UNSUPPORTED;
+            }
+
+            mSampleRate = U32_LE_AT(&formatSpec[4]);
+
+            if (U16_LE_AT(&formatSpec[14]) != 16) {
+                return ERROR_UNSUPPORTED;
+            }
+
+            mValidFormat = true;
+        } else if (!memcmp(chunkHeader, "data", 4)) {
+            if (mValidFormat) {
+                mDataOffset = offset;
+                mDataSize = chunkSize;
+
+                return OK;
+            }
+        }
+
+        offset += chunkSize;
+    }
+
+    return NO_INIT;
+}
+
+const size_t WAVSource::kMaxFrameSize = 32768;
+
+WAVSource::WAVSource(
+        const sp<DataSource> &dataSource,
+        int32_t sampleRate, int32_t numChannels,
+        off_t offset, size_t size)
+    : mDataSource(dataSource),
+      mSampleRate(sampleRate),
+      mNumChannels(numChannels),
+      mOffset(offset),
+      mSize(size),
+      mStarted(false),
+      mGroup(NULL) {
+}
+
+WAVSource::~WAVSource() {
+    if (mStarted) {
+        stop();
+    }
+}
+
+status_t WAVSource::start(MetaData *params) {
+    LOGV("WAVSource::start");
+
+    CHECK(!mStarted);
+
+    mGroup = new MediaBufferGroup;
+    mGroup->add_buffer(new MediaBuffer(kMaxFrameSize));
+
+    mCurrentPos = mOffset;
+
+    mStarted = true;
+
+    return OK;
+}
+
+status_t WAVSource::stop() {
+    LOGV("WAVSource::stop");
+
+    CHECK(mStarted);
+
+    delete mGroup;
+    mGroup = NULL;
+
+    mStarted = false;
+
+    return OK;
+}
+
+sp<MetaData> WAVSource::getFormat() {
+    LOGV("WAVSource::getFormat");
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+    meta->setInt32(kKeyChannelCount, mNumChannels);
+    meta->setInt32(kKeySampleRate, mSampleRate);
+
+    int64_t durationUs =
+        1000000LL * (mSize / (mNumChannels * 2)) / mSampleRate;
+
+    meta->setInt64(kKeyDuration, durationUs);
+
+    return meta;
+}
+
+status_t WAVSource::read(
+        MediaBuffer **out, const ReadOptions *options) {
+    *out = NULL;
+
+    int64_t seekTimeUs;
+    if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+        int64_t pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * 2;
+        if (pos > mSize) {
+            pos = mSize;
+        }
+        mCurrentPos = pos + mOffset;
+    }
+
+    MediaBuffer *buffer;
+    status_t err = mGroup->acquire_buffer(&buffer);
+    if (err != OK) {
+        return err;
+    }
+
+    ssize_t n = mDataSource->readAt(
+            mCurrentPos, buffer->data(), kMaxFrameSize);
+
+    if (n <= 0) {
+        buffer->release();
+        buffer = NULL;
+
+        return ERROR_END_OF_STREAM;
+    }
+
+    mCurrentPos += n;
+
+    buffer->set_range(0, n);
+    buffer->meta_data()->setInt64(
+            kKeyTime,
+            1000000LL * (mCurrentPos - mOffset)
+                / (mNumChannels * 2) / mSampleRate);
+
+
+    *out = buffer;
+
+    return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SniffWAV(
+        const sp<DataSource> &source, String8 *mimeType, float *confidence) {
+    char header[12];
+    if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+        return false;
+    }
+
+    if (memcmp(header, "RIFF", 4) || memcmp(&header[8], "WAVE", 4)) {
+        return false;
+    }
+
+    *mimeType = MEDIA_MIMETYPE_CONTAINER_WAV;
+    *confidence = 0.3f;
+
+    return true;
+}
+
+}  // namespace android
+
diff --git a/include/media/stagefright/AMRExtractor.h b/media/libstagefright/include/AMRExtractor.h
similarity index 94%
rename from include/media/stagefright/AMRExtractor.h
rename to media/libstagefright/include/AMRExtractor.h
index c8710d3..debf006 100644
--- a/include/media/stagefright/AMRExtractor.h
+++ b/media/libstagefright/include/AMRExtractor.h
@@ -30,7 +30,7 @@
 
     virtual size_t countTracks();
     virtual sp<MediaSource> getTrack(size_t index);
-    virtual sp<MetaData> getTrackMetaData(size_t index);
+    virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
 
     static sp<MetaData> makeAMRFormat(bool isWide);
 
diff --git a/include/media/stagefright/ESDS.h b/media/libstagefright/include/ESDS.h
similarity index 100%
rename from include/media/stagefright/ESDS.h
rename to media/libstagefright/include/ESDS.h
diff --git a/include/media/stagefright/HTTPStream.h b/media/libstagefright/include/HTTPStream.h
similarity index 96%
rename from include/media/stagefright/HTTPStream.h
rename to media/libstagefright/include/HTTPStream.h
index 72e796c..43ef614 100644
--- a/include/media/stagefright/HTTPStream.h
+++ b/media/libstagefright/include/HTTPStream.h
@@ -18,10 +18,11 @@
 
 #define HTTP_STREAM_H_
 
+#include "stagefright_string.h"
+
 #include <sys/types.h>
 
 #include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/stagefright_string.h>
 #include <utils/KeyedVector.h>
 
 namespace android {
diff --git a/include/media/stagefright/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h
similarity index 94%
rename from include/media/stagefright/MP3Extractor.h
rename to media/libstagefright/include/MP3Extractor.h
index 11ba01d..074973b 100644
--- a/include/media/stagefright/MP3Extractor.h
+++ b/media/libstagefright/include/MP3Extractor.h
@@ -32,7 +32,7 @@
 
     virtual size_t countTracks();
     virtual sp<MediaSource> getTrack(size_t index);
-    virtual sp<MetaData> getTrackMetaData(size_t index);
+    virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
 
 protected:
     virtual ~MP3Extractor();
diff --git a/include/media/stagefright/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
similarity index 93%
rename from include/media/stagefright/MPEG4Extractor.h
rename to media/libstagefright/include/MPEG4Extractor.h
index 932e30f..ce4736d 100644
--- a/include/media/stagefright/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -33,7 +33,7 @@
 
     size_t countTracks();
     sp<MediaSource> getTrack(size_t index);
-    sp<MetaData> getTrackMetaData(size_t index);
+    sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
 
 protected:
     virtual ~MPEG4Extractor();
@@ -44,6 +44,7 @@
         sp<MetaData> meta;
         uint32_t timescale;
         sp<SampleTable> sampleTable;
+        bool includes_expensive_metadata;
     };
 
     sp<DataSource> mDataSource;
diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h
index d0bd61e..a4b62b2 100644
--- a/media/libstagefright/include/OMX.h
+++ b/media/libstagefright/include/OMX.h
@@ -99,7 +99,7 @@
             OMX_IN OMX_U32 nData1,
             OMX_IN OMX_U32 nData2,
             OMX_IN OMX_PTR pEventData);
-
+        
     OMX_ERRORTYPE OnEmptyBufferDone(
             node_id node, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);
 
diff --git a/include/media/stagefright/QComHardwareRenderer.h b/media/libstagefright/include/QComHardwareRenderer.h
similarity index 100%
rename from include/media/stagefright/QComHardwareRenderer.h
rename to media/libstagefright/include/QComHardwareRenderer.h
diff --git a/include/media/stagefright/SampleTable.h b/media/libstagefright/include/SampleTable.h
similarity index 86%
rename from include/media/stagefright/SampleTable.h
rename to media/libstagefright/include/SampleTable.h
index 808d142..ead3431 100644
--- a/include/media/stagefright/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -35,17 +35,17 @@
 
     // type can be 'stco' or 'co64'.
     status_t setChunkOffsetParams(
-            uint32_t type, off_t data_offset, off_t data_size);
+            uint32_t type, off_t data_offset, size_t data_size);
 
-    status_t setSampleToChunkParams(off_t data_offset, off_t data_size);
+    status_t setSampleToChunkParams(off_t data_offset, size_t data_size);
 
     // type can be 'stsz' or 'stz2'.
     status_t setSampleSizeParams(
-            uint32_t type, off_t data_offset, off_t data_size);
+            uint32_t type, off_t data_offset, size_t data_size);
 
-    status_t setTimeToSampleParams(off_t data_offset, off_t data_size);
+    status_t setTimeToSampleParams(off_t data_offset, size_t data_size);
 
-    status_t setSyncSampleParams(off_t data_offset, off_t data_size);
+    status_t setSyncSampleParams(off_t data_offset, size_t data_size);
 
     ////////////////////////////////////////////////////////////////////////////
 
@@ -75,6 +75,8 @@
     status_t findClosestSyncSample(
             uint32_t start_sample_index, uint32_t *sample_index);
 
+    status_t findThumbnailSample(uint32_t *sample_index);
+
 protected:
     ~SampleTable();
 
diff --git a/include/media/stagefright/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h
similarity index 86%
rename from include/media/stagefright/SoftwareRenderer.h
rename to media/libstagefright/include/SoftwareRenderer.h
index 1545493..9eed089 100644
--- a/include/media/stagefright/SoftwareRenderer.h
+++ b/media/libstagefright/include/SoftwareRenderer.h
@@ -18,7 +18,7 @@
 
 #define SOFTWARE_RENDERER_H_
 
-#include <OMX_Video.h>
+#include <media/stagefright/ColorConverter.h>
 #include <media/stagefright/VideoRenderer.h>
 #include <utils/RefBase.h>
 
@@ -41,13 +41,8 @@
             const void *data, size_t size, void *platformPrivate);
 
 private:
-    uint8_t *initClip();
-
-    void renderCbYCrY(const void *data, size_t size);
-    void renderYUV420Planar(const void *data, size_t size);
-    void renderQCOMYUV420SemiPlanar(const void *data, size_t size);
-
     OMX_COLOR_FORMATTYPE mColorFormat;
+    ColorConverter mConverter;
     sp<ISurface> mISurface;
     size_t mDisplayWidth, mDisplayHeight;
     size_t mDecodedWidth, mDecodedHeight;
@@ -55,8 +50,6 @@
     sp<MemoryHeapBase> mMemoryHeap;
     int mIndex;
 
-    uint8_t *mClip;
-
     SoftwareRenderer(const SoftwareRenderer &);
     SoftwareRenderer &operator=(const SoftwareRenderer &);
 };
diff --git a/include/media/stagefright/TIHardwareRenderer.h b/media/libstagefright/include/TIHardwareRenderer.h
similarity index 100%
rename from include/media/stagefright/TIHardwareRenderer.h
rename to media/libstagefright/include/TIHardwareRenderer.h
diff --git a/include/media/stagefright/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h
similarity index 100%
rename from include/media/stagefright/TimedEventQueue.h
rename to media/libstagefright/include/TimedEventQueue.h
diff --git a/include/media/stagefright/MP3Extractor.h b/media/libstagefright/include/WAVExtractor.h
similarity index 65%
copy from include/media/stagefright/MP3Extractor.h
copy to media/libstagefright/include/WAVExtractor.h
index 11ba01d..10b9700 100644
--- a/include/media/stagefright/MP3Extractor.h
+++ b/media/libstagefright/include/WAVExtractor.h
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef MP3_EXTRACTOR_H_
+#ifndef WAV_EXTRACTOR_H_
 
-#define MP3_EXTRACTOR_H_
+#define WAV_EXTRACTOR_H_
 
 #include <media/stagefright/MediaExtractor.h>
 
@@ -25,31 +25,37 @@
 class DataSource;
 class String8;
 
-class MP3Extractor : public MediaExtractor {
+class WAVExtractor : public MediaExtractor {
 public:
     // Extractor assumes ownership of "source".
-    MP3Extractor(const sp<DataSource> &source);
+    WAVExtractor(const sp<DataSource> &source);
 
     virtual size_t countTracks();
     virtual sp<MediaSource> getTrack(size_t index);
-    virtual sp<MetaData> getTrackMetaData(size_t index);
+    virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
 
 protected:
-    virtual ~MP3Extractor();
+    virtual ~WAVExtractor();
 
 private:
     sp<DataSource> mDataSource;
-    off_t mFirstFramePos;
-    sp<MetaData> mMeta;
-    uint32_t mFixedHeader;
+    status_t mInitCheck;
+    bool mValidFormat;
+    uint16_t mNumChannels;
+    uint32_t mSampleRate;
+    off_t mDataOffset;
+    size_t mDataSize;
 
-    MP3Extractor(const MP3Extractor &);
-    MP3Extractor &operator=(const MP3Extractor &);
+    status_t init();
+
+    WAVExtractor(const WAVExtractor &);
+    WAVExtractor &operator=(const WAVExtractor &);
 };
 
-bool SniffMP3(
+bool SniffWAV(
         const sp<DataSource> &source, String8 *mimeType, float *confidence);
 
 }  // namespace android
 
-#endif  // MP3_EXTRACTOR_H_
+#endif  // WAV_EXTRACTOR_H_
+
diff --git a/include/media/stagefright/stagefright_string.h b/media/libstagefright/include/stagefright_string.h
similarity index 92%
rename from include/media/stagefright/stagefright_string.h
rename to media/libstagefright/include/stagefright_string.h
index 1ed4c86..5dc7116 100644
--- a/include/media/stagefright/stagefright_string.h
+++ b/media/libstagefright/include/stagefright_string.h
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#ifndef STAGEFRIGHT_STRING_H_
+#ifndef STRING_H_
 
-#define STAGEFRIGHT_STRING_H_
+#define STRING_H_
 
 #include <utils/String8.h>
 
@@ -51,4 +51,4 @@
 
 }  // namespace android
 
-#endif  // STAGEFRIGHT_STRING_H_
+#endif  // STRING_H_
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index 20fb4f3..edbc04e 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -10,6 +10,7 @@
 LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
 
 LOCAL_SRC_FILES:=                 \
+        ColorConverter.cpp        \
 	OMX.cpp                   \
         OMXNodeInstance.cpp       \
         QComHardwareRenderer.cpp  \
diff --git a/media/libstagefright/omx/ColorConverter.cpp b/media/libstagefright/omx/ColorConverter.cpp
new file mode 100644
index 0000000..e74782f
--- /dev/null
+++ b/media/libstagefright/omx/ColorConverter.cpp
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/ColorConverter.h>
+#include <media/stagefright/MediaDebug.h>
+
+namespace android {
+
+static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
+
+ColorConverter::ColorConverter(
+        OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to)
+    : mSrcFormat(from),
+      mDstFormat(to),
+      mClip(NULL) {
+}
+
+ColorConverter::~ColorConverter() {
+    delete[] mClip;
+    mClip = NULL;
+}
+
+bool ColorConverter::isValid() const {
+    if (mDstFormat != OMX_COLOR_Format16bitRGB565) {
+        return false;
+    }
+
+    switch (mSrcFormat) {
+        case OMX_COLOR_FormatYUV420Planar:
+        case OMX_COLOR_FormatCbYCrY:
+        case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
+void ColorConverter::convert(
+        size_t width, size_t height,
+        const void *srcBits, size_t srcSkip,
+        void *dstBits, size_t dstSkip) {
+    CHECK_EQ(mDstFormat, OMX_COLOR_Format16bitRGB565);
+
+    switch (mSrcFormat) {
+        case OMX_COLOR_FormatYUV420Planar:
+            convertYUV420Planar(
+                    width, height, srcBits, srcSkip, dstBits, dstSkip);
+            break;
+
+        case OMX_COLOR_FormatCbYCrY:
+            convertCbYCrY(
+                    width, height, srcBits, srcSkip, dstBits, dstSkip);
+            break;
+
+        case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
+            convertQCOMYUV420SemiPlanar(
+                    width, height, srcBits, srcSkip, dstBits, dstSkip);
+            break;
+
+        default:
+        {
+            CHECK(!"Should not be here. Unknown color conversion.");
+            break;
+        }
+    }
+}
+
+void ColorConverter::convertCbYCrY(
+        size_t width, size_t height,
+        const void *srcBits, size_t srcSkip,
+        void *dstBits, size_t dstSkip) {
+    CHECK_EQ(srcSkip, 0);  // Doesn't really make sense for YUV formats.
+    CHECK(dstSkip >= width * 2);
+    CHECK((dstSkip & 3) == 0);
+
+    uint8_t *kAdjustedClip = initClip();
+
+    uint32_t *dst_ptr = (uint32_t *)dstBits;
+
+    const uint8_t *src = (const uint8_t *)srcBits;
+
+    for (size_t y = 0; y < height; ++y) {
+        for (size_t x = 0; x < width; x += 2) {
+            signed y1 = (signed)src[2 * x + 1] - 16;
+            signed y2 = (signed)src[2 * x + 3] - 16;
+            signed u = (signed)src[2 * x] - 128;
+            signed v = (signed)src[2 * x + 2] - 128;
+
+            signed u_b = u * 517;
+            signed u_g = -u * 100;
+            signed v_g = -v * 208;
+            signed v_r = v * 409;
+
+            signed tmp1 = y1 * 298;
+            signed b1 = (tmp1 + u_b) / 256;
+            signed g1 = (tmp1 + v_g + u_g) / 256;
+            signed r1 = (tmp1 + v_r) / 256;
+
+            signed tmp2 = y2 * 298;
+            signed b2 = (tmp2 + u_b) / 256;
+            signed g2 = (tmp2 + v_g + u_g) / 256;
+            signed r2 = (tmp2 + v_r) / 256;
+
+            uint32_t rgb1 =
+                ((kAdjustedClip[r1] >> 3) << 11)
+                | ((kAdjustedClip[g1] >> 2) << 5)
+                | (kAdjustedClip[b1] >> 3);
+
+            uint32_t rgb2 =
+                ((kAdjustedClip[r2] >> 3) << 11)
+                | ((kAdjustedClip[g2] >> 2) << 5)
+                | (kAdjustedClip[b2] >> 3);
+
+            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+        }
+
+        src += width * 2;
+        dst_ptr += dstSkip / 4;
+    }
+}
+
+void ColorConverter::convertYUV420Planar(
+        size_t width, size_t height,
+        const void *srcBits, size_t srcSkip,
+        void *dstBits, size_t dstSkip) {
+    CHECK_EQ(srcSkip, 0);  // Doesn't really make sense for YUV formats.
+    CHECK(dstSkip >= width * 2);
+    CHECK((dstSkip & 3) == 0);
+
+    uint8_t *kAdjustedClip = initClip();
+
+    uint32_t *dst_ptr = (uint32_t *)dstBits;
+    const uint8_t *src_y = (const uint8_t *)srcBits;
+
+    const uint8_t *src_u =
+        (const uint8_t *)src_y + width * height;
+
+    const uint8_t *src_v =
+        (const uint8_t *)src_u + (width / 2) * (height / 2);
+
+    for (size_t y = 0; y < height; ++y) {
+        for (size_t x = 0; x < width; x += 2) {
+            // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
+            // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
+            // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
+
+            // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
+            // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
+            // R = .................. + 409/256 * (V - 128)
+
+            // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
+            // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
+            // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
+
+            // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
+            // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
+            // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
+
+            // clip range -278 .. 535
+
+            signed y1 = (signed)src_y[x] - 16;
+            signed y2 = (signed)src_y[x + 1] - 16;
+
+            signed u = (signed)src_u[x / 2] - 128;
+            signed v = (signed)src_v[x / 2] - 128;
+
+            signed u_b = u * 517;
+            signed u_g = -u * 100;
+            signed v_g = -v * 208;
+            signed v_r = v * 409;
+
+            signed tmp1 = y1 * 298;
+            signed b1 = (tmp1 + u_b) / 256;
+            signed g1 = (tmp1 + v_g + u_g) / 256;
+            signed r1 = (tmp1 + v_r) / 256;
+
+            signed tmp2 = y2 * 298;
+            signed b2 = (tmp2 + u_b) / 256;
+            signed g2 = (tmp2 + v_g + u_g) / 256;
+            signed r2 = (tmp2 + v_r) / 256;
+
+            uint32_t rgb1 =
+                ((kAdjustedClip[r1] >> 3) << 11)
+                | ((kAdjustedClip[g1] >> 2) << 5)
+                | (kAdjustedClip[b1] >> 3);
+
+            uint32_t rgb2 =
+                ((kAdjustedClip[r2] >> 3) << 11)
+                | ((kAdjustedClip[g2] >> 2) << 5)
+                | (kAdjustedClip[b2] >> 3);
+
+            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+        }
+
+        src_y += width;
+
+        if (y & 1) {
+            src_u += width / 2;
+            src_v += width / 2;
+        }
+
+        dst_ptr += dstSkip / 4;
+    }
+}
+
+void ColorConverter::convertQCOMYUV420SemiPlanar(
+        size_t width, size_t height,
+        const void *srcBits, size_t srcSkip,
+        void *dstBits, size_t dstSkip) {
+    CHECK_EQ(srcSkip, 0);  // Doesn't really make sense for YUV formats.
+    CHECK(dstSkip >= width * 2);
+    CHECK((dstSkip & 3) == 0);
+
+    uint8_t *kAdjustedClip = initClip();
+
+    uint32_t *dst_ptr = (uint32_t *)dstBits;
+    const uint8_t *src_y = (const uint8_t *)srcBits;
+
+    const uint8_t *src_u =
+        (const uint8_t *)src_y + width * height;
+
+    for (size_t y = 0; y < height; ++y) {
+        for (size_t x = 0; x < width; x += 2) {
+            signed y1 = (signed)src_y[x] - 16;
+            signed y2 = (signed)src_y[x + 1] - 16;
+
+            signed u = (signed)src_u[x & ~1] - 128;
+            signed v = (signed)src_u[(x & ~1) + 1] - 128;
+
+            signed u_b = u * 517;
+            signed u_g = -u * 100;
+            signed v_g = -v * 208;
+            signed v_r = v * 409;
+
+            signed tmp1 = y1 * 298;
+            signed b1 = (tmp1 + u_b) / 256;
+            signed g1 = (tmp1 + v_g + u_g) / 256;
+            signed r1 = (tmp1 + v_r) / 256;
+
+            signed tmp2 = y2 * 298;
+            signed b2 = (tmp2 + u_b) / 256;
+            signed g2 = (tmp2 + v_g + u_g) / 256;
+            signed r2 = (tmp2 + v_r) / 256;
+
+            uint32_t rgb1 =
+                ((kAdjustedClip[b1] >> 3) << 11)
+                | ((kAdjustedClip[g1] >> 2) << 5)
+                | (kAdjustedClip[r1] >> 3);
+
+            uint32_t rgb2 =
+                ((kAdjustedClip[b2] >> 3) << 11)
+                | ((kAdjustedClip[g2] >> 2) << 5)
+                | (kAdjustedClip[r2] >> 3);
+
+            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+        }
+
+        src_y += width;
+
+        if (y & 1) {
+            src_u += width;
+        }
+
+        dst_ptr += dstSkip / 4;
+    }
+}
+
+uint8_t *ColorConverter::initClip() {
+    static const signed kClipMin = -278;
+    static const signed kClipMax = 535;
+
+    if (mClip == NULL) {
+        mClip = new uint8_t[kClipMax - kClipMin + 1];
+
+        for (signed i = kClipMin; i <= kClipMax; ++i) {
+            mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
+        }
+    }
+
+    return &mClip[-kClipMin];
+}
+
+}  // namespace android
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 9ac0d44..e361018 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -24,12 +24,12 @@
 #include "pv_omxcore.h"
 
 #include "../include/OMXNodeInstance.h"
+#include "../include/QComHardwareRenderer.h"
+#include "../include/SoftwareRenderer.h"
+#include "../include/TIHardwareRenderer.h"
 
 #include <binder/IMemory.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/QComHardwareRenderer.h>
-#include <media/stagefright/SoftwareRenderer.h>
-#include <media/stagefright/TIHardwareRenderer.h>
 #include <media/stagefright/VideoRenderer.h>
 
 #include <OMX_Component.h>
@@ -235,7 +235,7 @@
             &OMXNodeInstance::kCallbacks);
 
     if (err != OMX_ErrorNone) {
-        LOGE("FAILED to allocate omx component '%s'", name);
+        LOGV("FAILED to allocate omx component '%s'", name);
 
         instance->onGetHandleFailed();
 
diff --git a/media/libstagefright/omx/QComHardwareRenderer.cpp b/media/libstagefright/omx/QComHardwareRenderer.cpp
index c65d1f3..837b6a4 100644
--- a/media/libstagefright/omx/QComHardwareRenderer.cpp
+++ b/media/libstagefright/omx/QComHardwareRenderer.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
+#include "../include/QComHardwareRenderer.h"
+
 #include <binder/MemoryHeapBase.h>
 #include <binder/MemoryHeapPmem.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/QComHardwareRenderer.h>
 #include <ui/ISurface.h>
 
 namespace android {
diff --git a/media/libstagefright/omx/SoftwareRenderer.cpp b/media/libstagefright/omx/SoftwareRenderer.cpp
index 4ed6869..ef6ede0 100644
--- a/media/libstagefright/omx/SoftwareRenderer.cpp
+++ b/media/libstagefright/omx/SoftwareRenderer.cpp
@@ -17,21 +17,21 @@
 #define LOG_TAG "SoftwareRenderer"
 #include <utils/Log.h>
 
+#include "../include/SoftwareRenderer.h"
+
 #include <binder/MemoryHeapBase.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/SoftwareRenderer.h>
 #include <ui/ISurface.h>
 
 namespace android {
 
-#define QCOM_YUV        0
-
 SoftwareRenderer::SoftwareRenderer(
         OMX_COLOR_FORMATTYPE colorFormat,
         const sp<ISurface> &surface,
         size_t displayWidth, size_t displayHeight,
         size_t decodedWidth, size_t decodedHeight)
     : mColorFormat(colorFormat),
+      mConverter(colorFormat, OMX_COLOR_Format16bitRGB565),
       mISurface(surface),
       mDisplayWidth(displayWidth),
       mDisplayHeight(displayHeight),
@@ -39,12 +39,12 @@
       mDecodedHeight(decodedHeight),
       mFrameSize(mDecodedWidth * mDecodedHeight * 2),  // RGB565
       mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)),
-      mIndex(0),
-      mClip(NULL) {
+      mIndex(0) {
     CHECK(mISurface.get() != NULL);
     CHECK(mDecodedWidth > 0);
     CHECK(mDecodedHeight > 0);
     CHECK(mMemoryHeap->heapID() >= 0);
+    CHECK(mConverter.isValid());
 
     ISurface::BufferHeap bufferHeap(
             mDisplayWidth, mDisplayHeight,
@@ -58,278 +58,19 @@
 
 SoftwareRenderer::~SoftwareRenderer() {
     mISurface->unregisterBuffers();
-
-    delete[] mClip;
-    mClip = NULL;
 }
 
 void SoftwareRenderer::render(
         const void *data, size_t size, void *platformPrivate) {
-    static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
-
-    switch (mColorFormat) {
-        case OMX_COLOR_FormatYUV420Planar:
-            return renderYUV420Planar(data, size);
-
-        case OMX_COLOR_FormatCbYCrY:
-            return renderCbYCrY(data, size);
-
-        case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
-            return renderQCOMYUV420SemiPlanar(data, size);
-
-        default:
-        {
-            LOGW("Cannot render color format %ld", mColorFormat);
-            break;
-        }
-    }
-}
-
-void SoftwareRenderer::renderYUV420Planar(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 3) / 2);
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
-    uint8_t *kAdjustedClip = initClip();
-
     size_t offset = mIndex * mFrameSize;
-
     void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
 
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src_y = (const uint8_t *)data;
-
-    const uint8_t *src_u =
-        (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
-#if !QCOM_YUV
-    const uint8_t *src_v =
-        (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2);
-#endif
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
-            // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
-            // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
-
-            // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
-            // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
-            // R = .................. + 409/256 * (V - 128)
-
-            // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
-            // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
-            // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
-
-            // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
-            // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
-            // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
-
-            // clip range -278 .. 535
-
-            signed y1 = (signed)src_y[x] - 16;
-            signed y2 = (signed)src_y[x + 1] - 16;
-
-#if QCOM_YUV
-            signed u = (signed)src_u[x & ~1] - 128;
-            signed v = (signed)src_u[(x & ~1) + 1] - 128;
-#else
-            signed u = (signed)src_u[x / 2] - 128;
-            signed v = (signed)src_v[x / 2] - 128;
-#endif
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[r1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[b1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[r2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[b2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src_y += mDecodedWidth;
-
-        if (y & 1) {
-#if QCOM_YUV
-            src_u += mDecodedWidth;
-#else
-            src_u += mDecodedWidth / 2;
-            src_v += mDecodedWidth / 2;
-#endif
-        }
-
-        dst_ptr += mDecodedWidth / 2;
-    }
+    mConverter.convert(
+            mDecodedWidth, mDecodedHeight,
+            data, 0, dst, 2 * mDecodedWidth);
 
     mISurface->postBuffer(offset);
     mIndex = 1 - mIndex;
 }
 
-void SoftwareRenderer::renderCbYCrY(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 2)) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 2));
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 2));
-
-    uint8_t *kAdjustedClip = initClip();
-
-    size_t offset = mIndex * mFrameSize;
-    void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src = (const uint8_t *)data;
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            signed y1 = (signed)src[2 * x + 1] - 16;
-            signed y2 = (signed)src[2 * x + 3] - 16;
-            signed u = (signed)src[2 * x] - 128;
-            signed v = (signed)src[2 * x + 2] - 128;
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[r1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[b1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[r2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[b2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src += mDecodedWidth * 2;
-        dst_ptr += mDecodedWidth / 2;
-    }
-
-    mISurface->postBuffer(offset);
-    mIndex = 1 - mIndex;
-}
-
-void SoftwareRenderer::renderQCOMYUV420SemiPlanar(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 3) / 2);
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
-    uint8_t *kAdjustedClip = initClip();
-
-    size_t offset = mIndex * mFrameSize;
-
-    void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
-
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src_y = (const uint8_t *)data;
-
-    const uint8_t *src_u =
-        (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            signed y1 = (signed)src_y[x] - 16;
-            signed y2 = (signed)src_y[x + 1] - 16;
-
-            signed u = (signed)src_u[x & ~1] - 128;
-            signed v = (signed)src_u[(x & ~1) + 1] - 128;
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[b1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[r1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[b2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[r2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src_y += mDecodedWidth;
-
-        if (y & 1) {
-            src_u += mDecodedWidth;
-        }
-
-        dst_ptr += mDecodedWidth / 2;
-    }
-
-    mISurface->postBuffer(offset);
-    mIndex = 1 - mIndex;
-}
-
-uint8_t *SoftwareRenderer::initClip() {
-    static const signed kClipMin = -278;
-    static const signed kClipMax = 535;
-
-    if (mClip == NULL) {
-        mClip = new uint8_t[kClipMax - kClipMin + 1];
-
-        for (signed i = kClipMin; i <= kClipMax; ++i) {
-            mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
-        }
-    }
-
-    return &mClip[-kClipMin];
-}
-
 }  // namespace android
diff --git a/media/libstagefright/omx/TIHardwareRenderer.cpp b/media/libstagefright/omx/TIHardwareRenderer.cpp
index ebade4a..6dde86a 100644
--- a/media/libstagefright/omx/TIHardwareRenderer.cpp
+++ b/media/libstagefright/omx/TIHardwareRenderer.cpp
@@ -17,7 +17,8 @@
 #define LOG_TAG "TIHardwareRenderer"
 #include <utils/Log.h>
 
-#include <media/stagefright/TIHardwareRenderer.h>
+#include "../include/TIHardwareRenderer.h"
+
 #include <media/stagefright/MediaDebug.h>
 #include <ui/ISurface.h>
 #include <ui/Overlay.h>
diff --git a/media/libstagefright/stagefright_string.cpp b/media/libstagefright/string.cpp
similarity index 97%
rename from media/libstagefright/stagefright_string.cpp
rename to media/libstagefright/string.cpp
index 2aedb80..bd6204b 100644
--- a/media/libstagefright/stagefright_string.cpp
+++ b/media/libstagefright/string.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <media/stagefright/stagefright_string.h>
+#include "include/stagefright_string.h"
 
 namespace android {
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
index e66e560..fa0986a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CameraTest.java
@@ -226,8 +226,7 @@
      * Test case 1: Take a picture and verify all the callback
      * functions are called properly.
      */
-    // TODO: add this back to LargeTest once bug 2141755 is fixed
-    // @LargeTest
+    @LargeTest
     public void testTakePicture() throws Exception {  
         synchronized (lock) {
             initializeMessageLooper();
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index ac27a2d..f11123e 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -18,7 +18,6 @@
 
 import java.io.Writer;
 import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGL11;
@@ -30,6 +29,8 @@
 import javax.microedition.khronos.opengles.GL10;
 
 import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.SurfaceHolder;
@@ -682,7 +683,10 @@
         }
         public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
             int[] num_config = new int[1];
-            egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config);
+            if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
+                    num_config)) {
+                throw new IllegalArgumentException("eglChooseConfig failed");
+            }
 
             int numConfigs = num_config[0];
 
@@ -692,8 +696,10 @@
             }
 
             EGLConfig[] configs = new EGLConfig[numConfigs];
-            egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
-                    num_config);
+            if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
+                    num_config)) {
+                throw new IllegalArgumentException("eglChooseConfig#2 failed");
+            }
             EGLConfig config = chooseConfig(egl, display, configs);
             if (config == null) {
                 throw new IllegalArgumentException("No config chosen");
@@ -818,11 +824,17 @@
              */
             mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
 
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+
             /*
              * We can now initialize EGL for that display
              */
             int[] version = new int[2];
-            mEgl.eglInitialize(mEglDisplay, version);
+            if(!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
             mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
 
             /*
@@ -1059,6 +1071,7 @@
                     }
                     if (changed) {
                         gl = (GL10) mEglHelper.createSurface(getHolder());
+                        sGLThreadManager.checkGLDriver(gl);
                         tellRendererSurfaceChanged = true;
                     }
                     if (tellRendererSurfaceCreated) {
@@ -1272,37 +1285,76 @@
         }
     }
 
-    static class GLThreadManager {
-        public boolean shouldQuit(GLThread thread) {
-            synchronized(this) {
-                return thread != mMostRecentGLThread;
-            }
-        }
+    private static class GLThreadManager {
         public void start(GLThread thread) throws InterruptedException {
+           if (! mGLESVersionCheckComplete) {
+                mGLESVersion = SystemProperties.getInt(
+                        "ro.opengles.version",
+                        ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+                if (mGLESVersion >= kGLES_20) {
+                    mMultipleGLESContextsAllowed = true;
+                }
+                mGLESVersionCheckComplete = true;
+            }
+
             GLThread oldThread = null;
             synchronized(this) {
                 oldThread = mMostRecentGLThread;
                 mMostRecentGLThread = thread;
             }
-            if (oldThread != null) {
+            if (oldThread != null && ! mMultipleGLESContextsAllowed) {
                 synchronized(oldThread) {
                     oldThread.notifyAll();
                 }
             }
-            sEglSemaphore.acquire();
-        }
-        public void end(GLThread thread) {
-            sEglSemaphore.release();
+
             synchronized(this) {
-                if (mMostRecentGLThread == thread) {
-                    mMostRecentGLThread = null;
+                while ((! mMultipleGLESContextsAllowed)
+                        && mGLContextCount > 0) {
+                    wait();
                 }
+
+                mGLContextCount++;
             }
         }
-        private GLThread mMostRecentGLThread;
-    }
 
-    private static final Semaphore sEglSemaphore = new Semaphore(1);
+        public synchronized void end(GLThread thread) {
+            mGLContextCount--;
+            notifyAll();
+            if (mMostRecentGLThread == thread) {
+                mMostRecentGLThread = null;
+            }
+        }
+
+        public synchronized void checkGLDriver(GL10 gl) {
+            if (! mGLESDriverCheckComplete) {
+                if (mGLESVersion < kGLES_20) {
+                    String renderer = gl.glGetString(GL10.GL_RENDERER);
+                    mMultipleGLESContextsAllowed =
+                        ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
+                    notifyAll();
+                }
+                mGLESDriverCheckComplete = true;
+            }
+        }
+
+        public boolean shouldQuit(GLThread thread) {
+            synchronized(this) {
+                return thread != mMostRecentGLThread;
+            }
+        }
+
+        private boolean mGLESVersionCheckComplete;
+        private int mGLESVersion;
+        private GLThread mMostRecentGLThread;
+        private boolean mGLESDriverCheckComplete;
+        private boolean mMultipleGLESContextsAllowed;
+        private int mGLContextCount;
+        private static final int kGLES_20 = 0x20000;
+        private static final String kMSM7K_RENDERER_PREFIX =
+            "Q3Dimension MSM7500 ";
+    };
+
     private static final GLThreadManager sGLThreadManager = new GLThreadManager();
     private boolean mSizeChanged = true;
 
diff --git a/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java b/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
index 2dae090..72b1dfb 100644
--- a/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
+++ b/opengl/tests/gl2_jni/src/com/android/gl2jni/GL2JNIView.java
@@ -56,19 +56,22 @@
  */
 class GL2JNIView extends GLSurfaceView {
     private static String TAG = "GL2JNIView";
-    GL2JNIView(Context context) {
+
+    public GL2JNIView(Context context) {
         super(context);
-        init();
+        init(false, 0, 0);
     }
 
-    public GL2JNIView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        init();
+    public GL2JNIView(Context context, boolean translucent, int depth, int stencil) {
+        super(context);
+        init(translucent, depth, stencil);
     }
 
-    private void init() {
+    private void init(boolean translucent, int depth, int stencil) {
         setEGLContextFactory(new ContextFactory());
-        setEGLConfigChooser(new ConfigChooser());
+        setEGLConfigChooser( translucent ?
+              new ConfigChooser(8,8,8,8, depth, stencil) :
+              new ConfigChooser(5,6,5,0, depth, stencil));
         setRenderer(new Renderer());
     }
 
@@ -105,6 +108,16 @@
             EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
             EGL10.EGL_NONE
         };
+
+        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+            mRedSize = r;
+            mGreenSize = g;
+            mBlueSize = b;
+            mAlphaSize = a;
+            mDepthSize = depth;
+            mStencilSize = stencil;
+        }
+
         public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
 
             int[] num_config = new int[1];
@@ -112,14 +125,158 @@
 
             int numConfigs = num_config[0];
 
-            Log.w(TAG, String.format("Found %d configurations", numConfigs));
             if (numConfigs <= 0) {
                 throw new IllegalArgumentException("No configs match configSpec");
             }
             EGLConfig[] configs = new EGLConfig[numConfigs];
             egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
-            return configs[0];
+            // printConfigs(egl, display, configs);
+            return chooseConfig(egl, display, configs);
         }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig[] configs) {
+            EGLConfig closestConfig = null;
+            int closestDistance = 1000;
+            for(EGLConfig config : configs) {
+                int d = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_DEPTH_SIZE, 0);
+                int s = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_STENCIL_SIZE, 0);
+                if (d >= mDepthSize && s>= mStencilSize) {
+                    int r = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config,
+                             EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config,
+                              EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_ALPHA_SIZE, 0);
+                    int distance = Math.abs(r - mRedSize)
+                                + Math.abs(g - mGreenSize)
+                                + Math.abs(b - mBlueSize)
+                                + Math.abs(a - mAlphaSize);
+                    if (distance < closestDistance) {
+                        closestDistance = distance;
+                        closestConfig = config;
+                    }
+                }
+            }
+            return closestConfig;
+        }
+
+        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+                EGLConfig config, int attribute, int defaultValue) {
+
+            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+                return mValue[0];
+            }
+            return defaultValue;
+        }
+
+        private void printConfigs(EGL10 egl, EGLDisplay display,
+            EGLConfig[] configs) {
+            int numConfigs = configs.length;
+            Log.w(TAG, String.format("%d configurations", numConfigs));
+            for (int i = 0; i < numConfigs; i++) {
+                Log.w(TAG, String.format("Configuration %d:\n", i));
+                printConfig(egl, display, configs[i]);
+            }
+        }
+
+        private void printConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig config) {
+            int[] attributes = {
+                    EGL10.EGL_BUFFER_SIZE,
+                    EGL10.EGL_ALPHA_SIZE,
+                    EGL10.EGL_BLUE_SIZE,
+                    EGL10.EGL_GREEN_SIZE,
+                    EGL10.EGL_RED_SIZE,
+                    EGL10.EGL_DEPTH_SIZE,
+                    EGL10.EGL_STENCIL_SIZE,
+                    EGL10.EGL_CONFIG_CAVEAT,
+                    EGL10.EGL_CONFIG_ID,
+                    EGL10.EGL_LEVEL,
+                    EGL10.EGL_MAX_PBUFFER_HEIGHT,
+                    EGL10.EGL_MAX_PBUFFER_PIXELS,
+                    EGL10.EGL_MAX_PBUFFER_WIDTH,
+                    EGL10.EGL_NATIVE_RENDERABLE,
+                    EGL10.EGL_NATIVE_VISUAL_ID,
+                    EGL10.EGL_NATIVE_VISUAL_TYPE,
+                    0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+                    EGL10.EGL_SAMPLES,
+                    EGL10.EGL_SAMPLE_BUFFERS,
+                    EGL10.EGL_SURFACE_TYPE,
+                    EGL10.EGL_TRANSPARENT_TYPE,
+                    EGL10.EGL_TRANSPARENT_RED_VALUE,
+                    EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+                    EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+                    0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+                    0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+                    0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+                    0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+                    EGL10.EGL_LUMINANCE_SIZE,
+                    EGL10.EGL_ALPHA_MASK_SIZE,
+                    EGL10.EGL_COLOR_BUFFER_TYPE,
+                    EGL10.EGL_RENDERABLE_TYPE,
+                    0x3042 // EGL10.EGL_CONFORMANT
+            };
+            String[] names = {
+                    "EGL_BUFFER_SIZE",
+                    "EGL_ALPHA_SIZE",
+                    "EGL_BLUE_SIZE",
+                    "EGL_GREEN_SIZE",
+                    "EGL_RED_SIZE",
+                    "EGL_DEPTH_SIZE",
+                    "EGL_STENCIL_SIZE",
+                    "EGL_CONFIG_CAVEAT",
+                    "EGL_CONFIG_ID",
+                    "EGL_LEVEL",
+                    "EGL_MAX_PBUFFER_HEIGHT",
+                    "EGL_MAX_PBUFFER_PIXELS",
+                    "EGL_MAX_PBUFFER_WIDTH",
+                    "EGL_NATIVE_RENDERABLE",
+                    "EGL_NATIVE_VISUAL_ID",
+                    "EGL_NATIVE_VISUAL_TYPE",
+                    "EGL_PRESERVED_RESOURCES",
+                    "EGL_SAMPLES",
+                    "EGL_SAMPLE_BUFFERS",
+                    "EGL_SURFACE_TYPE",
+                    "EGL_TRANSPARENT_TYPE",
+                    "EGL_TRANSPARENT_RED_VALUE",
+                    "EGL_TRANSPARENT_GREEN_VALUE",
+                    "EGL_TRANSPARENT_BLUE_VALUE",
+                    "EGL_BIND_TO_TEXTURE_RGB",
+                    "EGL_BIND_TO_TEXTURE_RGBA",
+                    "EGL_MIN_SWAP_INTERVAL",
+                    "EGL_MAX_SWAP_INTERVAL",
+                    "EGL_LUMINANCE_SIZE",
+                    "EGL_ALPHA_MASK_SIZE",
+                    "EGL_COLOR_BUFFER_TYPE",
+                    "EGL_RENDERABLE_TYPE",
+                    "EGL_CONFORMANT"
+            };
+            int[] value = new int[1];
+            for (int i = 0; i < attributes.length; i++) {
+                int attribute = attributes[i];
+                String name = names[i];
+                if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+                    Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
+                } else {
+                    // Log.w(TAG, String.format("  %s: failed\n", name));
+                    while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+                }
+            }
+        }
+
+        // Subclasses can adjust these values:
+        protected int mRedSize;
+        protected int mGreenSize;
+        protected int mBlueSize;
+        protected int mAlphaSize;
+        protected int mDepthSize;
+        protected int mStencilSize;
+        private int[] mValue = new int[1];
     }
 
     private static class Renderer implements GLSurfaceView.Renderer {
diff --git a/opengl/tests/gldual/Android.mk b/opengl/tests/gldual/Android.mk
new file mode 100644
index 0000000..e73c249
--- /dev/null
+++ b/opengl/tests/gldual/Android.mk
@@ -0,0 +1,51 @@
+#########################################################################
+# OpenGL ES JNI sample
+# This makefile builds both an activity and a shared library.
+#########################################################################
+ifneq ($(TARGET_SIMULATOR),true) # not 64 bit clean
+
+TOP_LOCAL_PATH:= $(call my-dir)
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := GLDual
+
+LOCAL_JNI_SHARED_LIBRARIES := libgldualjni
+
+include $(BUILD_PACKAGE)
+
+#########################################################################
+# Build JNI Shared Library
+#########################################################################
+
+LOCAL_PATH:= $(LOCAL_PATH)/jni
+
+include $(CLEAR_VARS)
+
+# Optional tag would mean it doesn't get installed by default
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS := -Werror
+
+LOCAL_SRC_FILES:= \
+  gl_code.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libutils \
+	libEGL \
+	libGLESv2
+
+LOCAL_MODULE := libgldualjni
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
+endif # TARGET_SIMULATOR
diff --git a/opengl/tests/gldual/AndroidManifest.xml b/opengl/tests/gldual/AndroidManifest.xml
new file mode 100644
index 0000000..06f4c4d
--- /dev/null
+++ b/opengl/tests/gldual/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.gldual">
+    <application
+            android:label="@string/gldual_activity">
+        <activity android:name="GLDualActivity"
+                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+            	android:launchMode="singleTask"
+            	android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/opengl/tests/gldual/jni/gl_code.cpp b/opengl/tests/gldual/jni/gl_code.cpp
new file mode 100644
index 0000000..f1f0a1f
--- /dev/null
+++ b/opengl/tests/gldual/jni/gl_code.cpp
@@ -0,0 +1,165 @@
+// OpenGL ES 2.0 code
+
+#include <nativehelper/jni.h>
+#define LOG_TAG "GL2JNI gl_code.cpp"
+#include <utils/Log.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+static void printGLString(const char *name, GLenum s) {
+    const char *v = (const char *) glGetString(s);
+    LOGI("GL %s = %s\n", name, v);
+}
+
+static void checkGlError(const char* op) {
+    for (GLint error = glGetError(); error; error
+            = glGetError()) {
+        LOGI("after %s() glError (0x%x)\n", op, error);
+    }
+}
+
+static const char gVertexShader[] = "attribute vec4 vPosition;\n"
+    "void main() {\n"
+    "  gl_Position = vPosition;\n"
+    "}\n";
+
+static const char gFragmentShader[] = "precision mediump float;\n"
+    "void main() {\n"
+    "  gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+    "}\n";
+
+GLuint loadShader(GLenum shaderType, const char* pSource) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader) {
+        glShaderSource(shader, 1, &pSource, NULL);
+        glCompileShader(shader);
+        GLint compiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+        if (!compiled) {
+            GLint infoLen = 0;
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+            if (infoLen) {
+                char* buf = (char*) malloc(infoLen);
+                if (buf) {
+                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                    LOGE("Could not compile shader %d:\n%s\n",
+                            shaderType, buf);
+                    free(buf);
+                }
+                glDeleteShader(shader);
+                shader = 0;
+            }
+        }
+    }
+    return shader;
+}
+
+GLuint createProgram(const char* pVertexSource, const char* pFragmentSource) {
+    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
+    if (!vertexShader) {
+        return 0;
+    }
+
+    GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
+    if (!pixelShader) {
+        return 0;
+    }
+
+    GLuint program = glCreateProgram();
+    if (program) {
+        glAttachShader(program, vertexShader);
+        checkGlError("glAttachShader");
+        glAttachShader(program, pixelShader);
+        checkGlError("glAttachShader");
+        glLinkProgram(program);
+        GLint linkStatus = GL_FALSE;
+        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+        if (linkStatus != GL_TRUE) {
+            GLint bufLength = 0;
+            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+            if (bufLength) {
+                char* buf = (char*) malloc(bufLength);
+                if (buf) {
+                    glGetProgramInfoLog(program, bufLength, NULL, buf);
+                    LOGE("Could not link program:\n%s\n", buf);
+                    free(buf);
+                }
+            }
+            glDeleteProgram(program);
+            program = 0;
+        }
+    }
+    return program;
+}
+
+GLuint gProgram;
+GLuint gvPositionHandle;
+
+bool setupGraphics(int w, int h) {
+    printGLString("Version", GL_VERSION);
+    printGLString("Vendor", GL_VENDOR);
+    printGLString("Renderer", GL_RENDERER);
+    printGLString("Extensions", GL_EXTENSIONS);
+
+    LOGI("setupGraphics(%d, %d)", w, h);
+    gProgram = createProgram(gVertexShader, gFragmentShader);
+    if (!gProgram) {
+        LOGE("Could not create program.");
+        return false;
+    }
+    gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
+    checkGlError("glGetAttribLocation");
+    LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
+            gvPositionHandle);
+
+    glViewport(0, 0, w, h);
+    checkGlError("glViewport");
+    return true;
+}
+
+const GLfloat gTriangleVertices[] = { 0.0f, 0.5f, -0.5f, -0.5f,
+        0.5f, -0.5f };
+
+void renderFrame() {
+    static float grey;
+    grey += 0.01f;
+    if (grey > 1.0f) {
+        grey = 0.0f;
+    }
+    glClearColor(grey, grey, grey, 1.0f);
+    checkGlError("glClearColor");
+    glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+    checkGlError("glClear");
+
+    glUseProgram(gProgram);
+    checkGlError("glUseProgram");
+
+    glVertexAttribPointer(gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);
+    checkGlError("glVertexAttribPointer");
+    glEnableVertexAttribArray(gvPositionHandle);
+    checkGlError("glEnableVertexAttribArray");
+    glDrawArrays(GL_TRIANGLES, 0, 3);
+    checkGlError("glDrawArrays");
+}
+
+extern "C" {
+    JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_init(JNIEnv * env, jobject obj, jint width, jint height);
+    JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_step(JNIEnv * env, jobject obj);
+};
+
+JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_init(JNIEnv * env, jobject obj, jint width, jint height)

+{
+    setupGraphics(width, height);
+}
+
+JNIEXPORT void JNICALL Java_com_android_gldual_GLDualLib_step(JNIEnv * env, jobject obj)
+{
+    renderFrame();
+}
+
diff --git a/opengl/tests/gldual/res/layout/gldual_activity.xml b/opengl/tests/gldual/res/layout/gldual_activity.xml
new file mode 100644
index 0000000..f2d59c7
--- /dev/null
+++ b/opengl/tests/gldual/res/layout/gldual_activity.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+  <android.opengl.GLSurfaceView android:id="@+id/gl1"
+	android:layout_width="fill_parent"
+	    android:layout_height="0dip"
+	    android:layout_weight="1" />
+  <com.android.gldual.GLDualGL2View android:id="@+id/gl2"
+	android:layout_width="fill_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1" />
+</LinearLayout>
diff --git a/opengl/tests/gldual/res/values/strings.xml b/opengl/tests/gldual/res/values/strings.xml
new file mode 100644
index 0000000..4267dff
--- /dev/null
+++ b/opengl/tests/gldual/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2006, 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 file contains resource definitions for displayed strings, allowing
+     them to be changed based on the locale and options. -->
+
+<resources>
+    <!-- Simple strings. -->
+    <string name="gldual_activity">GLDual</string>
+
+</resources>
+
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java b/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java
new file mode 100644
index 0000000..9d88f64
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualActivity.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package com.android.gldual;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.LinearLayout;
+
+
+public class GLDualActivity extends Activity {
+
+    GLSurfaceView mGLView;
+    GLDualGL2View mGL2View;
+
+    @Override protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        View root = getLayoutInflater().inflate(R.layout.gldual_activity, null);
+        mGLView = (GLSurfaceView) root.findViewById(R.id.gl1);
+        mGLView.setEGLConfigChooser(5,6,5,0,0,0);
+        mGLView.setRenderer(new TriangleRenderer());
+        mGL2View = (GLDualGL2View) root.findViewById(R.id.gl2);
+        setContentView(root);
+    }
+
+    @Override protected void onPause() {
+        super.onPause();
+        mGLView.onPause();
+        mGL2View.onPause();
+    }
+
+    @Override protected void onResume() {
+        super.onResume();
+        mGLView.onResume();
+        mGL2View.onResume();
+    }
+}
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java b/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java
new file mode 100644
index 0000000..8f5e347
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualGL2View.java
@@ -0,0 +1,299 @@
+/*
+ * 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.gldual;
+/*
+ * 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.
+ */
+
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * An implementation of SurfaceView that uses the dedicated surface for
+ * displaying an OpenGL animation.  This allows the animation to run in a
+ * separate thread, without requiring that it be driven by the update mechanism
+ * of the view hierarchy.
+ *
+ * The application-specific rendering code is delegated to a GLView.Renderer
+ * instance.
+ */
+class GLDualGL2View extends GLSurfaceView {
+    private static String TAG = "GLDualGL2View";
+
+    public GLDualGL2View(Context context) {
+        super(context);
+        init(false, 0, 0);
+    }
+
+    public GLDualGL2View(Context context, AttributeSet set) {
+        super(context, set);
+        init(false, 0, 0);
+    }
+
+    public GLDualGL2View(Context context, boolean translucent, int depth, int stencil) {
+        super(context);
+        init(translucent, depth, stencil);
+    }
+
+    private void init(boolean translucent, int depth, int stencil) {
+        setEGLContextFactory(new ContextFactory());
+        setEGLConfigChooser( translucent ?
+              new ConfigChooser(8,8,8,8, depth, stencil) :
+              new ConfigChooser(5,6,5,0, depth, stencil));
+        setRenderer(new Renderer());
+    }
+
+    private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
+        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+            Log.w(TAG, "creating OpenGL ES 2.0 context");
+            checkEglError("Before eglCreateContext", egl);
+            int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+            EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+            checkEglError("After eglCreateContext", egl);
+            return context;
+        }
+
+        public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+            egl.eglDestroyContext(display, context);
+        }
+    }
+
+    private static void checkEglError(String prompt, EGL10 egl) {
+        int error;
+        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
+        }
+    }
+
+    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
+        private static int EGL_OPENGL_ES2_BIT = 4;
+        private static int[] s_configAttribs2 =
+        {
+            EGL10.EGL_RED_SIZE, 4,
+            EGL10.EGL_GREEN_SIZE, 4,
+            EGL10.EGL_BLUE_SIZE, 4,
+            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL10.EGL_NONE
+        };
+
+        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+            mRedSize = r;
+            mGreenSize = g;
+            mBlueSize = b;
+            mAlphaSize = a;
+            mDepthSize = depth;
+            mStencilSize = stencil;
+        }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+
+            int[] num_config = new int[1];
+            egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
+
+            int numConfigs = num_config[0];
+
+            if (numConfigs <= 0) {
+                throw new IllegalArgumentException("No configs match configSpec");
+            }
+            EGLConfig[] configs = new EGLConfig[numConfigs];
+            egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
+            // printConfigs(egl, display, configs);
+            return chooseConfig(egl, display, configs);
+        }
+
+        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig[] configs) {
+            EGLConfig closestConfig = null;
+            int closestDistance = 1000;
+            for(EGLConfig config : configs) {
+                int d = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_DEPTH_SIZE, 0);
+                int s = findConfigAttrib(egl, display, config,
+                        EGL10.EGL_STENCIL_SIZE, 0);
+                if (d >= mDepthSize && s>= mStencilSize) {
+                    int r = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_RED_SIZE, 0);
+                    int g = findConfigAttrib(egl, display, config,
+                             EGL10.EGL_GREEN_SIZE, 0);
+                    int b = findConfigAttrib(egl, display, config,
+                              EGL10.EGL_BLUE_SIZE, 0);
+                    int a = findConfigAttrib(egl, display, config,
+                            EGL10.EGL_ALPHA_SIZE, 0);
+                    int distance = Math.abs(r - mRedSize)
+                                + Math.abs(g - mGreenSize)
+                                + Math.abs(b - mBlueSize)
+                                + Math.abs(a - mAlphaSize);
+                    if (distance < closestDistance) {
+                        closestDistance = distance;
+                        closestConfig = config;
+                    }
+                }
+            }
+            return closestConfig;
+        }
+
+        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+                EGLConfig config, int attribute, int defaultValue) {
+
+            if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+                return mValue[0];
+            }
+            return defaultValue;
+        }
+
+        private void printConfigs(EGL10 egl, EGLDisplay display,
+            EGLConfig[] configs) {
+            int numConfigs = configs.length;
+            Log.w(TAG, String.format("%d configurations", numConfigs));
+            for (int i = 0; i < numConfigs; i++) {
+                Log.w(TAG, String.format("Configuration %d:\n", i));
+                printConfig(egl, display, configs[i]);
+            }
+        }
+
+        private void printConfig(EGL10 egl, EGLDisplay display,
+                EGLConfig config) {
+            int[] attributes = {
+                    EGL10.EGL_BUFFER_SIZE,
+                    EGL10.EGL_ALPHA_SIZE,
+                    EGL10.EGL_BLUE_SIZE,
+                    EGL10.EGL_GREEN_SIZE,
+                    EGL10.EGL_RED_SIZE,
+                    EGL10.EGL_DEPTH_SIZE,
+                    EGL10.EGL_STENCIL_SIZE,
+                    EGL10.EGL_CONFIG_CAVEAT,
+                    EGL10.EGL_CONFIG_ID,
+                    EGL10.EGL_LEVEL,
+                    EGL10.EGL_MAX_PBUFFER_HEIGHT,
+                    EGL10.EGL_MAX_PBUFFER_PIXELS,
+                    EGL10.EGL_MAX_PBUFFER_WIDTH,
+                    EGL10.EGL_NATIVE_RENDERABLE,
+                    EGL10.EGL_NATIVE_VISUAL_ID,
+                    EGL10.EGL_NATIVE_VISUAL_TYPE,
+                    0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+                    EGL10.EGL_SAMPLES,
+                    EGL10.EGL_SAMPLE_BUFFERS,
+                    EGL10.EGL_SURFACE_TYPE,
+                    EGL10.EGL_TRANSPARENT_TYPE,
+                    EGL10.EGL_TRANSPARENT_RED_VALUE,
+                    EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+                    EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+                    0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+                    0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+                    0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+                    0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+                    EGL10.EGL_LUMINANCE_SIZE,
+                    EGL10.EGL_ALPHA_MASK_SIZE,
+                    EGL10.EGL_COLOR_BUFFER_TYPE,
+                    EGL10.EGL_RENDERABLE_TYPE,
+                    0x3042 // EGL10.EGL_CONFORMANT
+            };
+            String[] names = {
+                    "EGL_BUFFER_SIZE",
+                    "EGL_ALPHA_SIZE",
+                    "EGL_BLUE_SIZE",
+                    "EGL_GREEN_SIZE",
+                    "EGL_RED_SIZE",
+                    "EGL_DEPTH_SIZE",
+                    "EGL_STENCIL_SIZE",
+                    "EGL_CONFIG_CAVEAT",
+                    "EGL_CONFIG_ID",
+                    "EGL_LEVEL",
+                    "EGL_MAX_PBUFFER_HEIGHT",
+                    "EGL_MAX_PBUFFER_PIXELS",
+                    "EGL_MAX_PBUFFER_WIDTH",
+                    "EGL_NATIVE_RENDERABLE",
+                    "EGL_NATIVE_VISUAL_ID",
+                    "EGL_NATIVE_VISUAL_TYPE",
+                    "EGL_PRESERVED_RESOURCES",
+                    "EGL_SAMPLES",
+                    "EGL_SAMPLE_BUFFERS",
+                    "EGL_SURFACE_TYPE",
+                    "EGL_TRANSPARENT_TYPE",
+                    "EGL_TRANSPARENT_RED_VALUE",
+                    "EGL_TRANSPARENT_GREEN_VALUE",
+                    "EGL_TRANSPARENT_BLUE_VALUE",
+                    "EGL_BIND_TO_TEXTURE_RGB",
+                    "EGL_BIND_TO_TEXTURE_RGBA",
+                    "EGL_MIN_SWAP_INTERVAL",
+                    "EGL_MAX_SWAP_INTERVAL",
+                    "EGL_LUMINANCE_SIZE",
+                    "EGL_ALPHA_MASK_SIZE",
+                    "EGL_COLOR_BUFFER_TYPE",
+                    "EGL_RENDERABLE_TYPE",
+                    "EGL_CONFORMANT"
+            };
+            int[] value = new int[1];
+            for (int i = 0; i < attributes.length; i++) {
+                int attribute = attributes[i];
+                String name = names[i];
+                if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+                    Log.w(TAG, String.format("  %s: %d\n", name, value[0]));
+                } else {
+                    // Log.w(TAG, String.format("  %s: failed\n", name));
+                    while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+                }
+            }
+        }
+
+        // Subclasses can adjust these values:
+        protected int mRedSize;
+        protected int mGreenSize;
+        protected int mBlueSize;
+        protected int mAlphaSize;
+        protected int mDepthSize;
+        protected int mStencilSize;
+        private int[] mValue = new int[1];
+    }
+
+    private static class Renderer implements GLSurfaceView.Renderer {
+        public void onDrawFrame(GL10 gl) {
+            GLDualLib.step();
+        }
+
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            GLDualLib.init(width, height);
+        }
+
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            // Do nothing.
+        }
+    }
+}
+
diff --git a/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java b/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java
new file mode 100644
index 0000000..d8f765e
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/GLDualLib.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package com.android.gldual;
+
+// Wrapper for native library
+
+public class GLDualLib {
+
+     static {
+         System.loadLibrary("gldualjni");
+     }
+
+    /**
+     * @param width the current view width
+     * @param height the current view height
+     */
+     public static native void init(int width, int height);
+     public static native void step();
+}
diff --git a/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java b/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java
new file mode 100644
index 0000000..098c4d2
--- /dev/null
+++ b/opengl/tests/gldual/src/com/android/gldual/TriangleRenderer.java
@@ -0,0 +1,149 @@
+package com.android.gldual;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.opengl.GLSurfaceView;
+import android.opengl.GLU;
+import android.os.SystemClock;
+
+public class TriangleRenderer implements GLSurfaceView.Renderer{
+
+    public TriangleRenderer() {
+        mTriangle = new Triangle();
+    }
+
+    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        /*
+         * By default, OpenGL enables features that improve quality
+         * but reduce performance. One might want to tweak that
+         * especially on software renderer.
+         */
+        gl.glDisable(GL10.GL_DITHER);
+
+        /*
+         * Some one-time OpenGL initialization can be made here
+         * probably based on features of this particular context
+         */
+        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+                GL10.GL_FASTEST);
+
+        gl.glClearColor(.5f, .5f, .5f, 1);
+        gl.glShadeModel(GL10.GL_SMOOTH);
+    }
+
+    public void onDrawFrame(GL10 gl) {
+        /*
+         * By default, OpenGL enables features that improve quality
+         * but reduce performance. One might want to tweak that
+         * especially on software renderer.
+         */
+        gl.glDisable(GL10.GL_DITHER);
+
+        /*
+         * Usually, the first thing one might want to do is to clear
+         * the screen. The most efficient way of doing this is to use
+         * glClear().
+         */
+
+        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+        /*
+         * Now we're ready to draw some 3D objects
+         */
+
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glLoadIdentity();
+
+        GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
+
+        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+        long time = SystemClock.uptimeMillis() % 4000L;
+        float angle = 0.090f * ((int) time);
+
+        gl.glRotatef(angle, 0, 0, 1.0f);
+
+        mTriangle.draw(gl);
+    }
+
+    public void onSurfaceChanged(GL10 gl, int w, int h) {
+        gl.glViewport(0, 0, w, h);
+
+        /*
+        * Set our projection matrix. This doesn't have to be done
+        * each time we draw, but usually a new projection needs to
+        * be set when the viewport is resized.
+        */
+
+        float ratio = (float) w / h;
+        gl.glMatrixMode(GL10.GL_PROJECTION);
+        gl.glLoadIdentity();
+        gl.glFrustumf(-ratio, ratio, -1, 1, 3, 7);
+
+    }
+
+    private Triangle mTriangle;
+}
+
+class Triangle {
+    public Triangle() {
+
+        // Buffers to be passed to gl*Pointer() functions
+        // must be direct, i.e., they must be placed on the
+        // native heap where the garbage collector cannot
+        // move them.
+        //
+        // Buffers with multi-byte datatypes (e.g., short, int, float)
+        // must have their byte order set to native order
+
+        ByteBuffer vbb = ByteBuffer.allocateDirect(VERTS * 3 * 4);
+        vbb.order(ByteOrder.nativeOrder());
+        mFVertexBuffer = vbb.asFloatBuffer();
+
+        ByteBuffer tbb = ByteBuffer.allocateDirect(VERTS * 2 * 4);
+        tbb.order(ByteOrder.nativeOrder());
+
+        ByteBuffer ibb = ByteBuffer.allocateDirect(VERTS * 2);
+        ibb.order(ByteOrder.nativeOrder());
+        mIndexBuffer = ibb.asShortBuffer();
+
+        // A unit-sided equalateral triangle centered on the origin.
+        float[] coords = {
+                // X, Y, Z
+                -0.5f, -0.25f, 0,
+                 0.5f, -0.25f, 0,
+                 0.0f,  0.559016994f, 0
+        };
+
+        for (int i = 0; i < VERTS; i++) {
+            for(int j = 0; j < 3; j++) {
+                mFVertexBuffer.put(coords[i*3+j] * 2.0f);
+            }
+        }
+
+        for(int i = 0; i < VERTS; i++) {
+            mIndexBuffer.put((short) i);
+        }
+
+        mFVertexBuffer.position(0);
+        mIndexBuffer.position(0);
+    }
+
+    public void draw(GL10 gl) {
+        gl.glFrontFace(GL10.GL_CCW);
+        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mFVertexBuffer);
+        gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, VERTS,
+                GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
+    }
+
+    private final static int VERTS = 3;
+
+    private FloatBuffer mFVertexBuffer;
+    private ShortBuffer mIndexBuffer;
+}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 78215b0..a91635e 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -572,6 +572,8 @@
 
     // javadoc from interface
     public int stopUsingNetworkFeature(int networkType, String feature) {
+        enforceChangePermission();
+
         int pid = getCallingPid();
         int uid = getCallingUid();
 
@@ -611,7 +613,7 @@
             Log.d(TAG, "stopUsingNetworkFeature for net " + networkType +
                     ": " + feature);
         }
-        enforceChangePermission();
+
         if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
             return -1;
         }
diff --git a/services/java/com/android/server/DropBoxService.java b/services/java/com/android/server/DropBoxService.java
new file mode 100644
index 0000000..f4e5ebc
--- /dev/null
+++ b/services/java/com/android/server/DropBoxService.java
@@ -0,0 +1,695 @@
+/*
+ * 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.server;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxService;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Implementation of {@link IDropBoxService} using the filesystem.
+ * Clients use {@link DropBox} to access this service.
+ *
+ * {@hide}
+ */
+public final class DropBoxService extends IDropBoxService.Stub {
+    private static final String TAG = "DropBoxService";
+    private static final int DEFAULT_RESERVE_PERCENT = 10;
+    private static final int DEFAULT_QUOTA_PERCENT = 10;
+    private static final int DEFAULT_QUOTA_KB = 5 * 1024;
+    private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
+    private static final int QUOTA_RESCAN_MILLIS = 5000;
+
+    // TODO: This implementation currently uses one file per entry, which is
+    // inefficient for smallish entries -- consider using a single queue file
+    // per tag (or even globally) instead.
+
+    // The cached context and derived objects
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final File mDropBoxDir;
+
+    // Accounting of all currently written log files (set in init()).
+
+    private FileList mAllFiles = null;
+    private HashMap<String, FileList> mFilesByTag = null;
+
+    // Various bits of disk information
+
+    private StatFs mStatFs = null;
+    private int mBlockSize = 0;
+    private int mCachedQuotaBlocks = 0;  // Space we can use: computed from free space, etc.
+    private long mCachedQuotaUptimeMillis = 0;
+
+    // Ensure that all log entries have a unique timestamp
+    private long mLastTimestamp = 0;
+
+    /** Receives events that might indicate a need to clean up files. */
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mCachedQuotaUptimeMillis = 0;  // Force a re-check of quota size
+            try {
+                init();
+                trimToFit();
+            } catch (IOException e) {
+                Log.e(TAG, "Can't init", e);
+            }
+        }
+    };
+
+    /**
+     * Creates an instance of managed drop box storage.  Normally there is one of these
+     * run by the system, but others can be created for testing and other purposes.
+     *
+     * @param context to use for receiving free space & gservices intents
+     * @param path to store drop box entries in
+     */
+    public DropBoxService(Context context, File path) {
+        mDropBoxDir = path;
+
+        // Set up intent receivers
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        context.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+        context.registerReceiver(mReceiver, new IntentFilter(Settings.Gservices.CHANGED_ACTION));
+
+        // The real work gets done lazily in init() -- that way service creation always
+        // succeeds, and things like disk problems cause individual method failures.
+    }
+
+    /** Unregisters broadcast receivers and any other hooks -- for test instances */
+    public void stop() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    public void add(DropBox.Entry entry) {
+        File temp = null;
+        OutputStream output = null;
+        final String tag = entry.getTag();
+        try {
+            int flags = entry.getFlags();
+            if ((flags & DropBox.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+            init();
+            if (!isTagEnabled(tag)) return;
+            long max = trimToFit();
+            long lastTrim = System.currentTimeMillis();
+
+            byte[] buffer = new byte[mBlockSize];
+            InputStream input = entry.getInputStream();
+
+            // First, accumulate up to one block worth of data in memory before
+            // deciding whether to compress the data or not.
+
+            int read = 0;
+            while (read < buffer.length) {
+                int n = input.read(buffer, read, buffer.length - read);
+                if (n <= 0) break;
+                read += n;
+            }
+
+            // If we have at least one block, compress it -- otherwise, just write
+            // the data in uncompressed form.
+
+            temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
+            output = new FileOutputStream(temp);
+            if (read == buffer.length && ((flags & DropBox.IS_GZIPPED) == 0)) {
+                output = new GZIPOutputStream(output);
+                flags = flags | DropBox.IS_GZIPPED;
+            }
+
+            do {
+                output.write(buffer, 0, read);
+
+                long now = System.currentTimeMillis();
+                if (now - lastTrim > 30 * 1000) {
+                    max = trimToFit();  // In case data dribbles in slowly
+                    lastTrim = now;
+                }
+
+                read = input.read(buffer);
+                if (read <= 0) {
+                    output.close();  // Get a final size measurement
+                    output = null;
+                } else {
+                    output.flush();  // So the size measurement is pseudo-reasonable
+                }
+
+                long len = temp.length();
+                if (len > max) {
+                    Log.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");
+                    temp.delete();
+                    temp = null;  // Pass temp = null to createEntry() to leave a tombstone
+                    break;
+                }
+            } while (read > 0);
+
+            createEntry(temp, tag, flags);
+            temp = null;
+        } catch (IOException e) {
+            Log.e(TAG, "Can't write: " + tag, e);
+        } finally {
+            try { if (output != null) output.close(); } catch (IOException e) {}
+            entry.close();
+            if (temp != null) temp.delete();
+        }
+    }
+
+    public boolean isTagEnabled(String tag) {
+        return !"disabled".equals(Settings.Gservices.getString(
+                mContentResolver, Settings.Gservices.DROPBOX_TAG_PREFIX + tag));
+    }
+
+    public synchronized DropBox.Entry getNextEntry(String tag, long millis) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.READ_LOGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("READ_LOGS permission required");
+        }
+
+        try {
+            init();
+        } catch (IOException e) {
+            Log.e(TAG, "Can't init", e);
+            return null;
+        }
+
+        FileList list = tag == null ? mAllFiles : mFilesByTag.get(tag);
+        if (list == null) return null;
+
+        for (EntryFile entry : list.contents.tailSet(new EntryFile(millis + 1))) {
+            if (entry.tag == null) continue;
+            if ((entry.flags & DropBox.IS_EMPTY) != 0) {
+                return new DropBox.Entry(entry.tag, entry.timestampMillis);
+            }
+            try {
+                return new DropBox.Entry(entry.tag, entry.timestampMillis, entry.file, entry.flags);
+            } catch (IOException e) {
+                Log.e(TAG, "Can't read: " + entry.file, e);
+                // Continue to next file
+            }
+        }
+
+        return null;
+    }
+
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: Can't dump DropBoxService");
+            return;
+        }
+
+        try {
+            init();
+        } catch (IOException e) {
+            pw.println("Can't initialize: " + e);
+            Log.e(TAG, "Can't init", e);
+            return;
+        }
+
+        boolean doPrint = false, doFile = false;
+        ArrayList<String> searchArgs = new ArrayList<String>();
+        for (int i = 0; args != null && i < args.length; i++) {
+            if (args[i].equals("-p") || args[i].equals("--print")) {
+                doPrint = true;
+            } else if (args[i].equals("-f") || args[i].equals("--file")) {
+                doFile = true;
+            } else if (args[i].startsWith("-")) {
+                pw.print("Unknown argument: ");
+                pw.println(args[i]);
+            } else {
+                searchArgs.add(args[i]);
+            }
+        }
+
+        pw.format("Drop box contents: %d entries", mAllFiles.contents.size());
+        pw.println();
+
+        if (!searchArgs.isEmpty()) {
+            pw.print("Searching for:");
+            for (String a : searchArgs) pw.format(" %s", a);
+            pw.println();
+        }
+
+        int numFound = 0;
+        pw.println();
+        for (EntryFile entry : mAllFiles.contents) {
+            String date = new Formatter().format("%s.%03d",
+                    DateFormat.format("yyyy-MM-dd kk:mm:ss", entry.timestampMillis),
+                    entry.timestampMillis % 1000).toString();
+
+            boolean match = true;
+            for (String a: searchArgs) match = match && (date.contains(a) || a.equals(entry.tag));
+            if (!match) continue;
+
+            numFound++;
+            pw.print(date);
+            pw.print(" ");
+            pw.print(entry.tag == null ? "(no tag)" : entry.tag);
+            if (entry.file == null) {
+                pw.println(" (no file)");
+                continue;
+            } else if ((entry.flags & DropBox.IS_EMPTY) != 0) {
+                pw.println(" (contents lost)");
+                continue;
+            } else {
+                pw.print((entry.flags & DropBox.IS_GZIPPED) != 0 ? " (comopressed " : " (");
+                pw.print((entry.flags & DropBox.IS_TEXT) != 0 ? "text" : "data");
+                pw.format(", %d bytes)", entry.file.length());
+                pw.println();
+            }
+
+            if (doFile || (doPrint && (entry.flags & DropBox.IS_TEXT) == 0)) {
+                if (!doPrint) pw.print("    ");
+                pw.println(entry.file.getPath());
+            }
+
+            if ((entry.flags & DropBox.IS_TEXT) != 0 && (doPrint || !doFile)) {
+                DropBox.Entry dbe = null;
+                try {
+                    dbe = new DropBox.Entry(
+                             entry.tag, entry.timestampMillis, entry.file, entry.flags);
+
+                    if (doPrint) {
+                        InputStreamReader r = new InputStreamReader(dbe.getInputStream());
+                        char[] buf = new char[4096];
+                        boolean newline = false;
+                        for (;;) {
+                            int n = r.read(buf);
+                            if (n <= 0) break;
+                            pw.write(buf, 0, n);
+                            newline = (buf[n - 1] == '\n');
+                        }
+                        if (!newline) pw.println();
+                    } else {
+                        String text = dbe.getText(70);
+                        boolean truncated = (text.length() == 70);
+                        pw.print("    ");
+                        pw.print(text.trim().replace('\n', '/'));
+                        if (truncated) pw.print(" ...");
+                        pw.println();
+                    }
+                } catch (IOException e) {
+                    pw.print("*** ");
+                    pw.println(e.toString());
+                    Log.e(TAG, "Can't read: " + entry.file, e);
+                } finally {
+                    if (dbe != null) dbe.close();
+                }
+            }
+
+            if (doPrint) pw.println();
+        }
+
+        if (numFound == 0) pw.println("(No entries found.)");
+
+        if (args == null || args.length == 0) {
+            if (!doPrint) pw.println();
+            pw.println("Usage: dumpsys dropbox [--print|--file] [YYYY-mm-dd] [HH:MM:SS.SSS] [tag]");
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Chronologically sorted list of {@link #EntryFile} */
+    private static final class FileList implements Comparable<FileList> {
+        public int blocks = 0;
+        public final TreeSet<EntryFile> contents = new TreeSet<EntryFile>();
+
+        /** Sorts bigger FileList instances before smaller ones. */
+        public final int compareTo(FileList o) {
+            if (blocks != o.blocks) return o.blocks - blocks;
+            if (this == o) return 0;
+            if (hashCode() < o.hashCode()) return -1;
+            if (hashCode() > o.hashCode()) return 1;
+            return 0;
+        }
+    }
+
+    /** Metadata describing an on-disk log file. */
+    private static final class EntryFile implements Comparable<EntryFile> {
+        public final String tag;
+        public final long timestampMillis;
+        public final int flags;
+        public final File file;
+        public final int blocks;
+
+        /** Sorts earlier EntryFile instances before later ones. */
+        public final int compareTo(EntryFile o) {
+            if (timestampMillis < o.timestampMillis) return -1;
+            if (timestampMillis > o.timestampMillis) return 1;
+            if (file != null && o.file != null) return file.compareTo(o.file);
+            if (o.file != null) return -1;
+            if (file != null) return 1;
+            if (this == o) return 0;
+            if (hashCode() < o.hashCode()) return -1;
+            if (hashCode() > o.hashCode()) return 1;
+            return 0;
+        }
+
+        /**
+         * Moves an existing temporary file to a new log filename.
+         * @param temp file to rename
+         * @param dir to store file in
+         * @param tag to use for new log file name
+         * @param timestampMillis of log entry
+         * @param flags for the entry data
+         * @param blockSize to use for space accounting
+         * @throws IOException if the file can't be moved
+         */
+        public EntryFile(File temp, File dir, String tag,long timestampMillis,
+                         int flags, int blockSize) throws IOException {
+            if ((flags & DropBox.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+            this.tag = tag;
+            this.timestampMillis = timestampMillis;
+            this.flags = flags;
+            this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis +
+                    ((flags & DropBox.IS_TEXT) != 0 ? ".txt" : ".dat") +
+                    ((flags & DropBox.IS_GZIPPED) != 0 ? ".gz" : ""));
+
+            if (!temp.renameTo(this.file)) {
+                throw new IOException("Can't rename " + temp + " to " + this.file);
+            }
+            this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+        }
+
+        /**
+         * Creates a zero-length tombstone for a file whose contents were lost.
+         * @param dir to store file in
+         * @param tag to use for new log file name
+         * @param timestampMillis of log entry
+         * @throws IOException if the file can't be created.
+         */
+        public EntryFile(File dir, String tag, long timestampMillis) throws IOException {
+            this.tag = tag;
+            this.timestampMillis = timestampMillis;
+            this.flags = DropBox.IS_EMPTY;
+            this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost");
+            this.blocks = 0;
+            new FileOutputStream(this.file).close();
+        }
+
+        /**
+         * Extracts metadata from an existing on-disk log filename.
+         * @param file name of existing log file
+         * @param blockSize to use for space accounting
+         */
+        public EntryFile(File file, int blockSize) {
+            this.file = file;
+            this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+
+            String name = file.getName();
+            int at = name.lastIndexOf('@');
+            if (at < 0) {
+                this.tag = null;
+                this.timestampMillis = 0;
+                this.flags = DropBox.IS_EMPTY;
+                return;
+            }
+
+            int flags = 0;
+            this.tag = Uri.decode(name.substring(0, at));
+            if (name.endsWith(".gz")) {
+                flags |= DropBox.IS_GZIPPED;
+                name = name.substring(0, name.length() - 3);
+            }
+            if (name.endsWith(".lost")) {
+                flags |= DropBox.IS_EMPTY;
+                name = name.substring(at + 1, name.length() - 5);
+            } else if (name.endsWith(".txt")) {
+                flags |= DropBox.IS_TEXT;
+                name = name.substring(at + 1, name.length() - 4);
+            } else if (name.endsWith(".dat")) {
+                name = name.substring(at + 1, name.length() - 4);
+            } else {
+                this.flags = DropBox.IS_EMPTY;
+                this.timestampMillis = 0;
+                return;
+            }
+            this.flags = flags;
+
+            long millis;
+            try { millis = Long.valueOf(name); } catch (NumberFormatException e) { millis = 0; }
+            this.timestampMillis = millis;
+        }
+
+        /**
+         * Creates a EntryFile object with only a timestamp for comparison purposes.
+         * @param timestampMillis to compare with.
+         */
+        public EntryFile(long millis) {
+            this.tag = null;
+            this.timestampMillis = millis;
+            this.flags = DropBox.IS_EMPTY;
+            this.file = null;
+            this.blocks = 0;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** If never run before, scans disk contents to build in-memory tracking data. */
+    private synchronized void init() throws IOException {
+        if (mStatFs == null) {
+            if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {
+                throw new IOException("Can't mkdir: " + mDropBoxDir);
+            }
+            try {
+                mStatFs = new StatFs(mDropBoxDir.getPath());
+                mBlockSize = mStatFs.getBlockSize();
+            } catch (IllegalArgumentException e) {  // StatFs throws this on error
+                throw new IOException("Can't statfs: " + mDropBoxDir);
+            }
+        }
+
+        if (mAllFiles == null) {
+            File[] files = mDropBoxDir.listFiles();
+            if (files == null) throw new IOException("Can't list files: " + mDropBoxDir);
+
+            mAllFiles = new FileList();
+            mFilesByTag = new HashMap<String, FileList>();
+
+            // Scan pre-existing files.
+            for (File file : files) {
+                if (file.getName().endsWith(".tmp")) {
+                    Log.i(TAG, "Cleaning temp file: " + file);
+                    file.delete();
+                    continue;
+                }
+
+                EntryFile entry = new EntryFile(file, mBlockSize);
+                if (entry.tag == null) {
+                    Log.w(TAG, "Unrecognized file: " + file);
+                    continue;
+                } else if (entry.timestampMillis == 0) {
+                    Log.w(TAG, "Invalid filename: " + file);
+                    file.delete();
+                    continue;
+                }
+
+                enrollEntry(entry);
+            }
+        }
+    }
+
+    /** Adds a disk log file to in-memory tracking for accounting and enumeration. */
+    private synchronized void enrollEntry(EntryFile entry) {
+        mAllFiles.contents.add(entry);
+        mAllFiles.blocks += entry.blocks;
+
+        // mFilesByTag is used for trimming, so don't list empty files.
+        // (Zero-length/lost files are trimmed by date from mAllFiles.)
+
+        if (entry.tag != null && entry.file != null && entry.blocks > 0) {
+            FileList tagFiles = mFilesByTag.get(entry.tag);
+            if (tagFiles == null) {
+                tagFiles = new FileList();
+                mFilesByTag.put(entry.tag, tagFiles);
+            }
+            tagFiles.contents.add(entry);
+            tagFiles.blocks += entry.blocks;
+        }
+    }
+
+    /** Moves a temporary file to a final log filename and enrolls it. */
+    private synchronized void createEntry(File temp, String tag, int flags) throws IOException {
+        long t = System.currentTimeMillis();
+
+        // Require each entry to have a unique timestamp; if there are entries
+        // >10sec in the future (due to clock skew), drag them back to avoid
+        // keeping them around forever.
+
+        SortedSet<EntryFile> tail = mAllFiles.contents.tailSet(new EntryFile(t + 10000));
+        EntryFile[] future = null;
+        if (!tail.isEmpty()) {
+            future = tail.toArray(new EntryFile[tail.size()]);
+            tail.clear();  // Remove from mAllFiles
+        }
+
+        if (!mAllFiles.contents.isEmpty()) {
+            t = Math.max(t, mAllFiles.contents.last().timestampMillis + 1);
+        }
+
+        if (future != null) {
+            for (EntryFile late : future) {
+                mAllFiles.blocks -= late.blocks;
+                FileList tagFiles = mFilesByTag.get(late.tag);
+                if (tagFiles.contents.remove(late)) tagFiles.blocks -= late.blocks;
+                if ((late.flags & DropBox.IS_EMPTY) == 0) {
+                    enrollEntry(new EntryFile(
+                            late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize));
+                } else {
+                    enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++));
+                }
+            }
+        }
+
+        if (temp == null) {
+            enrollEntry(new EntryFile(mDropBoxDir, tag, t));
+        } else {
+            enrollEntry(new EntryFile(temp, mDropBoxDir, tag, t, flags, mBlockSize));
+        }
+    }
+
+    /**
+     * Trims the files on disk to make sure they aren't using too much space.
+     * @return the overall quota for storage (in bytes)
+     */
+    private synchronized long trimToFit() {
+        // Expunge aged items (including tombstones marking deleted data).
+
+        int ageSeconds = Settings.Gservices.getInt(mContentResolver,
+                Settings.Gservices.DROPBOX_AGE_SECONDS, DEFAULT_AGE_SECONDS);
+        long cutoffMillis = System.currentTimeMillis() - ageSeconds * 1000;
+        while (!mAllFiles.contents.isEmpty()) {
+            EntryFile entry = mAllFiles.contents.first();
+            if (entry.timestampMillis > cutoffMillis) break;
+
+            FileList tag = mFilesByTag.get(entry.tag);
+            if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+            if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+            if (entry.file != null) entry.file.delete();
+        }
+
+        // Compute overall quota (a fraction of available free space) in blocks.
+        // The quota changes dynamically based on the amount of free space;
+        // that way when lots of data is available we can use it, but we'll get
+        // out of the way if storage starts getting tight.
+
+        long uptimeMillis = SystemClock.uptimeMillis();
+        if (uptimeMillis > mCachedQuotaUptimeMillis + QUOTA_RESCAN_MILLIS) {
+            int quotaPercent = Settings.Gservices.getInt(mContentResolver,
+                    Settings.Gservices.DROPBOX_QUOTA_PERCENT, DEFAULT_QUOTA_PERCENT);
+            int reservePercent = Settings.Gservices.getInt(mContentResolver,
+                    Settings.Gservices.DROPBOX_RESERVE_PERCENT, DEFAULT_RESERVE_PERCENT);
+            int quotaKb = Settings.Gservices.getInt(mContentResolver,
+                    Settings.Gservices.DROPBOX_QUOTA_KB, DEFAULT_QUOTA_KB);
+
+            mStatFs.restat(mDropBoxDir.getPath());
+            int available = mStatFs.getAvailableBlocks();
+            int nonreserved = available - mStatFs.getBlockCount() * reservePercent / 100;
+            int maximum = quotaKb * 1024 / mBlockSize;
+            mCachedQuotaBlocks = Math.min(maximum, Math.max(0, nonreserved * quotaPercent / 100));
+            mCachedQuotaUptimeMillis = uptimeMillis;
+        }
+
+        // If we're using too much space, delete old items to make room.
+        //
+        // We trim each tag independently (this is why we keep per-tag lists).
+        // Space is "fairly" shared between tags -- they are all squeezed
+        // equally until enough space is reclaimed.
+        //
+        // A single circular buffer (a la logcat) would be simpler, but this
+        // way we can handle fat/bursty data (like 1MB+ bugreports, 300KB+
+        // kernel crash dumps, and 100KB+ ANR reports) without swamping small,
+        // well-behaved data // streams (event statistics, profile data, etc).
+        //
+        // Deleted files are replaced with zero-length tombstones to mark what
+        // was lost.  Tombstones are expunged by age (see above).
+
+        if (mAllFiles.blocks > mCachedQuotaBlocks) {
+            Log.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")");
+
+            // Find a fair share amount of space to limit each tag
+            int unsqueezed = mAllFiles.blocks, squeezed = 0;
+            TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values());
+            for (FileList tag : tags) {
+                if (squeezed > 0 && tag.blocks <= (mCachedQuotaBlocks - unsqueezed) / squeezed) {
+                    break;
+                }
+                unsqueezed -= tag.blocks;
+                squeezed++;
+            }
+            int tagQuota = (mCachedQuotaBlocks - unsqueezed) / squeezed;
+
+            // Remove old items from each tag until it meets the per-tag quota.
+            for (FileList tag : tags) {
+                if (mAllFiles.blocks < mCachedQuotaBlocks) break;
+                while (tag.blocks > tagQuota && !tag.contents.isEmpty()) {
+                    EntryFile entry = tag.contents.first();
+                    if (tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+                    if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+
+                    try {
+                        if (entry.file != null) entry.file.delete();
+                        enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis));
+                    } catch (IOException e) {
+                        Log.e(TAG, "Can't write tombstone file", e);
+                    }
+                }
+            }
+        }
+
+        return mCachedQuotaBlocks * mBlockSize;
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b8cf844..5f30710 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -43,6 +43,7 @@
 import android.util.Log;
 import android.accounts.AccountManagerService;
 
+import java.io.File;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -294,6 +295,14 @@
             }
 
             try {
+                Log.i(TAG, "DropBox Service");
+                ServiceManager.addService(Context.DROPBOX_SERVICE,
+                        new DropBoxService(context, new File("/data/system/dropbox")));
+            } catch (Throwable e) {
+                Log.e(TAG, "Failure starting DropBox Service", e);
+            }
+
+            try {
                 Log.i(TAG, "Checkin Service");
                 Intent intent = new Intent().setComponent(new ComponentName(
                         "com.google.android.server.checkin",
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 32ad6c6..0ea832b 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -142,6 +142,7 @@
     private static final int MESSAGE_STOP_WIFI        = 2;
     private static final int MESSAGE_START_WIFI       = 3;
     private static final int MESSAGE_RELEASE_WAKELOCK = 4;
+    private static final int MESSAGE_UPDATE_STATE     = 5;
 
     private final  WifiHandler mWifiHandler;
 
@@ -542,7 +543,7 @@
 
         value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
         if (!TextUtils.isEmpty(value)) {
-            config.SSID = value;
+            config.SSID = removeDoubleQuotes(value);
         } else {
             config.SSID = null;
         }
@@ -675,11 +676,21 @@
             value = WifiNative.getNetworkVariableCommand(netId,
                     field.varName());
             if (!TextUtils.isEmpty(value)) {
+                if (field != config.eap) value = removeDoubleQuotes(value);
                 field.setValue(value);
             }
         }
     }
 
+    private static String removeDoubleQuotes(String string) {
+        if (string.length() <= 2) return "";
+        return string.substring(1, string.length() - 1);
+    }
+
+    private static String convertToQuotedString(String string) {
+        return "\"" + string + "\"";
+    }
+
     /**
      * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
      * @return the supplicant-assigned identifier for the new or updated
@@ -731,7 +742,7 @@
                 !WifiNative.setNetworkVariableCommand(
                     netId,
                     WifiConfiguration.ssidVarName,
-                    config.SSID)) {
+                    convertToQuotedString(config.SSID))) {
                 if (DBG) {
                     Log.d(TAG, "failed to set SSID: "+config.SSID);
                 }
@@ -894,18 +905,22 @@
                     : config.enterpriseFields) {
                 String varName = field.varName();
                 String value = field.value();
-                if ((value != null) && !WifiNative.setNetworkVariableCommand(
-                    netId,
-                    varName,
-                    value)) {
-                    if (DBG) {
-                        Log.d(TAG, config.SSID + ": failed to set " + varName +
-                              ": " + value);
+                if (value != null) {
+                    if (field != config.eap) {
+                        value = convertToQuotedString(value);
                     }
-                    break setVariables;
+                    if (!WifiNative.setNetworkVariableCommand(
+                            netId,
+                            varName,
+                            value)) {
+                        if (DBG) {
+                            Log.d(TAG, config.SSID + ": failed to set " + varName +
+                                  ": " + value);
+                        }
+                        break setVariables;
+                    }
                 }
             }
-
             return netId;
         }
 
@@ -1432,6 +1447,11 @@
     }
 
     private void updateWifiState() {
+        // send a message so it's all serialized
+        Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
+    }
+
+    private void doUpdateWifiState() {
         boolean wifiEnabled = getPersistedWifiEnabled();
         boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden;
         boolean lockHeld = mLocks.hasLocks();
@@ -1537,6 +1557,10 @@
                     sWakeLock.release();
                     break;
 
+                case MESSAGE_UPDATE_STATE:
+                    doUpdateWifiState();
+                    break;
+
                 case MESSAGE_DISABLE_WIFI:
                     // a non-zero msg.arg1 value means the "enabled" setting
                     // should be persisted
@@ -1815,6 +1839,19 @@
         }
     }
 
+    public void initializeMulticastFiltering() {
+        enforceMulticastChangePermission();
+
+        synchronized (mMulticasters) {
+            // if anybody had requested filters be off, leave off
+            if (mMulticasters.size() != 0) {
+                return;
+            } else {
+                WifiNative.startPacketFiltering();
+            }
+        }
+    }
+
     public void acquireMulticastLock(IBinder binder, String tag) {
         enforceMulticastChangePermission();
 
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 9acc97a..c92039a 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -9303,15 +9303,6 @@
                                     & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
                                 wallpaperMayChange = true;
                             }
-                            if (changed && !forceHiding
-                                    && (mCurrentFocus == null)
-                                    && (mFocusedApp != null)) {
-                                // It's possible that the last focus recalculation left no
-                                // current focused window even though the app has come to the
-                                // foreground already.  In this case, we make sure to recalculate
-                                // focus when we show a window.
-                                focusMayChange = true;
-                            }
                         }
                         
                         mPolicy.animatingWindowLw(w, attrs);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 1aa1c76..586f63f 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -207,6 +207,42 @@
     }
 
     /**
+     * Extracts the network address portion and canonicalize.
+     *
+     * This function is equivalent to extractNetworkPortion(), except
+     * for allowing the PLUS character to occur at arbitrary positions
+     * in the address portion, not just the first position.
+     *
+     * @hide
+     */
+    public static String extractNetworkPortionAlt(String phoneNumber) {
+        if (phoneNumber == null) {
+            return null;
+        }
+
+        int len = phoneNumber.length();
+        StringBuilder ret = new StringBuilder(len);
+        boolean haveSeenPlus = false;
+
+        for (int i = 0; i < len; i++) {
+            char c = phoneNumber.charAt(i);
+            if (c == '+') {
+                if (haveSeenPlus) {
+                    continue;
+                }
+                haveSeenPlus = true;
+            }
+            if (isDialable(c)) {
+                ret.append(c);
+            } else if (isStartsPostDial (c)) {
+                break;
+            }
+        }
+
+        return ret.toString();
+    }
+
+    /**
      * Strips separators from a phone number string.
      * @param phoneNumber phone number to strip.
      * @return phone string stripped of separators.
@@ -342,6 +378,8 @@
     compareLoosely(String a, String b) {
         int ia, ib;
         int matched;
+        int numNonDialableCharsInA = 0;
+        int numNonDialableCharsInB = 0;
 
         if (a == null || b == null) return a == b;
 
@@ -362,6 +400,7 @@
             if (!isDialable(ca)) {
                 ia--;
                 skipCmp = true;
+                numNonDialableCharsInA++;
             }
 
             cb = b.charAt(ib);
@@ -369,6 +408,7 @@
             if (!isDialable(cb)) {
                 ib--;
                 skipCmp = true;
+                numNonDialableCharsInB++;
             }
 
             if (!skipCmp) {
@@ -380,13 +420,16 @@
         }
 
         if (matched < MIN_MATCH) {
-            int aLen = a.length();
+            int effectiveALen = a.length() - numNonDialableCharsInA;
+            int effectiveBLen = b.length() - numNonDialableCharsInB;
 
-            // if the input strings match, but their lengths < MIN_MATCH,
-            // treat them as equal.
-            if (aLen == b.length() && aLen == matched) {
+
+            // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH,
+            // treat them as equal (i.e. 404-04 and 40404)
+            if (effectiveALen == effectiveBLen && effectiveALen == matched) {
                 return true;
             }
+
             return false;
         }
 
@@ -590,7 +633,7 @@
      */
     public static String
     toCallerIDMinMatch(String phoneNumber) {
-        String np = extractNetworkPortion(phoneNumber);
+        String np = extractNetworkPortionAlt(phoneNumber);
         return internalGetStrippedReversed(np, MIN_MATCH);
     }
 
@@ -603,7 +646,7 @@
      */
     public static String
     getStrippedReversed(String phoneNumber) {
-        String np = extractNetworkPortion(phoneNumber);
+        String np = extractNetworkPortionAlt(phoneNumber);
 
         if (np == null) return null;
 
@@ -1017,8 +1060,8 @@
      * Breaks the given number down and formats it according to the rules
      * for the country the number is from.
      *
-     * @param source the phone number to format
-     * @return a locally acceptable formatting of the input, or the raw input if
+     * @param source The phone number to format
+     * @return A locally acceptable formatting of the input, or the raw input if
      *  formatting rules aren't known for the number
      */
     public static String formatNumber(String source) {
@@ -1028,10 +1071,27 @@
     }
 
     /**
+     * Formats the given number with the given formatting type. Currently
+     * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type.
+     *
+     * @param source the phone number to format
+     * @param defaultFormattingType The default formatting rules to apply if the number does
+     * not begin with +<country_code>
+     * @return The phone number formatted with the given formatting type.
+     *
+     * @hide TODO:Shuold be unhidden.
+     */
+    public static String formatNumber(String source, int defaultFormattingType) {
+        SpannableStringBuilder text = new SpannableStringBuilder(source);
+        formatNumber(text, defaultFormattingType);
+        return text.toString();
+    }
+
+    /**
      * Returns the phone number formatting type for the given locale.
      *
      * @param locale The locale of interest, usually {@link Locale#getDefault()}
-     * @return the formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
+     * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting
      * rules are not known for the given locale
      */
     public static int getFormatTypeForLocale(Locale locale) {
@@ -1041,7 +1101,8 @@
     }
 
     /**
-     * Formats a phone number in-place. Currently only supports NANP formatting.
+     * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP}
+     * is supported as a second argument.
      *
      * @param text The number to be formatted, will be modified with the formatting
      * @param defaultFormattingType The default formatting rules to apply if the number does
@@ -1247,7 +1308,7 @@
 
         // Strip the separators from the number before comparing it
         // to the list.
-        number = extractNetworkPortion(number);
+        number = extractNetworkPortionAlt(number);
 
         // retrieve the list of emergency numbers
         String numbers = SystemProperties.get("ro.ril.ecclist");
@@ -1290,7 +1351,7 @@
 
         // Strip the separators from the number before comparing it
         // to the list.
-        number = extractNetworkPortion(number);
+        number = extractNetworkPortionAlt(number);
 
         // compare tolerates null so we need to make sure that we
         // don't return true when both are null.
diff --git a/telephony/java/com/android/internal/telephony/DriverCall.java b/telephony/java/com/android/internal/telephony/DriverCall.java
index 0d9a60f..66f6b9c 100644
--- a/telephony/java/com/android/internal/telephony/DriverCall.java
+++ b/telephony/java/com/android/internal/telephony/DriverCall.java
@@ -73,8 +73,7 @@
             if (p.hasMore()) {
                 // Some lame implementations return strings
                 // like "NOT AVAILABLE" in the CLCC line
-                ret.number = PhoneNumberUtils.extractNetworkPortion(
-                                    p.nextString());
+                ret.number = PhoneNumberUtils.extractNetworkPortionAlt(p.nextString());
 
                 if (ret.number.length() == 0) {
                     ret.number = null;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 3548cad..04a03b2 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -261,12 +261,6 @@
     }
 
     public ServiceState getServiceState() {
-        int roamInd = mSST.ss.getCdmaRoamingIndicator();
-        int defRoamInd = mSST.ss.getCdmaDefaultRoamingIndicator();
-
-        mSST.ss.setCdmaEriIconIndex(mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd));
-        mSST.ss.setCdmaEriIconMode(mEriManager.getCdmaEriIconMode(roamInd, defRoamInd));
-
         return mSST.ss;
     }
 
@@ -808,10 +802,6 @@
     }
 
      void notifyServiceStateChanged(ServiceState ss) {
-         // TODO this seems really inefficient.  Can't we calc this when the fundamentals change and store in the
-         // service state?
-         ss.setCdmaEriIconIndex(this.getCdmaEriIconIndex());
-         ss.setCdmaEriIconMode(this.getCdmaEriIconMode());
          super.notifyServiceStateChangedP(ss);
      }
 
@@ -1341,7 +1331,7 @@
     @Override
     public  boolean isOtaSpNumber(String dialStr){
         boolean isOtaSpNum = false;
-        String dialableStr = PhoneNumberUtils.extractNetworkPortion(dialStr);
+        String dialableStr = PhoneNumberUtils.extractNetworkPortionAlt(dialStr);
         if (dialableStr != null) {
             isOtaSpNum = isIs683OtaSpDialStr(dialableStr);
             if (isOtaSpNum == false) {
@@ -1354,9 +1344,7 @@
 
     @Override
     public int getCdmaEriIconIndex() {
-        int roamInd = getServiceState().getCdmaRoamingIndicator();
-        int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
-        return mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd);
+        return getServiceState().getCdmaEriIconIndex();
     }
 
     /**
@@ -1366,9 +1354,7 @@
      */
     @Override
     public int getCdmaEriIconMode() {
-        int roamInd = getServiceState().getCdmaRoamingIndicator();
-        int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
-        return mEriManager.getCdmaEriIconMode(roamInd, defRoamInd);
+        return getServiceState().getCdmaEriIconMode();
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
index f637d33..08946d2 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaConnection.java
@@ -154,7 +154,7 @@
         dialString = formatDialString(dialString);
         Log.d(LOG_TAG, "[CDMAConn] CdmaConnection:formated dialString=" + dialString);
 
-        this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
+        this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
         this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
 
         index = -1;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 3051e2f..03acf5c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -811,6 +811,12 @@
                 }
             }
 
+            int roamingIndicator = newSS.getCdmaRoamingIndicator();
+            newSS.setCdmaEriIconIndex(phone.mEriManager.getCdmaEriIconIndex(roamingIndicator,
+                    mDefaultRoamingIndicator));
+            newSS.setCdmaEriIconMode(phone.mEriManager.getCdmaEriIconMode(roamingIndicator,
+                    mDefaultRoamingIndicator));
+
             // NOTE: Some operator may require overriding mCdmaRoaming
             // (set by the modem), depending on the mRoamingIndicator.
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 5614c12..56499a8 100755
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -729,7 +729,7 @@
         }
 
         // Only look at the Network portion for mmi
-        String networkPortion = PhoneNumberUtils.extractNetworkPortion(newDialString);
+        String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
         GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this);
         if (LOCAL_DEBUG) Log.d(LOG_TAG,
                                "dialing w/ mmi '" + mmi + "'...");
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
index 445be39..4788a01 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmConnection.java
@@ -144,7 +144,7 @@
 
         this.dialString = dialString;
 
-        this.address = PhoneNumberUtils.extractNetworkPortion(dialString);
+        this.address = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
         this.postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
 
         index = -1;
diff --git a/test-runner/android/test/mock/MockCursor.java b/test-runner/android/test/mock/MockCursor.java
new file mode 100644
index 0000000..9b1c0ef
--- /dev/null
+++ b/test-runner/android/test/mock/MockCursor.java
@@ -0,0 +1,264 @@
+/*
+ * 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 android.test.mock;
+
+import android.content.ContentResolver;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.Map;
+
+/**
+ * <P>
+ * A mock {@link android.database.Cursor} class that isolates the test code from real
+ * Cursor implementation.
+ * </P>
+ * <P>
+ * All methods including ones related to querying the state of the cursor are
+ * are non-functional and throw {@link java.lang.UnsupportedOperationException}.
+ * </P>
+ */
+public class MockCursor implements Cursor {
+    public int getColumnCount() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public int getColumnIndex(String columnName) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public int getColumnIndexOrThrow(String columnName) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public String getColumnName(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public String[] getColumnNames() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public int getCount() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isNull(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public int getInt(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public long getLong(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public short getShort(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public float getFloat(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public double getDouble(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public byte[] getBlob(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public String getString(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public Bundle getExtras() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public int getPosition() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isAfterLast() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isBeforeFirst() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isFirst() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isLast() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean move(int offset) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean moveToFirst() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean moveToLast() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean moveToNext() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean moveToPrevious() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean moveToPosition(int position) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public void deactivate() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public void close() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean isClosed() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean requery() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public void registerContentObserver(ContentObserver observer) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public void registerDataSetObserver(DataSetObserver observer) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public Bundle respond(Bundle extras) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public boolean getWantsAllOnMoveCalls() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean commitUpdates() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean commitUpdates(Map<? extends Long, ? extends Map<String, Object>> values) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean hasUpdates() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public void setNotificationUri(ContentResolver cr, Uri uri) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean supportsUpdates() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean deleteRow() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public void unregisterContentObserver(ContentObserver observer) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public void unregisterDataSetObserver(DataSetObserver observer) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateBlob(int columnIndex, byte[] value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateDouble(int columnIndex, double value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateFloat(int columnIndex, float value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateInt(int columnIndex, int value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateLong(int columnIndex, long value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateShort(int columnIndex, short value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateString(int columnIndex, String value) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public boolean updateToNull(int columnIndex) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("deprecation")
+    public void abortUpdates() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk
index ced796a..757044f 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -3,7 +3,7 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner
+LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services
 
 LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
 
diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml
index d94327a..786178c 100644
--- a/tests/AndroidTests/AndroidManifest.xml
+++ b/tests/AndroidTests/AndroidManifest.xml
@@ -35,27 +35,27 @@
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.READ_SMS"/>
-    <uses-permission android:name="android.permission.WRITE_SMS"/>
-    <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
     <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+    <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
     <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_GSERVICES" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
-
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_GSERVICES" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
+    <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
-    <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
+
     <!-- InstrumentationTestRunner for AndroidTests -->
     <instrumentation android:name="android.test.InstrumentationTestRunner"
                      android:targetPackage="com.android.unit_tests"
diff --git a/tests/AndroidTests/res/raw/v21_org_before_title.vcf b/tests/AndroidTests/res/raw/v21_org_before_title.vcf
new file mode 100644
index 0000000..8ff1190
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_org_before_title.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD

+VERSION:2.1

+FN:Normal Guy

+ORG:Company;Organization;Devision;Room;Sheet No.

+TITLE:Excellent Janitor

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v21_pref_handling.vcf b/tests/AndroidTests/res/raw/v21_pref_handling.vcf
new file mode 100644
index 0000000..5105310
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_pref_handling.vcf
@@ -0,0 +1,15 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Smith
+TEL;HOME:1
+TEL;WORK;PREF:2
+TEL;ISDN:3
+EMAIL;PREF;HOME:test@example.com
+EMAIL;CELL;PREF:test2@examination.com
+ORG:Company
+TITLE:Engineer
+ORG:Mystery
+TITLE:Blogger
+ORG:Poetry
+TITLE:Poet
+END:VCARD
diff --git a/tests/AndroidTests/res/raw/v21_title_before_org.vcf b/tests/AndroidTests/res/raw/v21_title_before_org.vcf
new file mode 100644
index 0000000..9fdc738
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_title_before_org.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD

+VERSION:2.1

+FN:Nice Guy

+TITLE:Cool Title

+ORG:Marverous;Perfect;Great;Good;Bad;Poor

+END:VCARD

diff --git a/tests/AndroidTests/res/raw/v30_comma_separated.vcf b/tests/AndroidTests/res/raw/v30_comma_separated.vcf
new file mode 100644
index 0000000..98a7f20
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v30_comma_separated.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD

+VERSION:3.0

+N:F;G;M;;

+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com

+END:VCARD

diff --git a/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java
new file mode 100644
index 0000000..286f702
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java
@@ -0,0 +1,540 @@
+/*
+ * 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.unit_tests;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.DropBox;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+import com.android.server.DropBoxService;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.util.Random;
+import java.util.zip.GZIPOutputStream;
+
+/** Test {@link DropBox} functionality. */
+public class DropBoxTest extends AndroidTestCase {
+    public void tearDown() throws Exception {
+        Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+        override.putExtra(Settings.Gservices.DROPBOX_AGE_SECONDS, "");
+        override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, "");
+        override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "");
+        waitForBroadcast(override);
+    }
+
+    public void testAddText() throws Exception {
+        DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+        long before = System.currentTimeMillis();
+        Thread.sleep(5);
+        dropbox.addText("DropBoxTest", "TEST0");
+        Thread.sleep(5);
+        long between = System.currentTimeMillis();
+        Thread.sleep(5);
+        dropbox.addText("DropBoxTest", "TEST1");
+        dropbox.addText("DropBoxTest", "TEST2");
+        Thread.sleep(5);
+        long after = System.currentTimeMillis();
+
+        DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+        DropBox.Entry e2 = dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e2.getTimeMillis()));
+
+        assertTrue(e0.getTimeMillis() > before);
+        assertTrue(e0.getTimeMillis() < between);
+        assertTrue(e1.getTimeMillis() > between);
+        assertTrue(e1.getTimeMillis() < e2.getTimeMillis());
+        assertTrue(e2.getTimeMillis() < after);
+
+        assertEquals("TEST0", e0.getText(80));
+        assertEquals("TEST1", e1.getText(80));
+        assertEquals("TES", e2.getText(3));
+
+        e0.close();
+        e1.close();
+        e2.close();
+    }
+
+    public void testAddData() throws Exception {
+        DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+        long before = System.currentTimeMillis();
+        dropbox.addData("DropBoxTest", "TEST".getBytes(), 0);
+        long after = System.currentTimeMillis();
+
+        DropBox.Entry e = dropbox.getNextEntry("DropBoxTest", before);
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
+
+        assertEquals("DropBoxTest", e.getTag());
+        assertTrue(e.getTimeMillis() >= before);
+        assertEquals(0, e.getFlags());
+        assertTrue(null == e.getText(80));
+
+        byte[] buf = new byte[80];
+        assertEquals("TEST", new String(buf, 0, e.getInputStream().read(buf)));
+
+        e.close();
+    }
+
+    public void testAddFile() throws Exception {
+        File dir = getEmptyDir("testAddFile");
+        long before = System.currentTimeMillis();
+
+        File f0 = new File(dir, "f0.txt");
+        File f1 = new File(dir, "f1.txt.gz");
+        File f2 = new File(dir, "f2.dat");
+        File f3 = new File(dir, "f2.dat.gz");
+
+        FileWriter w0 = new FileWriter(f0);
+        GZIPOutputStream gz1 = new GZIPOutputStream(new FileOutputStream(f1));
+        FileOutputStream os2 = new FileOutputStream(f2);
+        GZIPOutputStream gz3 = new GZIPOutputStream(new FileOutputStream(f3));
+
+        w0.write("FILE0");
+        gz1.write("FILE1".getBytes());
+        os2.write("DATA2".getBytes());
+        gz3.write("DATA3".getBytes());
+
+        w0.close();
+        gz1.close();
+        os2.close();
+        gz3.close();
+
+        DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+        int mode = ParcelFileDescriptor.MODE_READ_ONLY;
+
+        ParcelFileDescriptor pfd0 = ParcelFileDescriptor.open(f0, mode);
+        ParcelFileDescriptor pfd1 = ParcelFileDescriptor.open(f1, mode);
+        ParcelFileDescriptor pfd2 = ParcelFileDescriptor.open(f2, mode);
+        ParcelFileDescriptor pfd3 = ParcelFileDescriptor.open(f3, mode);
+
+        dropbox.addFile("DropBoxTest", pfd0, DropBox.IS_TEXT);
+        dropbox.addFile("DropBoxTest", pfd1, DropBox.IS_TEXT | DropBox.IS_GZIPPED);
+        dropbox.addFile("DropBoxTest", pfd2, 0);
+        dropbox.addFile("DropBoxTest", pfd3, DropBox.IS_GZIPPED);
+
+        pfd0.close();
+        pfd1.close();
+        pfd2.close();
+        pfd3.close();
+
+        DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+        DropBox.Entry e2 = dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis());
+        DropBox.Entry e3 = dropbox.getNextEntry("DropBoxTest", e2.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e3.getTimeMillis()));
+
+        assertTrue(e0.getTimeMillis() > before);
+        assertTrue(e1.getTimeMillis() > e0.getTimeMillis());
+        assertTrue(e2.getTimeMillis() > e1.getTimeMillis());
+        assertTrue(e3.getTimeMillis() > e2.getTimeMillis());
+
+        assertEquals(DropBox.IS_TEXT, e0.getFlags());
+        assertEquals(DropBox.IS_TEXT, e1.getFlags());
+        assertEquals(0, e2.getFlags());
+        assertEquals(0, e3.getFlags());
+
+        assertEquals("FILE0", e0.getText(80));
+
+        byte[] buf1 = new byte[80];
+        assertEquals("FILE1", new String(buf1, 0, e1.getInputStream().read(buf1)));
+
+        assertTrue(null == e2.getText(80));
+        byte[] buf2 = new byte[80];
+        assertEquals("DATA2", new String(buf2, 0, e2.getInputStream().read(buf2)));
+
+        assertTrue(null == e3.getText(80));
+        byte[] buf3 = new byte[80];
+        assertEquals("DATA3", new String(buf3, 0, e3.getInputStream().read(buf3)));
+
+        e0.close();
+        e1.close();
+        e2.close();
+        e3.close();
+    }
+
+    public void testAddEntriesInTheFuture() throws Exception {
+        File dir = getEmptyDir("testAddEntriesInTheFuture");
+        long before = System.currentTimeMillis();
+
+        // Near future: should be allowed to persist
+        FileWriter w0 = new FileWriter(new File(dir, "DropBoxTest@" + (before + 5000) + ".txt"));
+        w0.write("FUTURE0");
+        w0.close();
+
+        // Far future: should be collapsed
+        FileWriter w1 = new FileWriter(new File(dir, "DropBoxTest@" + (before + 100000) + ".txt"));
+        w1.write("FUTURE1");
+        w1.close();
+
+        // Another far future item, this one gzipped
+        File f2 = new File(dir, "DropBoxTest@" + (before + 100001) + ".txt.gz");
+        GZIPOutputStream gz2 = new GZIPOutputStream(new FileOutputStream(f2));
+        gz2.write("FUTURE2".getBytes());
+        gz2.close();
+
+        // Tombstone in the far future
+        new FileOutputStream(new File(dir, "DropBoxTest@" + (before + 100002) + ".lost")).close();
+
+        DropBoxService service = new DropBoxService(getContext(), dir);
+        DropBox dropbox = new DropBox(service);
+
+        // Until a write, the timestamps are taken at face value
+        DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        DropBox.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+        DropBox.Entry e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry(null, e3.getTimeMillis()));
+
+        assertEquals("FUTURE0", e0.getText(80));
+        assertEquals("FUTURE1", e1.getText(80));
+        assertEquals("FUTURE2", e2.getText(80));
+        assertEquals(null, e3.getText(80));
+
+        assertEquals(before + 5000, e0.getTimeMillis());
+        assertEquals(before + 100000, e1.getTimeMillis());
+        assertEquals(before + 100001, e2.getTimeMillis());
+        assertEquals(before + 100002, e3.getTimeMillis());
+
+        e0.close();
+        e1.close();
+        e2.close();
+        e3.close();
+
+        // Write something to force a collapse
+        dropbox.addText("NotDropBoxTest", "FUTURE");
+        e0 = dropbox.getNextEntry(null, before);
+        e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+        e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e3.getTimeMillis()));
+
+        assertEquals("FUTURE0", e0.getText(80));
+        assertEquals("FUTURE1", e1.getText(80));
+        assertEquals("FUTURE2", e2.getText(80));
+        assertEquals(null, e3.getText(80));
+
+        assertEquals(before + 5000, e0.getTimeMillis());
+        assertEquals(before + 5001, e1.getTimeMillis());
+        assertEquals(before + 5002, e2.getTimeMillis());
+        assertEquals(before + 5003, e3.getTimeMillis());
+
+        e0.close();
+        e1.close();
+        e2.close();
+        e3.close();
+        service.stop();
+    }
+
+    public void testIsTagEnabled() throws Exception {
+        DropBox dropbox = (DropBox) getContext().getSystemService(Context.DROPBOX_SERVICE);
+        long before = System.currentTimeMillis();
+        dropbox.addText("DropBoxTest", "TEST-ENABLED");
+        assertTrue(dropbox.isTagEnabled("DropBoxTest"));
+
+        Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+        override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "disabled");
+        waitForBroadcast(override);
+
+        dropbox.addText("DropBoxTest", "TEST-DISABLED");
+        assertFalse(dropbox.isTagEnabled("DropBoxTest"));
+
+        override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+        override.putExtra(Settings.Gservices.DROPBOX_TAG_PREFIX + "DropBoxTest", "");
+        waitForBroadcast(override);
+
+        dropbox.addText("DropBoxTest", "TEST-ENABLED-AGAIN");
+        assertTrue(dropbox.isTagEnabled("DropBoxTest"));
+
+        DropBox.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBox.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis()));
+
+        assertEquals("TEST-ENABLED", e0.getText(80));
+        assertEquals("TEST-ENABLED-AGAIN", e1.getText(80));
+
+        e0.close();
+        e1.close();
+    }
+
+    public void testGetNextEntry() throws Exception {
+        File dir = getEmptyDir("testGetNextEntry");
+        DropBoxService service = new DropBoxService(getContext(), dir);
+        DropBox dropbox = new DropBox(service);
+
+        long before = System.currentTimeMillis();
+        dropbox.addText("DropBoxTest.A", "A0");
+        dropbox.addText("DropBoxTest.B", "B0");
+        dropbox.addText("DropBoxTest.A", "A1");
+
+        DropBox.Entry a0 = dropbox.getNextEntry("DropBoxTest.A", before);
+        DropBox.Entry a1 = dropbox.getNextEntry("DropBoxTest.A", a0.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest.A", a1.getTimeMillis()));
+
+        DropBox.Entry b0 = dropbox.getNextEntry("DropBoxTest.B", before);
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest.B", b0.getTimeMillis()));
+
+        DropBox.Entry x0 = dropbox.getNextEntry(null, before);
+        DropBox.Entry x1 = dropbox.getNextEntry(null, x0.getTimeMillis());
+        DropBox.Entry x2 = dropbox.getNextEntry(null, x1.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry(null, x2.getTimeMillis()));
+
+        assertEquals("DropBoxTest.A", a0.getTag());
+        assertEquals("DropBoxTest.A", a1.getTag());
+        assertEquals("A0", a0.getText(80));
+        assertEquals("A1", a1.getText(80));
+
+        assertEquals("DropBoxTest.B", b0.getTag());
+        assertEquals("B0", b0.getText(80));
+
+        assertEquals("DropBoxTest.A", x0.getTag());
+        assertEquals("DropBoxTest.B", x1.getTag());
+        assertEquals("DropBoxTest.A", x2.getTag());
+        assertEquals("A0", x0.getText(80));
+        assertEquals("B0", x1.getText(80));
+        assertEquals("A1", x2.getText(80));
+
+        a0.close();
+        a1.close();
+        b0.close();
+        x0.close();
+        x1.close();
+        x2.close();
+        service.stop();
+    }
+
+    public void testSizeLimits() throws Exception {
+        File dir = getEmptyDir("testSizeLimits");
+        int blockSize =  new StatFs(dir.getPath()).getBlockSize();
+
+        // Limit storage to 10 blocks
+        int kb = blockSize * 10 / 1024;
+        Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+        override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, Integer.toString(kb));
+        waitForBroadcast(override);
+
+        // Three tags using a total of 12 blocks:
+        // DropBoxTest0 [ ][ ]
+        // DropBoxTest1 [x][ ][    ][ ][xxx(20 blocks)xxx]
+        // DropBoxTest2 [xxxxxxxxxx][ ][ ]
+        //
+        // The blocks marked "x" will be removed due to storage restrictions.
+        // Use random fill (so it doesn't compress), subtract a little for gzip overhead
+
+        final int overhead = 64;
+        long before = System.currentTimeMillis();
+        DropBoxService service = new DropBoxService(getContext(), dir);
+        DropBox dropbox = new DropBox(service);
+
+        addRandomEntry(dropbox, "DropBoxTest0", blockSize - overhead);
+        addRandomEntry(dropbox, "DropBoxTest0", blockSize - overhead);
+
+        addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+        addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+        addRandomEntry(dropbox, "DropBoxTest1", blockSize * 2 - overhead);
+        addRandomEntry(dropbox, "DropBoxTest1", blockSize - overhead);
+        addRandomEntry(dropbox, "DropBoxTest1", blockSize * 20 - overhead);
+
+        addRandomEntry(dropbox, "DropBoxTest2", blockSize * 4 - overhead);
+        addRandomEntry(dropbox, "DropBoxTest2", blockSize - overhead);
+        addRandomEntry(dropbox, "DropBoxTest2", blockSize - overhead);
+
+        DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        DropBox.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+        DropBox.Entry e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+        DropBox.Entry e4 = dropbox.getNextEntry(null, e3.getTimeMillis());
+        DropBox.Entry e5 = dropbox.getNextEntry(null, e4.getTimeMillis());
+        DropBox.Entry e6 = dropbox.getNextEntry(null, e5.getTimeMillis());
+        DropBox.Entry e7 = dropbox.getNextEntry(null, e6.getTimeMillis());
+        DropBox.Entry e8 = dropbox.getNextEntry(null, e7.getTimeMillis());
+        DropBox.Entry e9 = dropbox.getNextEntry(null, e8.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry(null, e9.getTimeMillis()));
+
+        assertEquals("DropBoxTest0", e0.getTag());
+        assertEquals("DropBoxTest0", e1.getTag());
+        assertEquals(blockSize - overhead, getEntrySize(e0));
+        assertEquals(blockSize - overhead, getEntrySize(e1));
+
+        assertEquals("DropBoxTest1", e2.getTag());
+        assertEquals("DropBoxTest1", e3.getTag());
+        assertEquals("DropBoxTest1", e4.getTag());
+        assertEquals("DropBoxTest1", e5.getTag());
+        assertEquals("DropBoxTest1", e6.getTag());
+        assertEquals(-1, getEntrySize(e2));  // Tombstone
+        assertEquals(blockSize - overhead, getEntrySize(e3));
+        assertEquals(blockSize * 2 - overhead, getEntrySize(e4));
+        assertEquals(blockSize - overhead, getEntrySize(e5));
+        assertEquals(-1, getEntrySize(e6));
+
+        assertEquals("DropBoxTest2", e7.getTag());
+        assertEquals("DropBoxTest2", e8.getTag());
+        assertEquals("DropBoxTest2", e9.getTag());
+        assertEquals(-1, getEntrySize(e7));  // Tombstone
+        assertEquals(blockSize - overhead, getEntrySize(e8));
+        assertEquals(blockSize - overhead, getEntrySize(e9));
+
+        e0.close();
+        e1.close();
+        e2.close();
+        e3.close();
+        e4.close();
+        e5.close();
+        e6.close();
+        e7.close();
+        e8.close();
+        e9.close();
+
+        // Specifying a tag name skips tombstone records.
+
+        DropBox.Entry t0 = dropbox.getNextEntry("DropBoxTest1", before);
+        DropBox.Entry t1 = dropbox.getNextEntry("DropBoxTest1", t0.getTimeMillis());
+        DropBox.Entry t2 = dropbox.getNextEntry("DropBoxTest1", t1.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest1", t2.getTimeMillis()));
+
+        assertEquals("DropBoxTest1", t0.getTag());
+        assertEquals("DropBoxTest1", t1.getTag());
+        assertEquals("DropBoxTest1", t2.getTag());
+
+        assertEquals(blockSize - overhead, getEntrySize(t0));
+        assertEquals(blockSize * 2 - overhead, getEntrySize(t1));
+        assertEquals(blockSize - overhead, getEntrySize(t2));
+
+        t0.close();
+        t1.close();
+        t2.close();
+        service.stop();
+    }
+
+    public void testAgeLimits() throws Exception {
+        File dir = getEmptyDir("testAgeLimits");
+        int blockSize = new StatFs(dir.getPath()).getBlockSize();
+
+        // Limit storage to 10 blocks with an expiration of 1 second
+        int kb = blockSize * 10 / 1024;
+        Intent override = new Intent(Settings.Gservices.OVERRIDE_ACTION);
+        override.putExtra(Settings.Gservices.DROPBOX_AGE_SECONDS, "1");
+        override.putExtra(Settings.Gservices.DROPBOX_QUOTA_KB, Integer.toString(kb));
+        waitForBroadcast(override);
+
+        // Write one normal entry and another so big that it is instantly tombstoned
+        long before = System.currentTimeMillis();
+        DropBoxService service = new DropBoxService(getContext(), dir);
+        DropBox dropbox = new DropBox(service);
+
+        dropbox.addText("DropBoxTest", "TEST");
+        addRandomEntry(dropbox, "DropBoxTest", blockSize * 20);
+
+        // Verify that things are as expected
+        DropBox.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBox.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry(null, e1.getTimeMillis()));
+
+        assertEquals("TEST", e0.getText(80));
+        assertEquals(null, e1.getText(80));
+        assertEquals(-1, getEntrySize(e1));
+
+        e0.close();
+        e1.close();
+
+        // Wait a second and write another entry -- old ones should be expunged
+        Thread.sleep(2000);
+        dropbox.addText("DropBoxTest", "TEST1");
+
+        e0 = dropbox.getNextEntry(null, before);
+        assertTrue(null == dropbox.getNextEntry(null, e0.getTimeMillis()));
+        assertEquals("TEST1", e0.getText(80));
+        e0.close();
+    }
+
+    public void testCreateDropBoxWithInvalidDirectory() throws Exception {
+        // If created with an invalid directory, the DropBox should suffer quietly
+        // and fail all operations (this is how it survives a full disk).
+        // Once the directory becomes possible to create, it will start working.
+
+        File dir = new File(getEmptyDir("testCreateDropBoxWith"), "InvalidDirectory");
+        new FileOutputStream(dir).close();  // Create an empty file
+        DropBoxService service = new DropBoxService(getContext(), dir);
+        DropBox dropbox = new DropBox(service);
+
+        dropbox.addText("DropBoxTest", "should be ignored");
+        dropbox.addData("DropBoxTest", "should be ignored".getBytes(), 0);
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", 0));
+
+        dir.delete();  // Remove the file so a directory can be created
+        dropbox.addText("DropBoxTest", "TEST");
+        DropBox.Entry e = dropbox.getNextEntry("DropBoxTest", 0);
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest", e.getTimeMillis()));
+        assertEquals("DropBoxTest", e.getTag());
+        assertEquals("TEST", e.getText(80));
+        e.close();
+        service.stop();
+    }
+
+    private void addRandomEntry(DropBox dropbox, String tag, int size) throws Exception {
+        byte[] bytes = new byte[size];
+        new Random(System.currentTimeMillis()).nextBytes(bytes);
+
+        File f = new File(getEmptyDir("addRandomEntry"), "random.dat");
+        FileOutputStream os = new FileOutputStream(f);
+        os.write(bytes);
+        os.close();
+
+        ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+        dropbox.addFile(tag, fd, 0);
+        fd.close();
+    }
+
+    private int getEntrySize(DropBox.Entry e) throws Exception {
+        InputStream is = e.getInputStream();
+        if (is == null) return -1;
+        int length = 0;
+        while (is.read() != -1) length++;
+        return length;
+    }
+
+    private void waitForBroadcast(Intent intent) throws InterruptedException {
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            public synchronized void onReceive(Context context, Intent intent) { notify(); }
+        };
+
+        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
+        synchronized (receiver) { receiver.wait(); }
+    }
+
+    private void recursiveDelete(File file) {
+        if (!file.delete() && file.isDirectory()) {
+            for (File f : file.listFiles()) recursiveDelete(f);
+            file.delete();
+        }
+    }
+
+    private File getEmptyDir(String name) {
+        File dir = getContext().getDir("DropBoxTest." + name, 0);
+        for (File f : dir.listFiles()) recursiveDelete(f);
+        assertTrue(dir.listFiles().length == 0);
+        return dir;
+    }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java
new file mode 100644
index 0000000..e99e4cb
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/ContentValuesBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+
+/**
+ * ContentValues-like class which enables users to chain put() methods and restricts
+ * the other methods.
+ */
+/* package */ class ContentValuesBuilder {
+    private final ContentValues mContentValues;
+
+    public ContentValuesBuilder(final ContentValues contentValues) {
+        mContentValues = contentValues;
+    }
+
+    public ContentValuesBuilder put(String key, String value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Byte value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Short value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Integer value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Long value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Float value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Double value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, Boolean value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder put(String key, byte[] value) {
+        mContentValues.put(key, value);
+        return this;
+    }
+
+    public ContentValuesBuilder putNull(String key) {
+        mContentValues.putNull(key);
+        return this;
+    }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
index 0ee74df..d93a41b 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
@@ -16,19 +16,21 @@
 package com.android.unit_tests.vcard;
 
 import android.content.ContentValues;
-
-import org.apache.commons.codec.binary.Base64;
+import android.pim.vcard.ContactStruct;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
 
 /**
- * @hide old class just for test
+ * Previously used in main vCard handling code but now exists only for testing.
+ *
+ * Especially useful for testing parser code (VCardParser), since all properties can be
+ * checked via this class unlike {@link ContactStruct}, which only emits the result of
+ * interpretation of the content of each vCard. We cannot know whether vCard parser or
+ * ContactStruct is wrong withouth this class.
  */
 public class PropertyNode {
     public String propName;
@@ -101,6 +103,15 @@
     }
     
     @Override
+    public int hashCode() {
+        // vCard may contain more than one same line in one entry, while HashSet or any other
+        // library which utilize hashCode() does not honor that, so intentionally throw an
+        // Exception.
+        throw new UnsupportedOperationException(
+                "PropertyNode does not provide hashCode() implementation intentionally.");
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof PropertyNode)) {
             return false;
@@ -159,164 +170,4 @@
         builder.append(propValue);
         return builder.toString();
     }
-    
-    /**
-     * Encode this object into a string which can be decoded. 
-     */
-    public String encode() {
-        // PropertyNode#toString() is for reading, not for parsing in the future.
-        // We construct appropriate String here.
-        StringBuilder builder = new StringBuilder();
-        if (propName.length() > 0) {
-            builder.append("propName:[");
-            builder.append(propName);
-            builder.append("],");
-        }
-        int size = propGroupSet.size();
-        if (size > 0) {
-            Set<String> set = propGroupSet;
-            builder.append("propGroup:[");
-            int i = 0;
-            for (String group : set) {
-                // We do not need to double quote groups.
-                // group        = 1*(ALPHA / DIGIT / "-")
-                builder.append(group);
-                if (i < size - 1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-            builder.append("],");
-        }
-
-        if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) {
-            ContentValues values = paramMap;
-            builder.append("paramMap:[");
-            size = paramMap.size(); 
-            int i = 0;
-            for (Entry<String, Object> entry : values.valueSet()) {
-                // Assuming param-key does not contain NON-ASCII nor symbols.
-                //
-                // According to vCard 3.0:
-                // param-name   = iana-token / x-name
-                builder.append(entry.getKey());
-
-                // param-value may contain any value including NON-ASCIIs.
-                // We use the following replacing rule.
-                // \ -> \\
-                // , -> \,
-                // In String#replaceAll(), "\\\\" means a single backslash.
-                builder.append("=");
-                builder.append(entry.getValue().toString()
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size -1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-
-            Set<String> set = paramMap_TYPE;
-            size = paramMap_TYPE.size();
-            if (i > 0 && size > 0) {
-                builder.append(",");
-            }
-            i = 0;
-            for (String type : set) {
-                builder.append("TYPE=");
-                builder.append(type
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size - 1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-            builder.append("],");
-        }
-
-        size = propValue_vector.size();
-        if (size > 0) {
-            builder.append("propValue:[");
-            List<String> list = propValue_vector;
-            for (int i = 0; i < size; i++) {
-                builder.append(list.get(i)
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size -1) {
-                    builder.append(",");
-                }
-            }
-            builder.append("],");
-        }
-
-        return builder.toString();
-    }
-
-    public static PropertyNode decode(String encodedString) {
-        PropertyNode propertyNode = new PropertyNode();
-        String trimed = encodedString.trim();
-        if (trimed.length() == 0) {
-            return propertyNode;
-        }
-        String[] elems = trimed.split("],");
-        
-        for (String elem : elems) {
-            int index = elem.indexOf('[');
-            String name = elem.substring(0, index - 1);
-            Pattern pattern = Pattern.compile("(?<!\\\\),");
-            String[] values = pattern.split(elem.substring(index + 1), -1);
-            if (name.equals("propName")) {
-                propertyNode.propName = values[0];
-            } else if (name.equals("propGroupSet")) {
-                for (String value : values) {
-                    propertyNode.propGroupSet.add(value);
-                }
-            } else if (name.equals("paramMap")) {
-                ContentValues paramMap = propertyNode.paramMap;
-                Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE;
-                for (String value : values) {
-                    String[] tmp = value.split("=", 2);
-                    String mapKey = tmp[0];
-                    // \, -> ,
-                    // \\ -> \
-                    // In String#replaceAll(), "\\\\" means a single backslash.
-                    String mapValue =
-                        tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
-                    if (mapKey.equalsIgnoreCase("TYPE")) {
-                        paramMap_TYPE.add(mapValue);
-                    } else {
-                        paramMap.put(mapKey, mapValue);
-                    }
-                }
-            } else if (name.equals("propValue")) {
-                StringBuilder builder = new StringBuilder();
-                List<String> list = propertyNode.propValue_vector;
-                int length = values.length;
-                for (int i = 0; i < length; i++) {
-                    String normValue = values[i]
-                                              .replaceAll("\\\\,", ",")
-                                              .replaceAll("\\\\\\\\", "\\\\");
-                    list.add(normValue);
-                    builder.append(normValue);
-                    if (i < length - 1) {
-                        builder.append(";");
-                    }
-                }
-                propertyNode.propValue = builder.toString();
-            }
-        }
-        
-        // At this time, QUOTED-PRINTABLE is already decoded to Java String.
-        // We just need to decode BASE64 String to binary.
-        String encoding = propertyNode.paramMap.getAsString("ENCODING");
-        if (encoding != null &&
-                (encoding.equalsIgnoreCase("BASE64") ||
-                        encoding.equalsIgnoreCase("B"))) {
-            propertyNode.propValue_bytes =
-                Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes());
-        }
-        
-        return propertyNode;
-    }
 }
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
new file mode 100644
index 0000000..917b18e
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
@@ -0,0 +1,344 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class PropertyNodesVerifier extends VNodeBuilder {
+    private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
+    private final AndroidTestCase mAndroidTestCase;
+    private int mIndex;
+
+    public PropertyNodesVerifier(AndroidTestCase testCase) {
+        mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
+        mAndroidTestCase = testCase;
+    }
+
+    public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+        PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
+        mPropertyNodesVerifierElemList.add(elem);
+        return elem;
+    }
+
+    public void verify(int resId, int vCardType)
+            throws IOException, VCardException {
+        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
+    }
+
+    public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+        final VCardParser vCardParser;
+        if (VCardConfig.isV30(vCardType)) {
+            vCardParser = new VCardParser_V30(true);  // use StrictParsing
+        } else {
+            vCardParser = new VCardParser_V21();
+        }
+        try {
+            vCardParser.parse(is, this);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endRecord() {
+        super.endRecord();
+        mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+        mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+        mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+        mIndex++;
+    }
+}
+
+/**
+ * Utility class which verifies input VNode.
+ *
+ * This class first checks whether each propertyNode in the VNode is in the
+ * "ordered expected property list".
+ * If the node does not exist in the "ordered list", the class refers to
+ * "unorderd expected property set" and checks the node is expected somewhere.
+ */
+class PropertyNodesVerifierElem {
+    public static class TypeSet extends HashSet<String> {
+        public TypeSet(String ... array) {
+            super(Arrays.asList(array));
+        }
+    }
+
+    public static class GroupSet extends HashSet<String> {
+        public GroupSet(String ... array) {
+            super(Arrays.asList(array));
+        }
+    }
+
+    private final HashMap<String, List<PropertyNode>> mOrderedNodeMap;
+    // Intentionally use ArrayList instead of Set, assuming there may be more than one
+    // exactly same objects.
+    private final ArrayList<PropertyNode> mUnorderedNodeList;
+    private final TestCase mTestCase;
+
+    public PropertyNodesVerifierElem(TestCase testCase) {
+        mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
+        mUnorderedNodeList = new ArrayList<PropertyNode>();
+        mTestCase = testCase;
+    }
+
+    // WithOrder
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue) {
+        return addNodeWithOrder(propName, propValue, null, null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            ContentValues contentValues) {
+        return addNodeWithOrder(propName, propValue, null, null, contentValues, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList) {
+        return addNodeWithOrder(propName, propValue, propValueList, null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, List<String> propValueList) {
+        final String propValue = concatinateListWithSemiColon(propValueList);
+        return addNodeWithOrder(propName, propValue.toString(), propValueList,
+                null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            TypeSet paramMap_TYPE) {
+        return addNodeWithOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        final String propValue = concatinateListWithSemiColon(propValueList);
+        return addNodeWithOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        return addNodeWithOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList, byte[] propValue_bytes,
+            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+        PropertyNode propertyNode = new PropertyNode(propName,
+                propValue, propValueList, propValue_bytes,
+                paramMap, paramMap_TYPE, propGroupSet);
+        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+        if (expectedNodeList == null) {
+            expectedNodeList = new ArrayList<PropertyNode>();
+            mOrderedNodeMap.put(propName, expectedNodeList);
+        }
+        expectedNodeList.add(propertyNode);
+        return this;
+    }
+
+    // WithoutOrder
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue) {
+        return addNodeWithoutOrder(propName, propValue, null, null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            ContentValues contentValues) {
+        return addNodeWithoutOrder(propName, propValue, null, null, contentValues, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList) {
+        return addNodeWithoutOrder(propName, propValue, propValueList, null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, List<String> propValueList) {
+        final String propValue = concatinateListWithSemiColon(propValueList);
+        return addNodeWithoutOrder(propName, propValue, propValueList,
+                null, null, null, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            TypeSet paramMap_TYPE) {
+        return addNodeWithoutOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        final String propValue = concatinateListWithSemiColon(propValueList);
+        return addNodeWithoutOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        return addNodeWithoutOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList, byte[] propValue_bytes,
+            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+        mUnorderedNodeList.add(new PropertyNode(propName, propValue,
+                propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
+        return this;
+    }
+
+    public void verify(VNode vnode) {
+        for (PropertyNode actualNode : vnode.propList) {
+            verifyNode(actualNode.propName, actualNode);
+        }
+        if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
+            List<String> expectedProps = new ArrayList<String>();
+            for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
+                for (PropertyNode node : nodes) {
+                    if (!expectedProps.contains(node.propName)) {
+                        expectedProps.add(node.propName);
+                    }
+                }
+            }
+            for (PropertyNode node : mUnorderedNodeList) {
+                if (!expectedProps.contains(node.propName)) {
+                    expectedProps.add(node.propName);
+                }
+            }
+            mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+                    + " was not found.");
+        }
+    }
+
+    private void verifyNode(final String propName, final PropertyNode actualNode) {
+        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+        final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
+        if (size > 0) {
+            for (int i = 0; i < size; i++) {
+                PropertyNode expectedNode = expectedNodeList.get(i);
+                List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
+                if (expectedNode.propName.equals(propName)) {
+                    if (expectedNode.equals(actualNode)) {
+                        expectedNodeList.remove(i);
+                        if (expectedNodeList.size() == 0) {
+                            mOrderedNodeMap.remove(propName);
+                        }
+                        return;
+                    } else {
+                        expectedButDifferentValueList.add(expectedNode);
+                    }
+                }
+
+                // "actualNode" is not in ordered expected list.
+                // Try looking over unordered expected list.
+                if (tryFoundExpectedNodeFromUnorderedList(actualNode,
+                        expectedButDifferentValueList)) {
+                    return;
+                }
+
+                if (!expectedButDifferentValueList.isEmpty()) {
+                    // Same propName exists but with different value(s).
+                    failWithExpectedNodeList(propName, actualNode,
+                            expectedButDifferentValueList);
+                } else {
+                    // There's no expected node with same propName.
+                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+                }
+            }
+        } else {
+            List<PropertyNode> expectedButDifferentValueList =
+                new ArrayList<PropertyNode>();
+            if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
+                return;
+            } else {
+                if (!expectedButDifferentValueList.isEmpty()) {
+                    // Same propName exists but with different value(s).
+                    failWithExpectedNodeList(propName, actualNode,
+                            expectedButDifferentValueList);
+                } else {
+                    // There's no expected node with same propName.
+                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+                }
+            }
+        }
+    }
+
+    private String concatinateListWithSemiColon(List<String> array) {
+        StringBuffer buffer = new StringBuffer();
+        boolean first = true;
+        for (String propValueElem : array) {
+            if (first) {
+                first = false;
+            } else {
+                buffer.append(';');
+            }
+            buffer.append(propValueElem);
+        }
+
+        return buffer.toString();
+    }
+
+    private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode,
+            List<PropertyNode> expectedButDifferentValueList) {
+        final String propName = actualNode.propName;
+        int unorderedListSize = mUnorderedNodeList.size();
+        for (int i = 0; i < unorderedListSize; i++) {
+            PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i);
+            if (unorderedExpectedNode.propName.equals(propName)) {
+                if (unorderedExpectedNode.equals(actualNode)) {
+                    mUnorderedNodeList.remove(i);
+                    return true;
+                }
+                expectedButDifferentValueList.add(unorderedExpectedNode);
+            }
+        }
+        return false;
+    }
+
+    private void failWithExpectedNodeList(String propName, PropertyNode actualNode,
+            List<PropertyNode> expectedNodeList) {
+        StringBuilder builder = new StringBuilder();
+        for (PropertyNode expectedNode : expectedNodeList) {
+            builder.append("expected: ");
+            builder.append(expectedNode.toString());
+            builder.append("\n");
+        }
+        mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+                + builder.toString()
+                + "  actual: " + actualNode.toString());
+    }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
new file mode 100644
index 0000000..727d33b
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -0,0 +1,892 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+/**
+ * Tests for the code related to vCard exporter, inculding vCard composer.
+ * This test class depends on vCard importer code, so if tests for vCard importer fail,
+ * the result of this class will not be reliable.
+ */
+public class VCardExporterTests extends VCardTestsBase {
+    private static final byte[] sPhotoByteArray =
+        VCardImporterTests.sPhotoByteArrayForComplicatedCase;
+
+    public void testSimpleV21() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "Ando")
+                .put(StructuredName.GIVEN_NAME, "Roid");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, V21);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "Roid Ando")
+                .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", ""));
+        verifier.verify();
+    }
+
+    private void testStructuredNameBasic(int version) {
+        final boolean isV30 = VCardConfig.isV30(version);
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+                .put(StructuredName.PREFIX, "AppropriatePrefix")
+                .put(StructuredName.SUFFIX, "AppropriateSuffix")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("N",
+                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+                        + "AppropriatePrefix;AppropriateSuffix",
+                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+                .addNodeWithOrder("FN",
+                        "AppropriatePrefix AppropriateGivenName "
+                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+                .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+                .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+                .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+        if (isV30) {
+            elem.addNodeWithoutOrder("SORT-STRING",
+                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+                    + "AppropriatePhoneticFamily");
+        }
+
+        verifier.verify();
+    }
+
+    public void testStructuredNameBasicV21() {
+        testStructuredNameBasic(V21);
+    }
+
+    public void testStructuredNameBasicV30() {
+        testStructuredNameBasic(V30);
+    }
+
+    /**
+     * Test that only "primary" StructuredName is emitted, so that our vCard file
+     * will not confuse the external importer, assuming there may be some importer
+     * which presume that there's only one property toward each of  "N", "FN", etc.
+     * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
+     */
+    private void testStructuredNameUsePrimaryCommon(int version) {
+        final boolean isV30 = (version == V30);
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+                .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+                .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+        // With "IS_PRIMARY=1". This is what we should use.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+                .put(StructuredName.PREFIX, "AppropriatePrefix")
+                .put(StructuredName.SUFFIX, "AppropriateSuffix")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+                .put(StructuredName.IS_PRIMARY, 1);
+
+        // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+                .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+                .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+                .put(StructuredName.IS_PRIMARY, 1);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("N",
+                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+                        + "AppropriatePrefix;AppropriateSuffix",
+                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+                .addNodeWithOrder("FN",
+                        "AppropriatePrefix AppropriateGivenName "
+                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+                .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+                .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+                .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+        if (isV30) {
+            elem.addNodeWithoutOrder("SORT-STRING",
+                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+                    + "AppropriatePhoneticFamily");
+        }
+
+        verifier.verify();
+    }
+
+    public void testStructuredNameUsePrimaryV21() {
+        testStructuredNameUsePrimaryCommon(V21);
+    }
+
+    public void testStructuredNameUsePrimaryV30() {
+        testStructuredNameUsePrimaryCommon(V30);
+    }
+
+    /**
+     * Tests that only "super primary" StructuredName is emitted.
+     * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
+     */
+    private void testStructuredNameUseSuperPrimaryCommon(int version) {
+        final boolean isV30 = (version == V30);
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+                .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+                .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+        // With "IS_PRIMARY=1", but we should ignore this time.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+                .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+                .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+                .put(StructuredName.IS_PRIMARY, 1);
+
+        // With "IS_SUPER_PRIMARY=1". This is what we should use.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+                .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+                .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+                .put(StructuredName.PREFIX, "AppropriatePrefix")
+                .put(StructuredName.SUFFIX, "AppropriateSuffix")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+                .put(StructuredName.IS_SUPER_PRIMARY, 1);
+
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
+                .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
+                .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
+                .put(StructuredName.PREFIX, "DoNotEmitPrefix3")
+                .put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
+                .put(StructuredName.IS_PRIMARY, 1);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("N",
+                        "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+                        + "AppropriatePrefix;AppropriateSuffix",
+                        Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+                                "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+                .addNodeWithOrder("FN",
+                        "AppropriatePrefix AppropriateGivenName "
+                        + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+                .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+                .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+                .addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+        if (isV30) {
+            elem.addNodeWithoutOrder("SORT-STRING",
+                    "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+                    + " AppropriatePhoneticFamily");
+        }
+
+        verifier.verify();
+    }
+
+    public void testStructuredNameUseSuperPrimaryV21() {
+        testStructuredNameUseSuperPrimaryCommon(V21);
+    }
+
+    public void testStructuredNameUseSuperPrimaryV30() {
+        testStructuredNameUseSuperPrimaryCommon(V30);
+    }
+
+    public void testNickNameV30() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(Nickname.CONTENT_ITEM_TYPE)
+                .put(Nickname.NAME, "Nicky");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, V30);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+            .addNodeWithOrder("NICKNAME", "Nicky");
+
+        verifier.verify();
+    }
+
+    private void testPhoneBasicCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        resolver.buildContactEntry().buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME"));
+
+        verifier.verify();
+    }
+
+    public void testPhoneBasicV21() {
+        testPhoneBasicCommon(V21);
+    }
+
+    public void testPhoneBasicV30() {
+        testPhoneBasicCommon(V30);
+    }
+
+    /**
+     * Tests that vCard composer emits corresponding type param which we expect.
+     */
+    private void testPhoneVariousTypeSupport(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "10")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "20")
+                .put(Phone.TYPE, Phone.TYPE_WORK);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "30")
+                .put(Phone.TYPE, Phone.TYPE_FAX_HOME);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "40")
+                .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "50")
+                .put(Phone.TYPE, Phone.TYPE_MOBILE);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "60")
+                .put(Phone.TYPE, Phone.TYPE_PAGER);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "70")
+                .put(Phone.TYPE, Phone.TYPE_OTHER);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "80")
+                .put(Phone.TYPE, Phone.TYPE_CAR);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "90")
+                .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "100")
+                .put(Phone.TYPE, Phone.TYPE_ISDN);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "110")
+                .put(Phone.TYPE, Phone.TYPE_MAIN);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "120")
+                .put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "130")
+                .put(Phone.TYPE, Phone.TYPE_TELEX);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "140")
+                .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "150")
+                .put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "160")
+                .put(Phone.TYPE, Phone.TYPE_MMS);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("TEL", "10", new TypeSet("HOME"))
+                .addNodeWithoutOrder("TEL", "20", new TypeSet("WORK"))
+                .addNodeWithoutOrder("TEL", "30", new TypeSet("HOME", "FAX"))
+                .addNodeWithoutOrder("TEL", "40", new TypeSet("WORK", "FAX"))
+                .addNodeWithoutOrder("TEL", "50", new TypeSet("CELL"))
+                .addNodeWithoutOrder("TEL", "60", new TypeSet("PAGER"))
+                .addNodeWithoutOrder("TEL", "70", new TypeSet("VOICE"))
+                .addNodeWithoutOrder("TEL", "80", new TypeSet("CAR"))
+                .addNodeWithoutOrder("TEL", "90", new TypeSet("WORK", "PREF"))
+                .addNodeWithoutOrder("TEL", "100", new TypeSet("ISDN"))
+                .addNodeWithoutOrder("TEL", "110", new TypeSet("PREF"))
+                .addNodeWithoutOrder("TEL", "120", new TypeSet("FAX"))
+                .addNodeWithoutOrder("TEL", "130", new TypeSet("TLX"))
+                .addNodeWithoutOrder("TEL", "140", new TypeSet("WORK", "CELL"))
+                .addNodeWithoutOrder("TEL", "150", new TypeSet("WORK", "PAGER"))
+                .addNodeWithoutOrder("TEL", "160", new TypeSet("MSG"));
+        verifier.verify();
+    }
+
+    public void testPhoneVariousTypeSupportV21() {
+        testPhoneVariousTypeSupport(V21);
+    }
+
+    public void testPhoneVariousTypeSupportV30() {
+        testPhoneVariousTypeSupport(V30);
+    }
+
+    /**
+     * Tests that "PREF"s are emitted appropriately.
+     */
+    private void testPhonePrefHandlingCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "2")
+                .put(Phone.TYPE, Phone.TYPE_WORK)
+                .put(Phone.IS_PRIMARY, 1);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "3")
+                .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+                .put(Phone.IS_PRIMARY, 1);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "4")
+                .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("TEL", "4", new TypeSet("WORK", "FAX"))
+                .addNodeWithoutOrder("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
+                .addNodeWithoutOrder("TEL", "2", new TypeSet("WORK", "PREF"))
+                .addNodeWithoutOrder("TEL", "1", new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    public void testPhonePrefHandlingV21() {
+        testPhonePrefHandlingCommon(V21);
+    }
+
+    public void testPhonePrefHandlingV30() {
+        testPhonePrefHandlingCommon(V30);
+    }
+
+    private void testEmailBasicCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "sample@example.com");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+        verifier.addPropertyNodesVerifierWithEmptyName()
+            .addNodeWithoutOrder("EMAIL", "sample@example.com");
+
+        verifier.verify();
+    }
+
+    public void testEmailBasicV21() {
+        testEmailBasicCommon(V21);
+    }
+
+    public void testEmailBasicV30() {
+        testEmailBasicCommon(V30);
+    }
+
+    private void testEmailVariousTypeSupportCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_home@example.com")
+                .put(Email.TYPE, Email.TYPE_HOME);
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_work@example.com")
+                .put(Email.TYPE, Email.TYPE_WORK);
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_mobile@example.com")
+                .put(Email.TYPE, Email.TYPE_MOBILE);
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_other@example.com")
+                .put(Email.TYPE, Email.TYPE_OTHER);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "type_work@example.com", new TypeSet("WORK"))
+                .addNodeWithoutOrder("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
+                .addNodeWithoutOrder("EMAIL", "type_other@example.com");
+
+        verifier.verify();
+    }
+
+    public void testEmailVariousTypeSupportV21() {
+        testEmailVariousTypeSupportCommon(V21);
+    }
+
+    public void testEmailVariousTypeSupportV30() {
+        testEmailVariousTypeSupportCommon(V30);
+    }
+
+    private void testEmailPrefHandlingCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_home@example.com")
+                .put(Email.TYPE, Email.TYPE_HOME)
+                .put(Email.IS_PRIMARY, 1);
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "type_notype@example.com")
+                .put(Email.IS_PRIMARY, 1);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
+                .addNodeWithoutOrder("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
+
+        verifier.verify();
+    }
+
+    public void testEmailPrefHandlingV21() {
+        testEmailPrefHandlingCommon(V21);
+    }
+
+    public void testEmailPrefHandlingV30() {
+        testEmailPrefHandlingCommon(V30);
+    }
+
+    private void testPostalOnlyWithStructuredDataCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        // adr-value    = 0*6(text-value ";") text-value
+        //              ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
+        //              ; Country Name
+        resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "Pobox")
+                .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood")
+                .put(StructuredPostal.STREET, "Street")
+                .put(StructuredPostal.CITY, "City")
+                .put(StructuredPostal.REGION, "Region")
+                .put(StructuredPostal.POSTCODE, "100")
+                .put(StructuredPostal.COUNTRY, "Country");
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("ADR", "Pobox;Neighborhood;Street;City;Region;100;Country",
+                        Arrays.asList("Pobox", "Neighborhood", "Street", "City",
+                                "Region", "100", "Country"), new TypeSet("HOME"));
+
+        verifier.verify();
+    }
+
+    public void testPostalOnlyWithStructuredDataV21() {
+        testPostalOnlyWithStructuredDataCommon(V21);
+    }
+
+    public void testPostalOnlyWithStructuredDataV30() {
+        testPostalOnlyWithStructuredDataCommon(V30);
+    }
+
+    private void testPostalOnlyWithFormattedAddressCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                "Formatted address CA 123-334 United Statue");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
+                        Arrays.asList("", "Formatted address CA 123-334 United Statue",
+                                "", "", "", "", ""), new TypeSet("HOME"));
+
+        verifier.verify();
+    }
+
+    public void testPostalOnlyWithFormattedAddressV21() {
+        testPostalOnlyWithFormattedAddressCommon(V21);
+    }
+
+    public void testPostalOnlyWithFormattedAddressV30() {
+        testPostalOnlyWithFormattedAddressCommon(V30);
+    }
+
+    /**
+     * Tests that the vCard composer honors formatted data when it is available
+     * even when it is partial.
+     */
+    private void testPostalWithBothStructuredAndFormattedCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        resolver.buildContactEntry().buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "Pobox")
+                .put(StructuredPostal.COUNTRY, "Country")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "Formatted address CA 123-334 United Statue");  // Should be ignored
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("ADR", "Pobox;;;;;;Country",
+                        Arrays.asList("Pobox", "", "", "", "", "", "Country"),
+                        new TypeSet("HOME"));
+
+        verifier.verify();
+    }
+
+    public void testPostalWithBothStructuredAndFormattedV21() {
+        testPostalWithBothStructuredAndFormattedCommon(V21);
+    }
+
+    public void testPostalWithBothStructuredAndFormattedV30() {
+        testPostalWithBothStructuredAndFormattedCommon(V30);
+    }
+
+    private void testOrganizationCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "CompanyX")
+                .put(Organization.DEPARTMENT, "DepartmentY")
+                .put(Organization.TITLE, "TitleZ")
+                .put(Organization.JOB_DESCRIPTION, "Description Rambda")  // Ignored.
+                .put(Organization.OFFICE_LOCATION, "Mountain View")  // Ignored.
+                .put(Organization.PHONETIC_NAME, "PhoneticName!")  // Ignored
+                .put(Organization.SYMBOL, "(^o^)/~~");  // Ignore him (her).
+
+        entry.buildData(Organization.CONTENT_ITEM_TYPE)
+                .putNull(Organization.COMPANY)
+                .put(Organization.DEPARTMENT, "DepartmentXX")
+                .putNull(Organization.TITLE);
+
+        entry.buildData(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "CompanyXYZ")
+                .putNull(Organization.DEPARTMENT)
+                .put(Organization.TITLE, "TitleXYZYX");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+
+        // Currently we do not use group but depend on the order.
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithOrder("ORG", "CompanyX;DepartmentY",
+                        Arrays.asList("CompanyX", "DepartmentY"))
+                .addNodeWithOrder("TITLE", "TitleZ")
+                .addNodeWithOrder("ORG", "DepartmentXX")
+                .addNodeWithOrder("ORG", "CompanyXYZ")
+                .addNodeWithOrder("TITLE", "TitleXYZYX");
+
+        verifier.verify();
+    }
+
+    public void testOrganizationV21() {
+        testOrganizationCommon(V21);
+    }
+
+    public void testOrganizationV30() {
+        testOrganizationCommon(V30);
+    }
+
+    private void testImVariousTypeSupportCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+                .put(Im.DATA, "aim");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_MSN)
+                .put(Im.DATA, "msn");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
+                .put(Im.DATA, "yahoo");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
+                .put(Im.DATA, "skype");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_QQ)
+                .put(Im.DATA, "qq");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
+                .put(Im.DATA, "google talk");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
+                .put(Im.DATA, "icq");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
+                .put(Im.DATA, "jabber");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
+                .put(Im.DATA, "netmeeting");
+
+        // No determined way to express unknown type...
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("X-JABBER", "jabber")
+                .addNodeWithoutOrder("X-ICQ", "icq")
+                .addNodeWithoutOrder("X-GOOGLE-TALK", "google talk")
+                .addNodeWithoutOrder("X-QQ", "qq")
+                .addNodeWithoutOrder("X-SKYPE-USERNAME", "skype")
+                .addNodeWithoutOrder("X-YAHOO", "yahoo")
+                .addNodeWithoutOrder("X-MSN", "msn")
+                .addNodeWithoutOrder("X-NETMEETING", "netmeeting")
+                .addNodeWithoutOrder("X-AIM", "aim");
+
+        verifier.verify();
+    }
+
+    public void testImBasiV21() {
+        testImVariousTypeSupportCommon(V21);
+    }
+
+    public void testImBasicV30() {
+        testImVariousTypeSupportCommon(V30);
+    }
+
+    private void testImPrefHandlingCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+                .put(Im.DATA, "aim1");
+
+        entry.buildData(Im.CONTENT_ITEM_TYPE)
+                .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+                .put(Im.DATA, "aim2")
+                .put(Im.TYPE, Im.TYPE_HOME)
+                .put(Im.IS_PRIMARY, 1);
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("X-AIM", "aim1")
+                .addNodeWithoutOrder("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
+
+        verifier.verify();
+    }
+
+    public void testImPrefHandlingV21() {
+        testImPrefHandlingCommon(V21);
+    }
+
+    public void testImPrefHandlingV30() {
+        testImPrefHandlingCommon(V30);
+    }
+
+    private void testWebsiteCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Website.CONTENT_ITEM_TYPE)
+                .put(Website.URL, "http://website.example.android.com/index.html")
+                .put(Website.TYPE, Website.TYPE_BLOG);
+
+        entry.buildData(Website.CONTENT_ITEM_TYPE)
+                .put(Website.URL, "ftp://ftp.example.android.com/index.html")
+                .put(Website.TYPE, Website.TYPE_FTP);
+
+        // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("URL", "ftp://ftp.example.android.com/index.html")
+                .addNodeWithoutOrder("URL", "http://website.example.android.com/index.html");
+        verifier.verify();
+    }
+
+    public void testWebsiteV21() {
+        testWebsiteCommon(V21);
+    }
+
+    public void testWebsiteV30() {
+        testWebsiteCommon(V30);
+    }
+
+    private void testEventCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Event.CONTENT_ITEM_TYPE)
+                .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
+                .put(Event.START_DATE, "1982-06-16");
+        entry.buildData(Event.CONTENT_ITEM_TYPE)
+                .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+                .put(Event.START_DATE, "2008-10-22");
+        entry.buildData(Event.CONTENT_ITEM_TYPE)
+                .put(Event.TYPE, Event.TYPE_OTHER)
+                .put(Event.START_DATE, "2018-03-12");
+        entry.buildData(Event.CONTENT_ITEM_TYPE)
+                .put(Event.TYPE, Event.TYPE_CUSTOM)
+                .put(Event.LABEL, "The last day")
+                .put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
+        entry.buildData(Event.CONTENT_ITEM_TYPE)
+                .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+                .put(Event.START_DATE, "2009-05-19");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithoutOrder("BDAY", "2008-10-22");
+
+        verifier.verify();
+    }
+
+    public void testEventV21() {
+        testEventCommon(V21);
+    }
+
+    public void testEventV30() {
+        testEventCommon(V30);
+    }
+
+    private void testNoteCommon(int version) {
+        ExportTestResolver resolver = new ExportTestResolver();
+
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "note1");
+        entry.buildData(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "note2")
+                .put(Note.IS_PRIMARY, 1);  // Just ignored.
+
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithOrder("NOTE", "note1")
+                .addNodeWithOrder("NOTE", "note2");
+
+        verifier.verify();
+    }
+
+    public void testNoteV21() {
+        testNoteCommon(V21);
+    }
+
+    public void testNoteV30() {
+        testNoteCommon(V30);
+    }
+
+    private void testPhotoCommon(int version) {
+        final boolean isV30 = version == V30;
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContactEntry entry = resolver.buildContactEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "PhotoTest");
+        entry.buildData(Photo.CONTENT_ITEM_TYPE)
+                .put(Photo.PHOTO, sPhotoByteArray);
+
+        ContentValues contentValuesForPhoto = new ContentValues();
+        contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "PhotoTest")
+                .addNodeWithoutOrder("N", "PhotoTest;;;;",
+                        Arrays.asList("PhotoTest", "", "", "", ""))
+                .addNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
+                        contentValuesForPhoto, new TypeSet("JPEG"), null);
+
+        verifier.verify();
+    }
+
+    public void testPhotoV21() {
+        testPhotoCommon(V21);
+    }
+
+    public void testPhotoV30() {
+        testPhotoCommon(V30);
+    }
+
+    public void testV30HandleEscape() {
+        final int version = V30;
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\\")
+                .put(StructuredName.GIVEN_NAME, ";")
+                .put(StructuredName.MIDDLE_NAME, ",")
+                .put(StructuredName.PREFIX, "\n")
+                .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
+        VCardVerifier verifier = new VCardVerifier(resolver, version);
+        // Verifies the vCard String correctly escapes each character which must be escaped.
+        verifier.addLineVerifier()
+                .addExpected("N:\\\\;\\;;\\,;\\n;")
+                .addExpected("FN:[<{Unescaped:Asciis}>]");
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "[<{Unescaped:Asciis}>]")
+                .addNodeWithoutOrder("N", Arrays.asList("\\", ";", ",", "\n", ""));
+
+        verifier.verify();
+    }
+
+    /**
+     * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
+     * We use Android-specific "X-ANDROID-CUSTOM" property.
+     * This test verifies the functionality.
+     */
+    public void testNickNameV21() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(Nickname.CONTENT_ITEM_TYPE)
+                .put(Nickname.NAME, "Nicky");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, V21);
+        verifier.addPropertyNodesVerifierWithEmptyName()
+                .addNodeWithOrder("X-ANDROID-CUSTOM",
+                        Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
+        verifier.addImportVerifier()
+                .addExpected(Nickname.CONTENT_ITEM_TYPE)
+                        .put(Nickname.NAME, "Nicky");
+        verifier.verify();
+    }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
new file mode 100644
index 0000000..169ea71
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
@@ -0,0 +1,1025 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.exception.VCardException;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.unit_tests.R;
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+public class VCardImporterTests extends VCardTestsBase {
+    // Push data into int array at first since values like 0x80 are
+    // interpreted as int by the compiler and casting all of them is
+    // cumbersome...
+    private static final int[] sPhotoIntArrayForComplicatedCase = {
+        0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
+        0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
+        0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+        0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+        0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
+        0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
+        0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
+        0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
+        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
+        0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
+        0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+        0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+        0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
+        0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
+        0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
+        0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
+        0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
+        0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+        0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
+        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+        0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
+        0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
+        0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+        0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+        0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
+        0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
+        0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
+        0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
+        0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
+        0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
+        0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
+        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
+        0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
+        0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
+        0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
+        0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
+        0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+        0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
+        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
+        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
+        0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
+        0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+        0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
+        0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
+        0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
+        0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
+        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
+        0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
+        0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
+        0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+        0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
+        0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
+        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
+        0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
+        0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
+        0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+        0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+        0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
+        0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
+        0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+        0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
+        0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
+        0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
+        0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
+        0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
+        0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
+        0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
+        0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
+        0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+        0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
+        0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
+        0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+        0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
+        0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
+        0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+        0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
+        0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+        0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
+        0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+        0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
+        0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
+        0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
+        0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
+        0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+        0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
+        0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
+        0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+        0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+        0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
+        0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+        0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
+        0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+        0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+        0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+        0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+        0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+        0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+        0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
+        0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+        0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+        0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+        0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+        0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+        0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
+        0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+        0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+        0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
+        0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+        0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
+        0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
+        0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
+        0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+        0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+        0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+        0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+        0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+        0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+        0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
+        0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+        0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+        0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+        0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
+        0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+        0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
+        0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
+        0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
+        0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
+        0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
+        0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
+        0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
+        0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
+        0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
+        0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
+        0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
+        0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
+        0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
+        0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
+        0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
+        0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
+        0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
+        0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
+        0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
+        0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
+        0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
+        0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
+        0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
+        0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
+        0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
+        0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
+        0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
+        0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
+        0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
+        0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
+        0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
+        0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
+        0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
+        0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
+        0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
+        0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
+        0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
+        0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
+        0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
+        0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
+        0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
+        0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
+        0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
+        0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
+        0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
+        0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
+        0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
+        0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
+        0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
+        0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
+        0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
+        0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
+        0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
+        0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
+        0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
+        0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
+        0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
+        0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
+        0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
+        0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
+        0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
+        0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
+        0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
+        0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
+        0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
+        0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
+        0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
+        0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
+        0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
+        0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
+        0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
+        0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
+        0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
+        0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
+        0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
+        0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
+        0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
+        0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
+        0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
+        0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
+        0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
+        0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
+        0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
+        0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+        0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
+        0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
+        0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
+        0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+        0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
+        0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+        0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
+        0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+        0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+        0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+        0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+        0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+        0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+        0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+        0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
+        0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+        0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+        0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
+        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+        0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
+        0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+        0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+        0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+        0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+        0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
+        0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
+        0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+        0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+        0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+        0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+        0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+        0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+        0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+        0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
+        0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+        0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
+        0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
+        0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
+        0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
+        0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
+        0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
+        0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
+        0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
+        0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
+        0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
+        0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
+        0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
+        0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
+        0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
+        0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
+        0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
+        0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
+        0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
+        0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
+        0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
+        0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
+        0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
+        0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
+        0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
+        0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
+        0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
+        0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
+        0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
+        0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
+        0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
+        0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
+        0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
+        0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
+        0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
+        0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
+        0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
+        0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
+        0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
+        0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
+        0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
+        0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
+        0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
+        0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
+        0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
+        0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
+        0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
+        0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
+        0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
+        0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
+        0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
+        0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
+        0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
+        0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
+        0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
+        0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
+        0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
+        0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
+        0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
+        0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
+        0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
+        0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
+        0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
+        0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
+        0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
+        0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
+        0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
+        0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
+        0x0c, 0xd1, 0x00, 0xff, 0xd9};
+
+    /* package */ static final byte[] sPhotoByteArrayForComplicatedCase;
+
+    static {
+        final int length = sPhotoIntArrayForComplicatedCase.length;
+        sPhotoByteArrayForComplicatedCase = new byte[length];
+        for (int i = 0; i < length; i++) {
+            sPhotoByteArrayForComplicatedCase[i] = (byte)sPhotoIntArrayForComplicatedCase[i];
+        }
+    }
+
+    public void testV21SimpleCase1_Parsing() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
+        verifier.verify(R.raw.v21_simple_1, V21);
+    }
+
+    public void testV21SimpleCase1_Type_Generic() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        verifier.addImportVerifierElem()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                        .put(StructuredName.FAMILY_NAME, "Ando")
+                        .put(StructuredName.GIVEN_NAME, "Roid")
+                        .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+        verifier.verify(R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testV21SimpleCase1_Type_Japanese() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        verifier.addImportVerifierElem()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                        .put(StructuredName.FAMILY_NAME, "Ando")
+                        .put(StructuredName.GIVEN_NAME, "Roid")
+                        // If name-related strings only contains printable Ascii,
+                        // the order is remained to be US's:
+                        // "Prefix Given Middle Family Suffix"
+                        .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+        verifier.verify(R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    public void testV21SimpleCase2() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        verifier.addImportVerifierElem()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                        .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+        verifier.verify(R.raw.v21_simple_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testV21SimpleCase3() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        verifier.addImportVerifierElem()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                        .put(StructuredName.FAMILY_NAME, "Ando")
+                        .put(StructuredName.GIVEN_NAME, "Roid")
+                        // "FN" field should be prefered since it should contain the original
+                        // order intended by the author of the file.
+                        .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+        verifier.verify(R.raw.v21_simple_3, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    /**
+     * Tests ';' is properly handled by VCardParser implementation.
+     */
+    public void testV21BackslashCase_Parsing() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
+                        Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
+                .addNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
+        verifier.verify(R.raw.v21_backslash, V21);
+    }
+
+    /**
+     * Tests ContactStruct correctly ignores redundant fields in "N" property values and
+     * inserts name related data.
+     */
+    public void testV21BackslashCase() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        verifier.addImportVerifierElem()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                        // FAMILY_NAME is empty and removed in this test...
+                        .put(StructuredName.GIVEN_NAME, "A;B\\")
+                        .put(StructuredName.MIDDLE_NAME, "C\\;")
+                        .put(StructuredName.PREFIX, "D")
+                        .put(StructuredName.SUFFIX, ":E")
+                        .put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\");
+        verifier.verify(R.raw.v21_backslash, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testOrgBeforTitle() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.DISPLAY_NAME, "Normal Guy");
+
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Company")
+                .put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.")
+                .put(Organization.TITLE, "Excellent Janitor")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify(R.raw.v21_org_before_title, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testTitleBeforOrg() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.DISPLAY_NAME, "Nice Guy");
+
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Marverous")
+                .put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor")
+                .put(Organization.TITLE, "Cool Title")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify(R.raw.v21_title_before_org, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    /**
+     * Verifies that vCard importer correctly interpret "PREF" attribute to IS_PRIMARY.
+     * The data contain three cases: one "PREF", no "PREF" and multiple "PREF", in each type.
+     */
+    public void testV21PrefToIsPrimary() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.DISPLAY_NAME, "Smith");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "2")
+                .put(Phone.TYPE, Phone.TYPE_WORK)
+                .put(Phone.IS_PRIMARY, 1);
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "3")
+                .put(Phone.TYPE, Phone.TYPE_ISDN);
+
+        elem.addExpected(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "test@example.com")
+                .put(Email.TYPE, Email.TYPE_HOME)
+                .put(Email.IS_PRIMARY, 1);
+
+        elem.addExpected(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "test2@examination.com")
+                .put(Email.TYPE, Email.TYPE_MOBILE)
+                .put(Email.IS_PRIMARY, 1);
+
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Company")
+                .put(Organization.TITLE, "Engineer")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Mystery")
+                .put(Organization.TITLE, "Blogger")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Poetry")
+                .put(Organization.TITLE, "Poet")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify(R.raw.v21_pref_handling, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    /**
+     * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
+     */
+    public void testV21ComplicatedCase_Parsing() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
+                        Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
+                .addNodeWithOrder("FN", "Joe Due")
+                .addNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+                        Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
+                .addNodeWithOrder("ROLE", "Fish Cake Keeper!")
+                .addNodeWithOrder("TITLE", "Shrimp Man")
+                .addNodeWithOrder("X-CLASS", "PUBLIC")
+                .addNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE"))
+                .addNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE"))
+                .addNodeWithOrder("TEL", "0311111111", new TypeSet("CELL"))
+                .addNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO"))
+                .addNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE"))
+                .addNodeWithOrder("ADR",
+                        ";;100 Waters Edge;Baytown;LA;30314;United States of America",
+                        Arrays.asList("", "", "100 Waters Edge", "Baytown",
+                                "LA", "30314", "United States of America"),
+                                null, null, new TypeSet("WORK"), null)
+                .addNodeWithOrder("LABEL",
+                        "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited  States of America",
+                        null, null, mContentValuesForQP, new TypeSet("WORK"), null)
+                .addNodeWithOrder("ADR",
+                        ";;42 Plantation St.;Baytown;LA;30314;United States of America",
+                        Arrays.asList("", "", "42 Plantation St.", "Baytown",
+                                "LA", "30314", "United States of America"), null, null,
+                                new TypeSet("HOME"), null)
+                .addNodeWithOrder("LABEL",
+                        "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited  States of America",
+                        null, null, mContentValuesForQP,
+                        new TypeSet("HOME"), null)
+                .addNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
+                        new TypeSet("PREF", "INTERNET"))
+                .addNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
+                .addNodeWithOrder("NOTE", "The following note is the example from RFC 2045.")
+                .addNodeWithOrder("NOTE",
+                        "Now's the time for all folk to come to the aid of their country.",
+                        null, null, mContentValuesForQP, null, null)
+                .addNodeWithOrder("PHOTO", null,
+                        null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21,
+                        new TypeSet("JPEG"), null)
+                .addNodeWithOrder("X-ATTRIBUTE", "Some String")
+                .addNodeWithOrder("BDAY", "19800101")
+                .addNodeWithOrder("GEO", "35.6563854,139.6994233")
+                .addNodeWithOrder("URL", "http://www.example.com/")
+                .addNodeWithOrder("REV", "20080424T195243Z");
+        verifier.verify(R.raw.v21_complicated, V21);
+    }
+
+    /**
+     * Checks ContactStruct correctly inserts values in a complicated vCard
+     * into ContentResolver.
+     */
+    public void testV21ComplicatedCase() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "Gump")
+                .put(StructuredName.GIVEN_NAME, "Forrest")
+                .put(StructuredName.MIDDLE_NAME, "Hoge")
+                .put(StructuredName.PREFIX, "Pos")
+                .put(StructuredName.SUFFIX, "Tao")
+                .put(StructuredName.DISPLAY_NAME, "Joe Due");
+        
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.TYPE, Organization.TYPE_WORK)
+                .put(Organization.COMPANY, "Gump Shrimp Co.")
+                .put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper")
+                .put(Organization.TITLE, "Shrimp Man");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_WORK)
+                // Phone number is expected to be formated with NAMP format in default.
+                .put(Phone.NUMBER, "111-555-1212");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_HOME)
+                .put(Phone.NUMBER, "404-555-1212");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_MOBILE)
+                .put(Phone.NUMBER, "031-111-1111");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "VIDEO")
+                .put(Phone.NUMBER, "032-222-2222");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "VOICE")
+                .put(Phone.NUMBER, "033-333-3333");
+
+        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+                .put(StructuredPostal.COUNTRY, "United States of America")
+                .put(StructuredPostal.POSTCODE, "30314")
+                .put(StructuredPostal.REGION, "LA")
+                .put(StructuredPostal.CITY, "Baytown")
+                .put(StructuredPostal.STREET, "100 Waters Edge")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "100 Waters Edge Baytown LA 30314 United States of America");
+
+        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+                .put(StructuredPostal.COUNTRY, "United States of America")
+                .put(StructuredPostal.POSTCODE, "30314")
+                .put(StructuredPostal.REGION, "LA")
+                .put(StructuredPostal.CITY, "Baytown")
+                .put(StructuredPostal.STREET, "42 Plantation St.")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "42 Plantation St. Baytown LA 30314 United States of America");
+
+        elem.addExpected(Email.CONTENT_ITEM_TYPE)
+                // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET"
+                .put(Email.TYPE, Email.TYPE_CUSTOM)
+                .put(Email.LABEL, "INTERNET")
+                .put(Email.DATA, "forrestgump@walladalla.com")
+                .put(Email.IS_PRIMARY, 1);
+
+        elem.addExpected(Email.CONTENT_ITEM_TYPE)
+                .put(Email.TYPE, Email.TYPE_MOBILE)
+                .put(Email.DATA, "cell@example.com");
+
+        elem.addExpected(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "The following note is the example from RFC 2045.");
+
+        elem.addExpected(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE,
+                        "Now's the time for all folk to come to the aid of their country.");
+
+        elem.addExpected(Photo.CONTENT_ITEM_TYPE)
+                // No information about its image format can be inserted.
+                .put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase);
+
+        elem.addExpected(Event.CONTENT_ITEM_TYPE)
+                .put(Event.START_DATE, "19800101")
+                .put(Event.TYPE, Event.TYPE_BIRTHDAY);
+
+        elem.addExpected(Website.CONTENT_ITEM_TYPE)
+                .put(Website.URL, "http://www.example.com/")
+                .put(Website.TYPE, Website.TYPE_HOMEPAGE);
+        verifier.verify(R.raw.v21_complicated, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testV30Simple_Parsing() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "3.0")
+                .addNodeWithOrder("FN", "And Roid")
+                .addNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", ""))
+                .addNodeWithOrder("ORG", "Open;Handset; Alliance",
+                        Arrays.asList("Open", "Handset", " Alliance"))
+                .addNodeWithOrder("SORT-STRING", "android")
+                .addNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE"))
+                .addNodeWithOrder("CLASS", "PUBLIC")
+                .addNodeWithOrder("X-GNO", "0")
+                .addNodeWithOrder("X-GN", "group0")
+                .addNodeWithOrder("X-REDUCTION", "0")
+                .addNodeWithOrder("REV", "20081031T065854Z");
+        verifier.verify(R.raw.v30_simple, V30);
+    }
+
+    public void testV30Simple() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "And")
+                .put(StructuredName.GIVEN_NAME, "Roid")
+                .put(StructuredName.DISPLAY_NAME, "And Roid")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "android");
+        elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+                .put(Organization.COMPANY, "Open")
+                .put(Organization.DEPARTMENT, "Handset  Alliance")
+                .put(Organization.TYPE, Organization.TYPE_WORK);
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "VOICE")
+                .put(Phone.NUMBER, "030-000-0000")
+                .put(Phone.IS_PRIMARY, 1);
+        verifier.verify(R.raw.v30_simple, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+    }
+
+    public void testV21Japanese1_Parsing() throws IOException, VCardException {
+        // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
+        // vCard 2.1/3.0 specification does not allow multiple values.
+        // Do not need to handle it as multiple values.
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
+                .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+                        null, null, mContentValuesForSJis,
+                        new TypeSet("X-IRMC-N"), null)
+                .addNodeWithOrder("TEL", "0300000000", null, null, null,
+                        new TypeSet("VOICE", "PREF"), null);
+        verifier.verify(R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    private void testV21Japanese1Common(int resId, int vcardType, boolean japanese)
+            throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+                // While vCard parser does not split "SOUND" property values,
+                // ContactStruct care it.
+                .put(StructuredName.PHONETIC_FAMILY_NAME,
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E");
+
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                // Phone number formatting is different.
+                .put(Phone.NUMBER, (japanese ? "03-0000-0000" : "030-000-0000"))
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "VOICE")
+                .put(Phone.IS_PRIMARY, 1);
+        verifier.verify(resId, vcardType);
+    }
+
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
+     */
+    public void testV21Japanese1_Type_Generic_Utf8() throws IOException, VCardException {
+        testV21Japanese1Common(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
+    }
+
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
+     */
+    public void testV21Japanese1_Type_Japanese_Sjis() throws IOException, VCardException {
+        testV21Japanese1Common(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
+    }
+
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with
+     * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
+     * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
+     */
+    public void testV21Japanese1_Type_Japanese_Utf8() throws IOException, VCardException {
+        testV21Japanese1Common(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true);
+    }
+
+    public void testV21Japanese2_Parsing() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+                        Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+                                "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+                        null, null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
+                        null, null, mContentValuesForSJis,
+                        new TypeSet("X-IRMC-N"), null)
+                .addNodeWithOrder("ADR",
+                        ";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
+                        "\u968E;;;;150-8512;",
+                        Arrays.asList("",
+                                "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                                "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                                "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+                                "\u0036\u968E", "", "", "", "150-8512", ""),
+                        null, mContentValuesForQPAndSJis, new TypeSet("HOME"), null)
+                .addNodeWithOrder("NOTE", "\u30E1\u30E2", null, null,
+                        mContentValuesForQPAndSJis, null, null);
+        verifier.verify(R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    public void testV21Japanese2_Type_Generic_Utf8() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
+                .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031")
+                .put(StructuredName.DISPLAY_NAME,
+                        "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031")
+                // ContactStruct should correctly split "SOUND" property into several elements,
+                // even though VCardParser side does not care it.
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF71\uFF9D\uFF84\uFF9E\uFF73")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF9B\uFF72\uFF84\uFF9E\u0031");
+
+        elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POSTCODE, "150-8512")
+                .put(StructuredPostal.NEIGHBORHOOD,
+                        "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+                        "\u0036\u968E")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+                        "\u0036\u968E 150-8512")
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+        elem.addExpected(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "\u30E1\u30E2");
+        verifier.verify(R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public void testV21MultipleEntryCase_Parse() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+                        null, null, mContentValuesForSJis,
+                        new TypeSet("X-IRMC-N"), null)
+                .addNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET"))
+                .addNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL"))
+                .addNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
+                .addNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
+
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+                        null, null, mContentValuesForSJis,
+                        new TypeSet("X-IRMC-N"), null)
+                .addNodeWithOrder("TEL", "13", new TypeSet("MODEM"))
+                .addNodeWithOrder("TEL", "14", new TypeSet("PAGER"))
+                .addNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
+                .addNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
+
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithOrder("SOUND",
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+                        null, null, mContentValuesForSJis,
+                        new TypeSet("X-IRMC-N"), null)
+                .addNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY"))
+                .addNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND"))
+                .addNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS"))
+                .addNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT"));
+        verifier.verify(R.raw.v21_multiple_entry, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    public void testV21MultipleEntryCase() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+                .put(StructuredName.PHONETIC_FAMILY_NAME,
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-SECRET")
+                .put(Phone.NUMBER, "9");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-HOTEL")
+                .put(Phone.NUMBER, "10");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-SCHOOL")
+                .put(Phone.NUMBER, "11");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+                .put(Phone.NUMBER, "12");
+
+        elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+                .put(StructuredName.PHONETIC_FAMILY_NAME,
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "MODEM")
+                .put(Phone.NUMBER, "13");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_PAGER)
+                .put(Phone.NUMBER, "14");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-FAMILY")
+                .put(Phone.NUMBER, "15");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-GIRL")
+                .put(Phone.NUMBER, "16");
+
+        elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+                .put(StructuredName.PHONETIC_FAMILY_NAME,
+                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-BOY")
+                .put(Phone.NUMBER, "17");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-FRIEND")
+                .put(Phone.NUMBER, "18");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-PHS")
+                .put(Phone.NUMBER, "19");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "NEC-RESTAURANT")
+                .put(Phone.NUMBER, "20");
+        verifier.verify(R.raw.v21_multiple_entry, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    public void testPagerV30_Parse() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "3.0")
+                .addNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
+                .addNodeWithOrder("TEL", "6101231234@pagersample.com",
+                        new TypeSet("WORK", "MSG", "PAGER"));
+        verifier.verify(R.raw.v30_comma_separated, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+    }
+
+    public void testPagerV30() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "F")
+                .put(StructuredName.MIDDLE_NAME, "M")
+                .put(StructuredName.GIVEN_NAME, "G")
+                .put(StructuredName.DISPLAY_NAME, "G M F");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_PAGER)
+                .put(Phone.NUMBER, "6101231234@pagersample.com");
+        verifier.verify(R.raw.v30_comma_separated, VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+    }
+}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java
new file mode 100644
index 0000000..4b65008
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java
@@ -0,0 +1,109 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardJapanizationTests extends VCardTestsBase {
+    private void testNameUtf8Common(int vcardType) {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+                .put(StructuredName.MIDDLE_NAME, "B")
+                .put(StructuredName.PREFIX, "Dr.")
+                .put(StructuredName.SUFFIX, "Ph.D");
+
+        VCardVerifier verifier = new VCardVerifier(resolver, vcardType);
+        ContentValues contentValues =
+            (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+                        contentValues)
+                .addNodeWithoutOrder("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+                        Arrays.asList(
+                                "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+                                null, contentValues, null, null);
+        verifier.verify();
+    }
+
+    public void testNameUtf8V21() {
+        testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+    }
+
+    public void testNameUtf8V30() {
+        testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+    }
+
+    public void testNameShiftJis() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+                .put(StructuredName.MIDDLE_NAME, "B")
+                .put(StructuredName.PREFIX, "Dr.")
+                .put(StructuredName.SUFFIX, "Ph.D");
+
+        VCardVerifier verifier = new VCardVerifier(resolver,
+                VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+                        mContentValuesForSJis)
+                .addNodeWithoutOrder("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+                        Arrays.asList(
+                                "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+                                null, mContentValuesForSJis, null, null);
+        verifier.verify();
+    }
+
+    /**
+     * DoCoMo phones require all name elements should be in "family name" field.
+     */
+    public void testNameDoCoMo() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        resolver.buildContactEntry().buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+                .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+                .put(StructuredName.MIDDLE_NAME, "B")
+                .put(StructuredName.PREFIX, "Dr.")
+                .put(StructuredName.SUFFIX, "Ph.D");
+
+        VCardVerifier verifier = new VCardVerifier(resolver,
+                VCardConfig.VCARD_TYPE_DOCOMO);
+        final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("N", fullName + ";;;;",
+                        Arrays.asList(fullName, "", "", "", ""),
+                        null, mContentValuesForSJis, null, null)
+                .addNodeWithoutOrder("FN", fullName, mContentValuesForSJis)
+                .addNodeWithoutOrder("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("ADR", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "");
+        verifier.verify();
+    }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java
new file mode 100644
index 0000000..f3d1c5e
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestSuite.java
@@ -0,0 +1,31 @@
+/*
+ * 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.unit_tests.vcard;
+
+import com.android.unit_tests.AndroidTests;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+import junit.framework.TestSuite;
+
+public class VCardTestSuite extends TestSuite {
+    public static TestSuite suite() {
+        TestSuiteBuilder suiteBuilder = new TestSuiteBuilder(AndroidTests.class);
+        suiteBuilder.includeAllPackagesUnderHere();
+        return suiteBuilder.build();
+    }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java
deleted file mode 100644
index 7589ba8..0000000
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTests.java
+++ /dev/null
@@ -1,923 +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.
- */
-
-package com.android.unit_tests.vcard;
-
-import android.content.ContentValues;
-import android.pim.vcard.ContactStruct;
-import android.pim.vcard.EntryHandler;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.exception.VCardException;
-import android.test.AndroidTestCase;
-
-import com.android.unit_tests.R;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-
-public class VCardTests extends AndroidTestCase {
-
-    // TODO: Use EntityIterator, which is added in Eclair.
-    private static class EntryHolder implements EntryHandler {
-        public List<ContactStruct> contacts = new ArrayList<ContactStruct>();
-        public void onParsingStart() {
-        }
-        public void onEntryCreated(ContactStruct contactStruct) {
-            contacts.add(contactStruct);
-        }
-        public void onParsingEnd() {
-        }
-    }
-    /*
-    static void verify(ContactStruct expected, ContactStruct actual) {
-        if (!equalsString(expected.getName(), actual.getName())) {
-            fail(String.format("Names do not equal: \"%s\" != \"%s\"",
-                    expected.getName(), actual.getName()));
-        }
-        if (!equalsString(
-                expected.getPhoneticName(), actual.getPhoneticName())) {
-            fail(String.format("Phonetic names do not equal: \"%s\" != \"%s\"",
-                    expected.getPhoneticName(), actual.getPhoneticName()));
-        }
-        {
-            final byte[] expectedPhotoBytes = expected.getPhotoBytes();
-            final byte[] actualPhotoBytes = actual.getPhotoBytes();
-            if (!((expectedPhotoBytes == null && actualPhotoBytes == null) ||
-                    Arrays.equals(expectedPhotoBytes, actualPhotoBytes))) {
-                fail("photoBytes is not equal.");
-            }
-        }
-        verifyInternal(expected.getNotes(), actual.getNotes(), "notes");
-        verifyInternal(expected.getPhoneList(), actual.getPhoneList(), "phones");
-        verifyInternal(expected.getContactMethodList(), actual.getContactMethodList(),
-                "contact lists");
-        verifyInternal(expected.getOrganizationList(), actual.getOrganizationList(),
-                "organizations");
-        {
-            final Map<String, List<String>> expectedMap =
-                expected.getExtensionMap();
-            final Map<String, List<String>> actualMap =
-                actual.getExtensionMap();
-            if (verifySize((expectedMap == null ? 0 : expectedMap.size()),
-                    (actualMap == null ? 0 : actualMap.size()), "extensions") > 0) {
-                for (String key : expectedMap.keySet()) {
-                    if (!actualMap.containsKey(key)) {
-                        fail(String.format(
-                                "Actual does not have %s extension while expected has",
-                                key));
-                    }
-                    final List<String> expectedList = expectedMap.get(key);
-                    final List<String> actualList = actualMap.get(key);
-                    verifyInternal(expectedList, actualList,
-                            String.format("extension \"%s\"", key));
-                }
-            }
-        }
-    }
-    
-    private static boolean equalsString(String a, String b) {
-        if (a == null || a.length() == 0) {
-            return b == null || b.length() == 0;
-        } else {
-            return a.equals(b);
-        }
-    }
-    
-    private static int verifySize(int expectedSize, int actualSize, String name) {
-        if (expectedSize != actualSize) {
-            fail(String.format("Size of %s is different: %d != %d", 
-                    name, expectedSize, actualSize));
-        }
-        return expectedSize;
-    }
-        
-    private static <T> void verifyInternal(final List<T> expected, final List<T> actual,
-            String name) {
-        if(verifySize((expected == null ? 0 : expected.size()),
-                (actual == null ? 0 : actual.size()), name) > 0) {
-            int size = expected.size();
-            for (int i = 0; i < size; i++) {
-                final T expectedObj = expected.get(i);
-                final T actualObj = actual.get(i);
-                if (!expected.equals(actual)) {
-                    fail(String.format("The %i %s are different: %s != %s",
-                            i, name, expectedObj, actualObj));
-                }
-            }
-        }
-    }*/
-
-    private class PropertyNodesVerifier {
-        private HashMap<String, ArrayList<PropertyNode>> mPropertyNodeMap;
-        public PropertyNodesVerifier(PropertyNode... nodes) {
-            mPropertyNodeMap = new HashMap<String, ArrayList<PropertyNode>>();
-            for (PropertyNode propertyNode : nodes) {
-                String propName = propertyNode.propName;
-                ArrayList<PropertyNode> expectedNodes =
-                    mPropertyNodeMap.get(propName);
-                if (expectedNodes == null) {
-                    expectedNodes = new ArrayList<PropertyNode>();
-                    mPropertyNodeMap.put(propName, expectedNodes);
-                }
-                expectedNodes.add(propertyNode);
-            }
-        }
-        
-        public void verify(VNode vnode) {
-            for (PropertyNode propertyNode : vnode.propList) {
-                String propName = propertyNode.propName;
-                ArrayList<PropertyNode> nodes = mPropertyNodeMap.get(propName);
-                if (nodes == null) {
-                    fail("Unexpected propName \"" + propName + "\" exists.");
-                }
-                boolean successful = false;
-                int size = nodes.size();
-                for (int i = 0; i < size; i++) {
-                    PropertyNode expectedNode = nodes.get(i);
-                    if (expectedNode.propName.equals(propName)) {
-                        if (expectedNode.equals(propertyNode)) {
-                            successful = true;
-                            nodes.remove(i);
-                            if (nodes.size() == 0) {
-                                mPropertyNodeMap.remove(propName);
-                            }
-                            break;
-                        } else {
-                            fail("Property \"" + propName + "\" has wrong value.\n" 
-                                    + "expected: " + expectedNode.toString() 
-                                    + "\n  actual: " + propertyNode.toString());
-                        }
-                    }
-                }
-                if (!successful) {
-                    fail("Unexpected property \"" + propName + "\" exists.");
-                }
-            }
-            if (mPropertyNodeMap.size() != 0) {
-                ArrayList<String> expectedProps = new ArrayList<String>();
-                for (ArrayList<PropertyNode> nodes : mPropertyNodeMap.values()) {
-                    for (PropertyNode node : nodes) {
-                        expectedProps.add(node.propName);
-                    }
-                }
-                fail("expected props " + Arrays.toString(expectedProps.toArray()) +
-                        " was not found");
-            }
-        }
-    }
-    
-    /*
-    public void testV21SimpleCase1_1() throws IOException, VCardException {
-        VCardParser parser = new VCardParser_V21();
-        VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
-        EntryHolder holder = new EntryHolder();
-        builder.addEntryHandler(holder);
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, holder.contacts.size());
-        verify(new ContactStruct("Roid Ando", null,
-                null, null, null, null, null, null),
-                holder.contacts.get(0));
-    }
-    
-    public void testV21SimpleCase1_2() throws IOException, VCardException {
-        VCardParser parser = new VCardParser_V21();
-        VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_JAPANESE);
-        EntryHolder holder = new EntryHolder();
-        builder.addEntryHandler(holder);
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, holder.contacts.size());
-        verify(new ContactStruct("Ando Roid", null,
-                null, null, null, null, null, null),
-                holder.contacts.get(0));
-    }
-    
-    public void testV21SimpleCase2() throws IOException, VCardException {
-        VCardParser parser = new VCardParser_V21();
-        VCardDataBuilder builder = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
-        EntryHolder holder = new EntryHolder();
-        builder.addEntryHandler(holder);
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_2);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, holder.contacts.size());
-        verify(new ContactStruct("Ando Roid", null,
-                null, null, null, null, null, null),
-                holder.contacts.get(0));
-    }
-
-    public void testV21SimpleCase3() throws IOException, VCardException {
-        VCardParser parser = new VCardParser_V21();
-        VCardDataBuilder builder1 = new VCardDataBuilder(VCardConfig.NAME_ORDER_TYPE_ENGLISH);
-        EntryHolder holder = new EntryHolder();
-        builder1.addEntryHandler(holder);
-        VNodeBuilder builder2 = new VNodeBuilder();
-        VCardBuilderCollection collection =
-            new VCardBuilderCollection(
-                    new ArrayList<VCardBuilder>(Arrays.asList(builder1, builder2)));
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_3);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", collection));
-        is.close();
-
-        assertEquals(1, builder2.vNodeList.size());
-        VNode vnode = builder2.vNodeList.get(0); 
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("N", "Ando;Roid;",
-                        Arrays.asList("Ando", "Roid", ""),
-                        null, null, null, null),
-                new PropertyNode("FN", "Ando Roid",
-                        null, null, null, null, null));
-        verifier.verify(vnode);
-        
-        // FN is prefered.
-        assertEquals(1, holder.contacts.size());
-        ContactStruct actual = holder.contacts.get(0); 
-        verify(new ContactStruct("Ando Roid", null,
-                null, null, null, null, null, null),
-                actual);
-    }*/
-
-    public void testV21BackslashCase() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V21();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_backslash);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, builder.vNodeList.size());
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", ";A;B\\;C\\;;D;:E;\\\\;",
-                        Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""),
-                        null, null, null, null),
-                new PropertyNode("FN", "A;B\\C\\;D:E\\\\",
-                        null, null, null, null, null));
-        verifier.verify(builder.vNodeList.get(0));
-    }
-    
-    public void testV21ComplicatedCase() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V21();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_complicated);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, builder.vNodeList.size());
-        ContentValues contentValuesForQP = new ContentValues();
-        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
-        ContentValues contentValuesForPhoto = new ContentValues();
-        contentValuesForPhoto.put("ENCODING", "BASE64");
-        // Push data into int array at first since values like 0x80 are
-        // interpreted as int by the compiler and casting all of them is
-        // cumbersome...
-        int[] photoIntArray = {
-                0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
-                0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
-                0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
-                0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
-                0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
-                0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
-                0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
-                0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
-                0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
-                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
-                0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
-                0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
-                0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
-                0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
-                0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
-                0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
-                0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
-                0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
-                0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
-                0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
-                0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-                0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
-                0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
-                0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
-                0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
-                0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
-                0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
-                0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
-                0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
-                0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
-                0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
-                0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
-                0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
-                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
-                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
-                0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
-                0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
-                0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
-                0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
-                0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
-                0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
-                0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
-                0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
-                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
-                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
-                0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
-                0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-                0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
-                0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
-                0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
-                0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
-                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
-                0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
-                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
-                0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
-                0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
-                0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
-                0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
-                0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
-                0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
-                0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
-                0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
-                0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
-                0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-                0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-                0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
-                0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
-                0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
-                0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
-                0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
-                0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
-                0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
-                0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
-                0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
-                0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
-                0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
-                0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
-                0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
-                0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
-                0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
-                0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
-                0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
-                0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
-                0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
-                0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
-                0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-                0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
-                0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
-                0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
-                0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
-                0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
-                0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
-                0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
-                0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
-                0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
-                0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
-                0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
-                0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
-                0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
-                0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
-                0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
-                0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
-                0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
-                0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
-                0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
-                0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
-                0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
-                0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
-                0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
-                0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
-                0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
-                0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
-                0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
-                0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
-                0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
-                0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
-                0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
-                0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
-                0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
-                0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
-                0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
-                0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
-                0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
-                0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
-                0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
-                0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
-                0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
-                0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
-                0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
-                0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
-                0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
-                0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
-                0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
-                0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
-                0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
-                0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
-                0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
-                0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
-                0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
-                0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
-                0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
-                0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
-                0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
-                0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
-                0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
-                0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
-                0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
-                0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
-                0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
-                0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
-                0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
-                0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
-                0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
-                0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
-                0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
-                0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
-                0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
-                0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
-                0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
-                0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
-                0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
-                0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
-                0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
-                0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
-                0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
-                0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
-                0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
-                0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
-                0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
-                0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
-                0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
-                0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
-                0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
-                0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
-                0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
-                0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
-                0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
-                0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
-                0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
-                0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
-                0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
-                0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
-                0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
-                0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
-                0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
-                0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
-                0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
-                0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
-                0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
-                0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
-                0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
-                0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
-                0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
-                0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
-                0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
-                0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
-                0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
-                0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
-                0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
-                0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
-                0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
-                0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
-                0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
-                0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
-                0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
-                0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
-                0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
-                0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
-                0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
-                0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
-                0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
-                0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
-                0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
-                0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
-                0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
-                0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
-                0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
-                0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
-                0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
-                0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
-                0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
-                0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
-                0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
-                0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-                0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
-                0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
-                0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
-                0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
-                0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
-                0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
-                0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
-                0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
-                0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
-                0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
-                0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
-                0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
-                0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
-                0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
-                0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
-                0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
-                0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
-                0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
-                0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
-                0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
-                0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
-                0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
-                0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
-                0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
-                0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
-                0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
-                0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
-                0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
-                0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
-                0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
-                0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
-                0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
-                0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
-                0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
-                0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
-                0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
-                0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
-                0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
-                0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
-                0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
-                0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
-                0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
-                0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
-                0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
-                0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
-                0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
-                0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
-                0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
-                0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
-                0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
-                0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
-                0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
-                0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
-                0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
-                0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
-                0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
-                0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
-                0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
-                0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
-                0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
-                0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
-                0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
-                0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
-                0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
-                0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
-                0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
-                0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
-                0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
-                0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
-                0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
-                0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
-                0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
-                0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
-                0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
-                0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
-                0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
-                0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
-                0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
-                0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
-                0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
-                0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
-                0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
-                0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
-                0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
-                0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
-                0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
-                0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
-                0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
-                0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
-                0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
-                0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
-                0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
-                0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
-                0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
-                0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
-                0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
-                0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
-                0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
-                0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
-                0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
-                0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
-                0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
-                0x0c, 0xd1, 0x00, 0xff, 0xd9};
-        int length = photoIntArray.length;
-        byte[] photoByteArray = new byte[length];
-        for (int i = 0; i < length; i++) {
-            photoByteArray[i] = (byte)photoIntArray[i];
-        }
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "Gump;Forrest;Hoge;Pos;Tao",
-                        Arrays.asList("Gump", "Forrest",
-                                "Hoge", "Pos", "Tao"),
-                        null, null, null, null),
-                new PropertyNode("FN", "Joe Due",
-                        null, null, null, null, null),
-                new PropertyNode("ORG", 
-                        "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
-                        Arrays.asList("Gump Shrimp Co.",
-                                "Sales Dept.;Manager",
-                                "Fish keeper"),
-                        null, null, null, null),
-                new PropertyNode("ROLE", "Fish Cake Keeper!",
-                        null, null, null, null, null),
-                new PropertyNode("TITLE", "Shrimp Man",
-                        null, null, null, null, null),
-                new PropertyNode("X-CLASS", "PUBLIC",
-                        null, null, null, null, null),
-                new PropertyNode("TEL", "(111) 555-1212",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("WORK", "VOICE")), null),
-                new PropertyNode("TEL", "(404) 555-1212",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("HOME", "VOICE")), null),
-                new PropertyNode("TEL", "0311111111",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("CELL")), null),
-                new PropertyNode("TEL", "0322222222",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("VIDEO")), null),
-                new PropertyNode("TEL", "0333333333",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("VOICE")), null),     
-                new PropertyNode("ADR",
-                        ";;100 Waters Edge;Baytown;LA;30314;United States of America",
-                        Arrays.asList("", "", "100 Waters Edge", "Baytown",
-                                "LA", "30314", "United States of America"),
-                                null, null,
-                new HashSet<String>(Arrays.asList("WORK")), null),
-                new PropertyNode("LABEL",
-                        "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited  States of America",
-                        null, null, contentValuesForQP,
-                        new HashSet<String>(Arrays.asList("WORK")), null),
-                new PropertyNode("ADR",
-                        ";;42 Plantation St.;Baytown;LA;30314;United States of America",
-                        Arrays.asList("", "", "42 Plantation St.", "Baytown",
-                                "LA", "30314", "United States of America"), null, null,
-                        new HashSet<String>(Arrays.asList("HOME")), null),
-                new PropertyNode("LABEL",
-                        "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited  States of America",
-                        null, null, contentValuesForQP,
-                        new HashSet<String>(Arrays.asList("HOME")), null),
-                new PropertyNode("EMAIL", "forrestgump@walladalla.com",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("PREF", "INTERNET")), null),
-                new PropertyNode("EMAIL", "cell@example.com",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("CELL")), null),
-                new PropertyNode("NOTE", "The following note is the example from RFC 2045.",
-                        null, null, null, null, null),
-                new PropertyNode("NOTE",
-                        "Now's the time for all folk to come to the aid of their country.",
-                        null, null, contentValuesForQP, null, null),
-                new PropertyNode("PHOTO", null,
-                        null, photoByteArray, contentValuesForPhoto,
-                        new HashSet<String>(Arrays.asList("JPEG")), null),
-                new PropertyNode("X-ATTRIBUTE", "Some String",
-                        null, null, null, null, null),
-                new PropertyNode("BDAY", "19800101", 
-                        null, null, null, null, null),
-                new PropertyNode("GEO", "35.6563854,139.6994233",
-                        null, null, null, null, null),
-                new PropertyNode("URL", "http://www.example.com/", 
-                        null, null, null, null, null),
-                new PropertyNode("REV", "20080424T195243Z",
-                        null, null, null, null, null));
-        verifier.verify(builder.vNodeList.get(0));
-    }
-    
-    public void testV21Japanese1() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V21();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_1);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, builder.vNodeList.size());
-        ContentValues contentValuesForShiftJis = new ContentValues();
-        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
-        ContentValues contentValuesForQP = new ContentValues();
-        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
-        contentValuesForQP.put("CHARSET", "SHIFT_JIS");
-        // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
-        // vCard 2.1/3.0 specification does not allow multiple values.
-        // Do not need to handle it as multiple values. 
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
-                        null, contentValuesForShiftJis, null, null),
-                new PropertyNode("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
-                        null, null, contentValuesForShiftJis,
-                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
-                new PropertyNode("TEL", "0300000000",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("VOICE", "PREF")), null));
-        verifier.verify(builder.vNodeList.get(0));
-    }
-    
-    public void testV21Japanese2() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V21();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_japanese_2);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, builder.vNodeList.size());
-        ContentValues contentValuesForShiftJis = new ContentValues();
-        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
-        ContentValues contentValuesForQP = new ContentValues();
-        contentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
-        contentValuesForQP.put("CHARSET", "SHIFT_JIS");
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
-                        Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
-                                "", "", ""),
-                        null, contentValuesForShiftJis, null, null),
-                new PropertyNode("FN",
-                        "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
-                        null, null, contentValuesForShiftJis, null, null),
-                new PropertyNode("SOUND",
-                        ("\uFF71\uFF9D\uFF84\uFF9E\uFF73" +
-                        ";\uFF9B\uFF72\uFF84\uFF9E\u0031;;;"),
-                        null, null, contentValuesForShiftJis,
-                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
-                new PropertyNode("ADR",
-                        (";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                        "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                        "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
-                        "\u968E;;;;150-8512;"),
-                        Arrays.asList("",
-                                "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
-                                "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
-                                "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
-                                "\u0036\u968E", "", "", "", "150-8512", ""),
-                        null, contentValuesForQP,
-                        new HashSet<String>(Arrays.asList("HOME")), null),
-                new PropertyNode("NOTE", "\u30E1\u30E2",
-                        null, null, contentValuesForQP, null, null));
-        verifier.verify(builder.vNodeList.get(0));
-    }
-    
-    public void testV21MultipleEntryCase() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V21();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v21_multiple_entry);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(3, builder.vNodeList.size());
-        ContentValues contentValuesForShiftJis = new ContentValues();
-        contentValuesForShiftJis.put("CHARSET", "SHIFT_JIS");
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
-                        null, contentValuesForShiftJis, null, null),
-                new PropertyNode("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
-                        null, null, contentValuesForShiftJis,
-                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
-                new PropertyNode("TEL", "9",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("X-NEC-SECRET")), null),
-               new PropertyNode("TEL", "10",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-HOTEL")), null),
-               new PropertyNode("TEL", "11",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-SCHOOL")), null),
-               new PropertyNode("TEL", "12",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("FAX", "HOME")), null));
-        verifier.verify(builder.vNodeList.get(0));
-        
-        verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
-                        null, contentValuesForShiftJis, null, null),
-                new PropertyNode("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
-                        null, null, contentValuesForShiftJis,
-                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
-                new PropertyNode("TEL", "13",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("MODEM")), null),
-               new PropertyNode("TEL", "14",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("PAGER")), null),
-               new PropertyNode("TEL", "15",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-FAMILY")), null),
-               new PropertyNode("TEL", "16",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-GIRL")), null));
-        verifier.verify(builder.vNodeList.get(1));
-        verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "2.1",
-                        null, null, null, null, null),
-                new PropertyNode("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
-                        Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
-                        null, contentValuesForShiftJis, null, null),
-                new PropertyNode("SOUND",
-                        "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
-                        null, null, contentValuesForShiftJis,
-                        new HashSet<String>(Arrays.asList("X-IRMC-N")), null),
-                new PropertyNode("TEL", "17",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("X-NEC-BOY")), null),
-               new PropertyNode("TEL", "18",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-FRIEND")), null),
-               new PropertyNode("TEL", "19",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-PHS")), null),
-               new PropertyNode("TEL", "20",
-                       null, null, null,
-                       new HashSet<String>(Arrays.asList("X-NEC-RESTAURANT")), null));
-        verifier.verify(builder.vNodeList.get(2));
-    }
-    
-    public void testV30SimpleCase() throws IOException, VCardException {
-        VCardParser_V21 parser = new VCardParser_V30();
-        VNodeBuilder builder = new VNodeBuilder();
-        InputStream is = getContext().getResources().openRawResource(R.raw.v30_simple);
-        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
-        is.close();
-        assertEquals(1, builder.vNodeList.size());
-        PropertyNodesVerifier verifier = new PropertyNodesVerifier(
-                new PropertyNode("VERSION", "3.0",
-                        null, null, null, null, null),
-                new PropertyNode("FN", "And Roid",
-                        null, null, null, null, null),
-                new PropertyNode("N", "And;Roid;;;",
-                        Arrays.asList("And", "Roid", "", "", ""),
-                        null, null, null, null),
-                new PropertyNode("ORG", "Open;Handset; Alliance",
-                        Arrays.asList("Open", "Handset", " Alliance"),
-                        null, null, null, null),
-                new PropertyNode("SORT-STRING", "android", null, null, null, null, null),
-                new PropertyNode("TEL", "0300000000",
-                        null, null, null,
-                        new HashSet<String>(Arrays.asList("PREF", "VOICE")), null),
-                new PropertyNode("CLASS", "PUBLIC", null, null, null, null, null),
-                new PropertyNode("X-GNO", "0", null, null, null, null, null),
-                new PropertyNode("X-GN", "group0", null, null, null, null, null),
-                new PropertyNode("X-REDUCTION", "0",
-                        null, null, null, null, null),
-                new PropertyNode("REV", "20081031T065854Z",
-                        null, null, null, null, null));
-        verifier.verify(builder.vNodeList.get(0));
-    }
-}
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java
new file mode 100644
index 0000000..bd4d13a
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java
@@ -0,0 +1,970 @@
+/*
+ * 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.unit_tests.vcard;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.pim.vcard.ContactStruct;
+import android.pim.vcard.EntryCommitter;
+import android.pim.vcard.EntryHandler;
+import android.pim.vcard.VCardBuilder;
+import android.pim.vcard.VCardBuilderCollection;
+import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.test.mock.MockCursor;
+import android.text.TextUtils;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+/**
+ * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this
+ * class extends ContentProvider, not implementing IContentProvider,
+ * so that MockContentResolver is able to accept this class :(
+ */
+class MockContentProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public int bulkInsert(Uri url, ContentValues[] initialValues) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @SuppressWarnings("unused")
+    public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, IContentObserver observer,
+            CursorWindow window) throws RemoteException {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    @SuppressWarnings("unused")
+    public int delete(Uri url, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public String getType(Uri url) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Uri insert(Uri url, ContentValues initialValues) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri url, String mode) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
+    public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    public IBinder asBinder() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+}
+
+class CustomMockContext extends MockContext {
+    final ContentResolver mResolver;
+    public CustomMockContext(ContentResolver resolver) {
+        mResolver = resolver;
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return mResolver;
+    }
+}
+
+/**
+ * BaseClass for vCard unit tests with utility classes.
+ * Please do not add each unit test here.
+ */
+/* package */ class VCardTestsBase extends AndroidTestCase {
+    public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8;
+    public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+
+    // Do not modify these during tests.
+    protected final ContentValues mContentValuesForQP;
+    protected final ContentValues mContentValuesForSJis;
+    protected final ContentValues mContentValuesForUtf8;
+    protected final ContentValues mContentValuesForQPAndSJis;
+    protected final ContentValues mContentValuesForQPAndUtf8;
+    protected final ContentValues mContentValuesForBase64V21;
+    protected final ContentValues mContentValuesForBase64V30;
+
+    public VCardTestsBase() {
+        super();
+        mContentValuesForQP = new ContentValues();
+        mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+        mContentValuesForSJis = new ContentValues();
+        mContentValuesForSJis.put("CHARSET", "SHIFT_JIS");
+        mContentValuesForUtf8 = new ContentValues();
+        mContentValuesForUtf8.put("CHARSET", "UTF-8");
+        mContentValuesForQPAndSJis = new ContentValues();
+        mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE");
+        mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS");
+        mContentValuesForQPAndUtf8 = new ContentValues();
+        mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE");
+        mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8");
+        mContentValuesForBase64V21 = new ContentValues();
+        mContentValuesForBase64V21.put("ENCODING", "BASE64");
+        mContentValuesForBase64V30 = new ContentValues();
+        mContentValuesForBase64V30.put("ENCODING", "b");
+    }
+
+    public class ImportTestResolver extends MockContentResolver {
+        ImportTestProvider mProvider = new ImportTestProvider();
+        @Override
+        public ContentProviderResult[] applyBatch(String authority,
+                ArrayList<ContentProviderOperation> operations) {
+            equalsString(authority, RawContacts.CONTENT_URI.toString());
+            return mProvider.applyBatch(operations);
+        }
+
+        public void addExpectedContentValues(ContentValues expectedContentValues) {
+            mProvider.addExpectedContentValues(expectedContentValues);
+        }
+
+        public void verify() {
+            mProvider.verify();
+        }
+    }
+
+    private static final Set<String> sKnownMimeTypeSet =
+        new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
+                Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
+                Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
+                Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
+                Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
+                Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
+                Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+                GroupMembership.CONTENT_ITEM_TYPE));
+
+    public class ImportTestProvider extends MockContentProvider {
+        final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
+
+        public ImportTestProvider() {
+            mMimeTypeToExpectedContentValues =
+                new HashMap<String, Collection<ContentValues>>();
+            for (String acceptanbleMimeType : sKnownMimeTypeSet) {
+                // Do not use HashSet since the current implementation changes the content of
+                // ContentValues after the insertion, which make the result of hashCode()
+                // changes...
+                mMimeTypeToExpectedContentValues.put(
+                        acceptanbleMimeType, new ArrayList<ContentValues>());
+            }
+        }
+
+        public void addExpectedContentValues(ContentValues expectedContentValues) {
+            final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
+            if (!sKnownMimeTypeSet.contains(mimeType)) {
+                fail(String.format(
+                        "Unknow MimeType %s in the test code. Test code should be broken.",
+                        mimeType));
+            }
+
+            final Collection<ContentValues> contentValuesCollection =
+                    mMimeTypeToExpectedContentValues.get(mimeType);
+            contentValuesCollection.add(expectedContentValues);
+        }
+
+        @Override
+        public ContentProviderResult[] applyBatch(
+                ArrayList<ContentProviderOperation> operations) {
+            if (operations == null) {
+                fail("There is no operation.");
+            }
+
+            final int size = operations.size();
+            ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
+            for (int i = 0; i < size; i++) {
+                Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
+                fakeResultArray[i] = new ContentProviderResult(uri);
+            }
+
+            for (int i = 0; i < size; i++) {
+                ContentProviderOperation operation = operations.get(i);
+                ContentValues contentValues = operation.resolveValueBackReferences(
+                        fakeResultArray, i);
+            }
+            for (int i = 0; i < size; i++) {
+                ContentProviderOperation operation = operations.get(i);
+                ContentValues actualContentValues = operation.resolveValueBackReferences(
+                        fakeResultArray, i);
+                final Uri uri = operation.getUri();
+                if (uri.equals(RawContacts.CONTENT_URI)) {
+                    assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
+                    assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
+                } else if (uri.equals(Data.CONTENT_URI)) {
+                    final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
+                    if (!sKnownMimeTypeSet.contains(mimeType)) {
+                        fail(String.format(
+                                "Unknown MimeType %s. Probably added after developing this test",
+                                mimeType));
+                    }
+                    // Remove data meaningless in this unit tests.
+                    // Specifically, Data.DATA1 - DATA7 are set to null or empty String
+                    // regardless of the input, but it may change depending on how
+                    // resolver-related code handles it.
+                    // Here, we ignore these implementation-dependent specs and
+                    // just check whether vCard importer correctly inserts rellevent data.
+                    Set<String> keyToBeRemoved = new HashSet<String>();
+                    for (Entry<String, Object> entry : actualContentValues.valueSet()) {
+                        Object value = entry.getValue();
+                        if (value == null || TextUtils.isEmpty(value.toString())) {
+                            keyToBeRemoved.add(entry.getKey());
+                        }
+                    }
+                    for (String key: keyToBeRemoved) {
+                        actualContentValues.remove(key);
+                    }
+                    /* for testing
+                    Log.d("@@@",
+                            String.format("MimeType: %s, data: %s",
+                                    mimeType, actualContentValues.toString())); */
+                    // Remove RAW_CONTACT_ID entry just for safety, since we do not care
+                    // how resolver-related code handles the entry in this unit test,
+                    if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
+                        actualContentValues.remove(Data.RAW_CONTACT_ID);
+                    }
+                    final Collection<ContentValues> contentValuesCollection =
+                        mMimeTypeToExpectedContentValues.get(mimeType);
+                    if (contentValuesCollection.isEmpty()) {
+                        fail("ContentValues for MimeType " + mimeType
+                                + " is not expected at all (" + actualContentValues + ")");
+                    }
+                    boolean checked = false;
+                    for (ContentValues expectedContentValues : contentValuesCollection) {
+                        /*for testing
+                        Log.d("@@@", "expected: "
+                                + convertToEasilyReadableString(expectedContentValues));
+                        Log.d("@@@", "actual  : "
+                                + convertToEasilyReadableString(actualContentValues));*/
+                        if (equalsForContentValues(expectedContentValues,
+                                actualContentValues)) {
+                            assertTrue(contentValuesCollection.remove(expectedContentValues));
+                            checked = true;
+                            break;
+                        }
+                    }
+                    if (!checked) {
+                        final String failMsg =
+                            "Unexpected ContentValues for MimeType " + mimeType
+                            + ": " + actualContentValues;
+                        fail(failMsg);
+                    }
+                } else {
+                    fail("Unexpected Uri has come: " + uri);
+                }
+            }  // for (int i = 0; i < size; i++) {
+            return null;
+        }
+
+        public void verify() {
+            StringBuilder builder = new StringBuilder();
+            for (Collection<ContentValues> contentValuesCollection :
+                    mMimeTypeToExpectedContentValues.values()) {
+                for (ContentValues expectedContentValues: contentValuesCollection) {
+                    builder.append(convertToEasilyReadableString(expectedContentValues));
+                    builder.append("\n");
+                }
+            }
+            if (builder.length() > 0) {
+                final String failMsg =
+                    "There is(are) remaining expected ContentValues instance(s): \n"
+                        + builder.toString();
+                fail(failMsg);
+            }
+        }
+    }
+
+    class ImportVerifierElem {
+        private final ImportTestResolver mResolver;
+        private final EntryHandler mHandler;
+
+        public ImportVerifierElem() {
+            mResolver = new ImportTestResolver();
+            mHandler = new EntryCommitter(mResolver);
+        }
+
+        public ContentValuesBuilder addExpected(String mimeType) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Data.MIMETYPE, mimeType);
+            mResolver.addExpectedContentValues(contentValues);
+            return new ContentValuesBuilder(contentValues);
+        }
+
+        public void verify(int resId, int vCardType)
+                throws IOException, VCardException {
+            verify(getContext().getResources().openRawResource(resId), vCardType);
+        }
+
+        public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+            final VCardParser vCardParser;
+            if (VCardConfig.isV30(vCardType)) {
+                vCardParser = new VCardParser_V30(true);  // use StrictParsing
+            } else {
+                vCardParser = new VCardParser_V21();
+            }
+            VCardDataBuilder builder =
+                    new VCardDataBuilder(null, null, false, vCardType, null);
+            builder.addEntryHandler(mHandler);
+            try {
+                vCardParser.parse(is, builder);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            verifyResolver();
+        }
+
+        public void verifyResolver() {
+            mResolver.verify();
+        }
+
+        public void onParsingStart() {
+            mHandler.onParsingStart();
+        }
+
+        public void onEntryCreated(ContactStruct entry) {
+            mHandler.onEntryCreated(entry);
+        }
+
+        public void onParsingEnd() {
+            mHandler.onParsingEnd();
+        }
+    }
+
+    class ImportVerifier implements EntryHandler {
+        private List<ImportVerifierElem> mImportVerifierElemList =
+            new ArrayList<ImportVerifierElem>();
+        private int mIndex;
+
+        public ImportVerifierElem addImportVerifierElem() {
+            ImportVerifierElem importVerifier = new ImportVerifierElem();
+            mImportVerifierElemList.add(importVerifier);
+            return importVerifier;
+        }
+
+        public void verify(int resId, int vCardType) throws IOException, VCardException {
+            verify(getContext().getResources().openRawResource(resId), vCardType);
+        }
+
+        public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+            final VCardParser vCardParser;
+            if (VCardConfig.isV30(vCardType)) {
+                vCardParser = new VCardParser_V30(true);  // use StrictParsing
+            } else {
+                vCardParser = new VCardParser_V21();
+            }
+            VCardDataBuilder builder =
+                new VCardDataBuilder(null, null, false, vCardType, null);
+            builder.addEntryHandler(this);
+            try {
+                vCardParser.parse(is, builder);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+
+        public void onParsingStart() {
+            for (ImportVerifierElem elem : mImportVerifierElemList) {
+                elem.onParsingStart();
+            }
+        }
+
+        public void onEntryCreated(ContactStruct entry) {
+            assertTrue(mIndex < mImportVerifierElemList.size());
+            mImportVerifierElemList.get(mIndex).onEntryCreated(entry);
+            mIndex++;
+        }
+
+        public void onParsingEnd() {
+            for (ImportVerifierElem elem : mImportVerifierElemList) {
+                elem.onParsingEnd();
+                elem.verifyResolver();
+            }
+        }
+    }
+
+    public class ExportTestResolver extends MockContentResolver {
+        ExportTestProvider mProvider = new ExportTestProvider();
+        public ExportTestResolver() {
+            addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+            addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+        }
+
+        public ContactEntry buildContactEntry() {
+            return mProvider.buildInputEntry();
+        }
+    }
+
+    public static class MockEntityIterator implements EntityIterator {
+        List<Entity> mEntityList;
+        Iterator<Entity> mIterator;
+
+        public MockEntityIterator(List<ContentValues> contentValuesList) {
+            mEntityList = new ArrayList<Entity>();
+            Entity entity = new Entity(new ContentValues());
+            for (ContentValues contentValues : contentValuesList) {
+                    entity.addSubValue(Data.CONTENT_URI, contentValues);
+            }
+            mEntityList.add(entity);
+            mIterator = mEntityList.iterator();
+        }
+
+        public boolean hasNext() {
+            return mIterator.hasNext();
+        }
+
+        public Entity next() {
+            return mIterator.next();
+        }
+
+        public void reset() {
+            mIterator = mEntityList.iterator();
+        }
+
+        public void close() {
+        }
+    }
+
+    /**
+     * Represents one contact, which should contain multiple ContentValues like
+     * StructuredName, Email, etc.
+     */
+    static class ContactEntry {
+        private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+        public ContentValuesBuilder buildData(String mimeType) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Data.MIMETYPE, mimeType);
+            mContentValuesList.add(contentValues);
+            return new ContentValuesBuilder(contentValues);
+        }
+
+        public List<ContentValues> getList() {
+            return mContentValuesList;
+        }
+    }
+
+    class ExportTestProvider extends MockContentProvider {
+        ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+
+        public ContactEntry buildInputEntry() {
+            ContactEntry contactEntry = new ContactEntry();
+            mContactEntryList.add(contactEntry);
+            return contactEntry;
+        }
+
+        @Override
+        public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
+                String sortOrder) {
+            assertTrue(uri != null);
+            assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+            final String authority = uri.getAuthority();
+            assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+            assertTrue((Data.CONTACT_ID + "=?").equals(selection));
+            assertEquals(1, selectionArgs.length);
+            int id = Integer.parseInt(selectionArgs[0]);
+            assertTrue(id >= 0 && id < mContactEntryList.size());
+
+            return new MockEntityIterator(mContactEntryList.get(id).getList());
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection,
+                String selection, String[] selectionArgs, String sortOrder) {
+            assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+            // In this test, following arguments are not supported.
+            assertNull(selection);
+            assertNull(selectionArgs);
+            assertNull(sortOrder);
+
+            return new MockCursor() {
+                int mCurrentPosition = -1;
+
+                @Override
+                public int getCount() {
+                    return mContactEntryList.size();
+                }
+
+                @Override
+                public boolean moveToFirst() {
+                    mCurrentPosition = 0;
+                    return true;
+                }
+
+                @Override
+                public boolean moveToNext() {
+                    if (mCurrentPosition < mContactEntryList.size()) {
+                        mCurrentPosition++;
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }
+
+                @Override
+                public boolean isBeforeFirst() {
+                    return mCurrentPosition < 0;
+                }
+
+                @Override
+                public boolean isAfterLast() {
+                    return mCurrentPosition >= mContactEntryList.size();
+                }
+
+                @Override
+                public int getColumnIndex(String columnName) {
+                    assertEquals(Contacts._ID, columnName);
+                    return 0;
+                }
+
+                @Override
+                public int getInt(int columnIndex) {
+                    assertEquals(0, columnIndex);
+                    assertTrue(mCurrentPosition >= 0
+                            && mCurrentPosition < mContactEntryList.size());
+                    return mCurrentPosition;
+                }
+
+                @Override
+                public String getString(int columnIndex) {
+                    return String.valueOf(getInt(columnIndex));
+                }
+
+                @Override
+                public void close() {
+                }
+            };
+        }
+    }
+
+    class LineVerifierElem {
+        private final List<String> mExpectedLineList = new ArrayList<String>();
+        private final boolean mIsV30;
+
+        public LineVerifierElem(boolean isV30) {
+            mIsV30 = isV30;
+        }
+
+        public LineVerifierElem addExpected(final String line) {
+            if (!TextUtils.isEmpty(line)) {
+                mExpectedLineList.add(line);
+            }
+            return this;
+        }
+
+        public void verify(final String vcard) {
+            final String[] lineArray = vcard.split("\\r?\\n");
+            final int length = lineArray.length;
+            final TestCase testCase = VCardTestsBase.this;
+            boolean beginExists = false;
+            boolean endExists = false;
+            boolean versionExists = false;
+
+            for (int i = 0; i < length; i++) {
+                final String line = lineArray[i];
+                if (TextUtils.isEmpty(line)) {
+                    continue;
+                }
+
+                if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
+                    if (beginExists) {
+                        testCase.fail("Multiple \"BEGIN:VCARD\" line found");
+                    } else {
+                        beginExists = true;
+                        continue;
+                    }
+                } else if ("END:VCARD".equalsIgnoreCase(line)) {
+                    if (endExists) {
+                        testCase.fail("Multiple \"END:VCARD\" line found");
+                    } else {
+                        endExists = true;
+                        continue;
+                    }
+                } else if (
+                        (mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
+                    if (versionExists) {
+                        testCase.fail("Multiple VERSION line + found");
+                    } else {
+                        versionExists = true;
+                        continue;
+                    }
+                }
+
+                if (!beginExists) {
+                    testCase.fail(
+                            "Property other than BEGIN came before BEGIN property: " + line);
+                } else if (endExists) {
+                    testCase.fail("Property other than END came after END property: " + line);
+                }
+
+                final int index = mExpectedLineList.indexOf(line);
+                if (index >= 0) {
+                    mExpectedLineList.remove(index);
+                } else {
+                    testCase.fail("Unexpected line: " + line);
+                }
+            }
+
+            if (!mExpectedLineList.isEmpty()) {
+                StringBuffer buffer = new StringBuffer();
+                for (String expectedLine : mExpectedLineList) {
+                    buffer.append(expectedLine);
+                    buffer.append("\n");
+                }
+
+                testCase.fail("Expected line(s) not found:" + buffer.toString());
+            }
+        }
+    }
+
+    class LineVerifier implements VCardComposer.OneEntryHandler {
+        private final ArrayList<LineVerifierElem> mLineVerifierElemList;
+        private final boolean mIsV30;
+        private int index;
+
+        public LineVerifier(final boolean isV30) {
+            mLineVerifierElemList = new ArrayList<LineVerifierElem>();
+            mIsV30 = isV30;
+        }
+
+        public LineVerifierElem addLineVerifierElem() {
+            LineVerifierElem lineVerifier = new LineVerifierElem(mIsV30);
+            mLineVerifierElemList.add(lineVerifier);
+            return lineVerifier;
+        }
+
+        public void verify(String vcard) {
+            if (index >= mLineVerifierElemList.size()) {
+                VCardTestsBase.this.fail("Insufficient number of LineVerifier (" + index + ")");
+            }
+
+            LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
+            lineVerifier.verify(vcard);
+
+            index++;
+        }
+
+        public boolean onEntryCreated(String vcard) {
+            verify(vcard);
+            return true;
+        }
+
+        public boolean onInit(Context context) {
+            return true;
+        }
+
+        public void onTerminate() {
+        }
+    }
+
+    class VCardVerifier {
+        private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
+            public boolean onInit(Context context) {
+                return true;
+            }
+            public boolean onEntryCreated(String vcard) {
+                verifyOneVCard(vcard);
+                return true;
+            }
+            public void onTerminate() {
+            }
+        }
+
+        private final VCardVerifierInternal mVCardVerifierInternal;
+        private final ExportTestResolver mResolver;
+        private final int mVCardType;
+        private final boolean mIsV30;
+
+        // To allow duplication, use list instead of set.
+        // When null, we don't need to do the verification.
+        private PropertyNodesVerifier mPropertyNodesVerifier;
+        private LineVerifier mLineVerificationHandler;
+        private ImportVerifier mImportVerifier;
+
+        public VCardVerifier(ExportTestResolver resolver, int vcardType) {
+            mVCardVerifierInternal = new VCardVerifierInternal();
+            mResolver = resolver;
+            mIsV30 = VCardConfig.isV30(vcardType);
+            mVCardType = vcardType;
+        }
+
+        public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+            if (mPropertyNodesVerifier == null) {
+                mPropertyNodesVerifier = new PropertyNodesVerifier(VCardTestsBase.this);
+            }
+            PropertyNodesVerifierElem elem =
+                    mPropertyNodesVerifier.addPropertyNodesVerifierElem();
+            elem.addNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
+
+            return elem;
+        }
+
+        public PropertyNodesVerifierElem addPropertyNodesVerifierWithEmptyName() {
+            PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+            if (mIsV30) {
+                elem.addNodeWithOrder("N", "").addNodeWithOrder("FN", "");
+            }
+            return elem;
+        }
+
+        public LineVerifierElem addLineVerifier() {
+            if (mLineVerificationHandler == null) {
+                mLineVerificationHandler = new LineVerifier(mIsV30);
+            }
+            return mLineVerificationHandler.addLineVerifierElem();
+        }
+
+        public ImportVerifierElem addImportVerifier() {
+            if (mImportVerifier == null) {
+                mImportVerifier = new ImportVerifier();
+            }
+
+            return mImportVerifier.addImportVerifierElem();
+        }
+
+        private void verifyOneVCard(final String vcard) {
+            final VCardBuilder builder;
+            if (mImportVerifier != null) {
+                final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
+                final VCardDataBuilder vcardDataBuilder = new VCardDataBuilder();
+                vcardDataBuilder.addEntryHandler(mImportVerifier);
+                if (mPropertyNodesVerifier != null) {
+                    builder = new VCardBuilderCollection(Arrays.asList(
+                            vcardDataBuilder, mPropertyNodesVerifier));
+                } else {
+                    builder = vnodeBuilder;
+                }
+            } else {
+                if (mPropertyNodesVerifier != null) {
+                    builder = mPropertyNodesVerifier;
+                } else {
+                    return;
+                }
+            }
+
+            final VCardParser parser =
+                    (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
+            final TestCase testCase = VCardTestsBase.this;
+
+            InputStream is = null;
+            try {
+                String charset =
+                    (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
+                is = new ByteArrayInputStream(vcard.getBytes(charset));
+                testCase.assertEquals(true, parser.parse(is, null, builder));
+            } catch (IOException e) {
+                testCase.fail("Unexpected IOException: " + e.getMessage());
+            } catch (VCardException e) {
+                testCase.fail("Unexpected VCardException: " + e.getMessage());
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+
+        public void verify() {
+            VCardComposer composer =
+                    new VCardComposer(new CustomMockContext(mResolver), mVCardType);
+            composer.addHandler(mLineVerificationHandler);
+            composer.addHandler(mVCardVerifierInternal);
+            if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+                fail("init() failed. Reason: " + composer.getErrorReason());
+            }
+            assertFalse(composer.isAfterLast());
+            try {
+                while (!composer.isAfterLast()) {
+                    assertTrue(composer.createOneEntry());
+                }
+            } finally {
+                composer.terminate();
+            }
+        }
+    }
+
+    /**
+     * Utility method to print ContentValues whose content is printed with sorted keys.
+     */
+    private static String convertToEasilyReadableString(ContentValues contentValues) {
+        if (contentValues == null) {
+            return "null";
+        }
+        String mimeTypeValue = "";
+        SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+        for (Entry<String, Object> entry : contentValues.valueSet()) {
+            final String key = entry.getKey();
+            final Object value = entry.getValue();
+            final String valueString = (value != null ? value.toString() : null);
+            if (Data.MIMETYPE.equals(key)) {
+                mimeTypeValue = valueString;
+            } else {
+                assertNotNull(key);
+                sortedMap.put(key, valueString);
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        builder.append(Data.MIMETYPE);
+        builder.append('=');
+        builder.append(mimeTypeValue);
+        for (Entry<String, String> entry : sortedMap.entrySet()) {
+            final String key = entry.getKey();
+            final String value = entry.getValue();
+            builder.append(' ');
+            builder.append(key);
+            builder.append('=');
+            builder.append(value);
+        }
+        return builder.toString();
+    }
+
+    private static boolean equalsForContentValues(
+            ContentValues expected, ContentValues actual) {
+        if (expected == actual) {
+            return true;
+        } else if (expected == null || actual == null || expected.size() != actual.size()) {
+            return false;
+        }
+
+        for (Entry<String, Object> entry : expected.valueSet()) {
+            final String key = entry.getKey();
+            final Object value = entry.getValue();
+            if (!actual.containsKey(key)) {
+                return false;
+            }
+            if (value instanceof byte[]) {
+                Object actualValue = actual.get(key);
+                if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
+                    return false;
+                }
+            } else if (!value.equals(actual.get(key))) {
+                    return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean equalsString(String a, String b) {
+        if (a == null || a.length() == 0) {
+            return b == null || b.length() == 0;
+        } else {
+            return a.equals(b);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
index 3eb827b..7587320 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
@@ -18,7 +18,7 @@
 import java.util.ArrayList;
 
 /**
- * @hide old class. Just for testing
+ * Previously used in main vCard handling code but now exists only for testing.
  */
 public class VNode {
     public String VName;
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
index 6d69223..ce4de03 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
@@ -36,7 +36,8 @@
  * Maybe several vcard instance, so use vNodeList to store.
  * VNode: standy by a vcard instance.
  * PropertyNode: standy by a property line of a card.
- * @hide old class, just for testing use 
+ *
+ * Previously used in main vCard handling code but now exists only for testing.
  */
 public class VNodeBuilder implements VCardBuilder {
     static private String LOG_TAG = "VDATABuilder"; 
@@ -189,6 +190,7 @@
     
     private String handleOneValue(String value, String targetCharset, String encoding) {
         if (encoding != null) {
+            encoding = encoding.toUpperCase();
             if (encoding.equals("BASE64") || encoding.equals("B")) {
                 // Assume BASE64 is used only when the number of values is 1.
                 mCurrentPropNode.propValue_bytes =
diff --git a/tests/CoreTests/android/core/MathTest.java b/tests/CoreTests/android/core/MathTest.java
index 64c8c1e..50009db 100644
--- a/tests/CoreTests/android/core/MathTest.java
+++ b/tests/CoreTests/android/core/MathTest.java
@@ -687,8 +687,7 @@
     /**
      * @tests java.lang.Math#tanh(double)
      */
-    // TODO: Known failure, temporarily remove from SmallSuite
-    // @SmallTest
+    @SmallTest
     public void testTanhD() {
         // Test for special situations
         assertTrue("Should return NaN", Double.isNaN(Math.tanh(Double.NaN)));
diff --git a/tests/CoreTests/android/core/StrictMathTest.java b/tests/CoreTests/android/core/StrictMathTest.java
index c98c91d..92e6cb6 100644
--- a/tests/CoreTests/android/core/StrictMathTest.java
+++ b/tests/CoreTests/android/core/StrictMathTest.java
@@ -709,8 +709,7 @@
     /**
      * @tests java.lang.StrictMath#tanh(double)
      */
-    // TODO: Known failure, temporarily remove from small suite
-    // @SmallTest
+    @SmallTest
     public void testTanhD() {
         // Test for special situations
         assertTrue(Double.isNaN(StrictMath.tanh(Double.NaN)));
diff --git a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 20ea4d7..aa2981b 100644
--- a/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/CoreTests/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -25,7 +25,7 @@
 public class PhoneNumberUtilsTest extends TestCase {
 
     @SmallTest
-    public void testA() throws Exception {
+    public void testExtractNetworkPortion() throws Exception {
         assertEquals(
                 "+17005554141",
                 PhoneNumberUtils.extractNetworkPortion("+17005554141")
@@ -181,6 +181,68 @@
     }
 
     @SmallTest
+    public void testExtractNetworkPortionAlt() throws Exception {
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("+17005554141")
+        );
+
+        assertEquals(
+                "+17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("+1 (700).555-4141")
+        );
+
+        assertEquals(
+                "17005554141",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141")
+        );
+
+        // This may seem wrong, but it's probably ok
+        assertEquals(
+                "17005554141*#",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-4141*#")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN,1234")
+        );
+
+        assertEquals(
+                "170055541NN",
+                PhoneNumberUtils.extractNetworkPortionAlt("1 (700).555-41NN;1234")
+        );
+
+        // An MMI string is unperterbed, even though it contains a
+        // (valid in this case) embedded +
+        assertEquals(
+                "**21**+17005554141#",
+                PhoneNumberUtils.extractNetworkPortionAlt("**21**+17005554141#")
+        );
+
+        assertEquals(
+                "*31#+447966164208",
+                PhoneNumberUtils.extractNetworkPortionAlt("*31#+447966164208")
+        );
+
+        assertEquals(
+                "*31#+447966164208",
+                PhoneNumberUtils.extractNetworkPortionAlt("*31# (+44) 79 6616 4208")
+        );
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(""));
+
+        assertEquals("", PhoneNumberUtils.extractNetworkPortionAlt(",1234"));
+
+        assertNull(PhoneNumberUtils.extractNetworkPortionAlt(null));
+    }
+
+    @SmallTest
     public void testB() throws Exception {
         assertEquals("", PhoneNumberUtils.extractPostDialPortion("+17005554141"));
         assertEquals("", PhoneNumberUtils.extractPostDialPortion("+1 (700).555-4141"));
@@ -252,6 +314,9 @@
         // 444 is not a valid country code, but
         // matchIntlPrefixAndCC doesnt know this
         assertTrue(PhoneNumberUtils.compare("+444 207 792 3490", "0 207 792 3490"));
+
+        // compare SMS short code
+        assertTrue(PhoneNumberUtils.compare("404-04", "40404"));
     }
 
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 73dbb6f..f3738e3 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -70,6 +70,8 @@
 
     boolean releaseWifiLock(IBinder lock);
 
+    void initializeMulticastFiltering();
+
     boolean isMulticastEnabled();
 
     void acquireMulticastLock(IBinder binder, String tag);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 27755ed..178f76e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1056,4 +1056,17 @@
             return false;
         }
     }
+
+    /**
+     * Initialize the multicast filtering to 'on'
+     * @hide no intent to publish
+     */
+    public boolean initializeMulticastFiltering() {
+        try {
+            mService.initializeMulticastFiltering();
+            return true;
+        } catch (RemoteException e) {
+             return false;
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index fc750e2..1e322bd 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -255,6 +255,8 @@
                 mWifiStateTracker.notifyDriverStopped();
             } else if (state.equals("STARTED")) {
                 mWifiStateTracker.notifyDriverStarted();
+            } else if (state.equals("HANGED")) {
+                mWifiStateTracker.notifyDriverHung();
             }
         }
 
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index b7d3a6e..66552e4 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -87,12 +87,19 @@
     /**
      * The driver is started or stopped. The object will be the state: true for
      * started, false for stopped.
-     */ 
+     */
     private static final int EVENT_DRIVER_STATE_CHANGED              = 12;
     private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT     = 13;
     private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT  = 14;
 
     /**
+     * The driver state indication.
+     */
+    private static final int DRIVER_STARTED                          = 0;
+    private static final int DRIVER_STOPPED                          = 1;
+    private static final int DRIVER_HUNG                             = 2;
+
+    /**
      * Interval in milliseconds between polling for connection
      * status items that are not sent via asynchronous events.
      * An example is RSSI (signal strength).
@@ -556,7 +563,7 @@
         mRunState = RUN_STATE_STOPPED;
 
         // Send a driver stopped message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, 0, 0).sendToTarget();
+        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
     }
 
     /**
@@ -565,9 +572,17 @@
      */
     void notifyDriverStarted() {
         // Send a driver started message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, 1, 0).sendToTarget();
+        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
     }
-    
+
+    /**
+     * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
+     */
+    void notifyDriverHung() {
+        // Send a driver hanged message to our handler
+        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
+    }
+
     /**
      * Set the interval timer for polling connection information
      * that is not delivered asynchronously.
@@ -786,16 +801,8 @@
                  * Filter out multicast packets. This saves battery power, since
                  * the CPU doesn't have to spend time processing packets that
                  * are going to end up being thrown away.
-                 *
-                 * Note that rather than turn this off directly, we use the
-                 * public api - this keeps us all in sync - turn multicast on
-                 * first and then off.. if nobody else wants it on it'll be
-                 * off then and it's all synchronized within the API.
                  */
-                WifiManager.MulticastLock l = 
-                        mWM.createMulticastLock("WifiStateTracker");
-                l.acquire();
-                l.release();
+                mWM.initializeMulticastFiltering();
 
                 if (mBluetoothA2dp == null) {
                     mBluetoothA2dp = new BluetoothA2dp(mContext);
@@ -1157,15 +1164,14 @@
                 break;
                 
             case EVENT_DRIVER_STATE_CHANGED:
-                boolean driverStarted = msg.arg1 != 0;
-                
                 // Wi-Fi driver state changed:
-                // [31- 1] Reserved for future use
-                // [ 0- 0] Driver start (1) or stopped (0)   
-                eventLogParam = driverStarted ? 1 : 0;
-                EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, eventLogParam);
-                
-                if (driverStarted) {
+                // 0 STARTED
+                // 1 STOPPED
+                // 2 HUNG
+                EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
+
+                switch (msg.arg1) {
+                case DRIVER_STARTED:
                     /**
                      * Set the number of allowed radio channels according
                      * to the system setting, since it gets reset by the
@@ -1184,6 +1190,15 @@
                             }
                         }
                     }
+                    break;
+                case DRIVER_HUNG:
+                    Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
+                    /**
+                     * restart the driver - toggle off and on
+                     */
+                    mWM.setWifiEnabled(false);
+                    mWM.setWifiEnabled(true);
+                    break;
                 }
                 noteRunState();
                 break;