am 23aa34a7: Merge change I137dac6f into eclair

Merge commit '23aa34a743ae18e0d45b459f0c7010eb47a2147a' into eclair-mr2

* commit '23aa34a743ae18e0d45b459f0c7010eb47a2147a':
  backout a workaround that is not needed anymore
diff --git a/Android.mk b/Android.mk
index 5034c7e..0d377c2 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/IDropBoxManagerService.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/DropBoxManager.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 \
@@ -521,7 +523,6 @@
 ext_dirs := \
 	../../external/apache-http/src \
 	../../external/gdata/src \
-	../../external/protobuf/src \
 	../../external/tagsoup/src
 
 ext_src_files := $(call all-java-files-under,$(ext_dirs))
@@ -533,7 +534,6 @@
 
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core
-LOCAL_STATIC_JAVA_LIBRARIES := libgoogleclient
 
 LOCAL_MODULE := ext
 
diff --git a/api/current.xml b/api/current.xml
index 83f406f..3d4c492 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -31958,6 +31958,17 @@
  visibility="public"
 >
 </field>
+<field name="DROPBOX_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;dropbox&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="INPUT_METHOD_SERVICE"
  type="java.lang.String"
  transient="false"
@@ -102073,6 +102084,326 @@
 >
 </field>
 </class>
+<class name="DropBoxManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="DropBoxManager"
+ type="android.os.DropBoxManager"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+</constructor>
+<method name="addData"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="data" type="byte[]">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="addFile"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="fd" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="addText"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="data" type="java.lang.String">
+</parameter>
+</method>
+<method name="getNextEntry"
+ return="android.os.DropBoxManager.Entry"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="msec" type="long">
+</parameter>
+</method>
+<method name="isTagEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+</method>
+<field name="IS_EMPTY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="IS_GZIPPED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="IS_TEXT"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="DropBoxManager.Entry"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.os.Parcelable">
+</implements>
+<constructor name="DropBoxManager.Entry"
+ type="android.os.DropBoxManager.Entry"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="millis" type="long">
+</parameter>
+</constructor>
+<constructor name="DropBoxManager.Entry"
+ type="android.os.DropBoxManager.Entry"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="millis" type="long">
+</parameter>
+<parameter name="text" type="java.lang.String">
+</parameter>
+</constructor>
+<constructor name="DropBoxManager.Entry"
+ type="android.os.DropBoxManager.Entry"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="millis" type="long">
+</parameter>
+<parameter name="data" type="byte[]">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
+<constructor name="DropBoxManager.Entry"
+ type="android.os.DropBoxManager.Entry"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="millis" type="long">
+</parameter>
+<parameter name="data" type="android.os.ParcelFileDescriptor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
+<constructor name="DropBoxManager.Entry"
+ type="android.os.DropBoxManager.Entry"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="tag" type="java.lang.String">
+</parameter>
+<parameter name="millis" type="long">
+</parameter>
+<parameter name="data" type="java.io.File">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</constructor>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFlags"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getInputStream"
+ return="java.io.InputStream"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="getTag"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getText"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="maxBytes" type="int">
+</parameter>
+</method>
+<method name="getTimeMillis"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="writeToParcel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="Environment"
  extends="java.lang.Object"
  abstract="false"
@@ -119277,6 +119608,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"
@@ -119309,6 +119657,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"
@@ -119734,6 +120103,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"
@@ -119766,6 +120152,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"
@@ -132529,6 +132936,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"
@@ -144465,6 +145539,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"
@@ -173953,7 +175050,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="callback" type="android.webkit.ValueCallback&lt;java.util.Set&gt;">
+<parameter name="callback" type="android.webkit.ValueCallback&lt;java.util.Set&lt;java.lang.String&gt;&gt;">
 </parameter>
 </method>
 </class>
@@ -177581,7 +178678,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="parent" type="android.view.View">
@@ -177596,7 +178693,7 @@
  synchronized="false"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="p" type="android.view.View">
diff --git a/cleanspec.mk b/cleanspec.mk
new file mode 100644
index 0000000..683e303
--- /dev/null
+++ b/cleanspec.mk
@@ -0,0 +1 @@
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/os/IDropBoxService.java)
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..8c25d85 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();
 
@@ -117,6 +133,8 @@
 int main(int argc, char **argv) {
     android::ProcessState::self()->startThreadPool();
 
+    DataSource::RegisterDefaultSniffers();
+
 #if 1
     if (argc != 2) {
         fprintf(stderr, "usage: %s filename\n", argv[0]);
@@ -126,7 +144,7 @@
     OMXClient client;
     CHECK_EQ(client.connect(), OK);
 
-#if 0
+#if 1
     sp<MediaSource> source = createSource(argv[1]);
 
     if (source == NULL) {
@@ -144,8 +162,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 +177,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/common/Android.mk b/common/Android.mk
new file mode 100644
index 0000000..349bb86
--- /dev/null
+++ b/common/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Note: the source code is in java/, not src/, because this code is also part of
+# the framework library, and build/core/pathmap.mk expects a java/ subdirectory.
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-common
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Include this library in the build server's output directory
+$(call dist-for-goals, droid, $(LOCAL_BUILT_MODULE):android-common.jar)
diff --git a/core/java/android/text/util/Regex.java b/common/java/com/android/common/Patterns.java
similarity index 92%
rename from core/java/android/text/util/Regex.java
rename to common/java/com/android/common/Patterns.java
index a6844a4..2eab3e1 100644
--- a/core/java/android/text/util/Regex.java
+++ b/common/java/com/android/common/Patterns.java
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package android.text.util;
+package com.android.common;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * @hide
+ * Commonly used regular expression patterns.
  */
-public class Regex {
+public class Patterns {
     /**
      *  Regular expression pattern to match all IANA top-level domains.
      *  List accurate as of 2007/06/15.  List taken from:
      *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
      *  This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
      */
-    public static final Pattern TOP_LEVEL_DOMAIN_PATTERN
+    public static final Pattern TOP_LEVEL_DOMAIN
         = Pattern.compile(
                 "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
                 + "|(biz|b[abdefghijmnorstvwyz])"
@@ -63,7 +63,7 @@
      *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
      *  This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
      */
-    public static final Pattern WEB_URL_PATTERN
+    public static final Pattern WEB_URL
         = Pattern.compile(
             "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
             + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
@@ -107,20 +107,20 @@
                             // input.  This is to stop foo.sure from
                             // matching as foo.su
 
-    public static final Pattern IP_ADDRESS_PATTERN
+    public static final Pattern IP_ADDRESS
         = Pattern.compile(
             "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
             + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
             + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
             + "|[1-9][0-9]|[0-9]))");
 
-    public static final Pattern DOMAIN_NAME_PATTERN
+    public static final Pattern DOMAIN_NAME
         = Pattern.compile(
             "(((([a-zA-Z0-9][a-zA-Z0-9\\-]*)*[a-zA-Z0-9]\\.)+"
-            + TOP_LEVEL_DOMAIN_PATTERN + ")|"
-            + IP_ADDRESS_PATTERN + ")");
+            + TOP_LEVEL_DOMAIN + ")|"
+            + IP_ADDRESS + ")");
 
-    public static final Pattern EMAIL_ADDRESS_PATTERN
+    public static final Pattern EMAIL_ADDRESS
         = Pattern.compile(
             "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
             "\\@" +
@@ -145,7 +145,7 @@
      * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
      * </ul>
      */
-    public static final Pattern PHONE_PATTERN
+    public static final Pattern PHONE
         = Pattern.compile(                                  // sdd = space, dot, or dash
                 "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
                 + "(\\([0-9]+\\)[\\- \\.]*)?"               // (<digits>)<sdd>*
@@ -201,4 +201,9 @@
         }
         return buffer.toString();
     }
+
+    /**
+     * Do not create this static utility class.
+     */
+    private Patterns() {}
 }
diff --git a/common/tests/Android.mk b/common/tests/Android.mk
new file mode 100644
index 0000000..0f2c3e4
--- /dev/null
+++ b/common/tests/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CERTIFICATE := platform
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := AndroidCommonTests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-common
+
+include $(BUILD_PACKAGE)
diff --git a/common/tests/AndroidManifest.xml b/common/tests/AndroidManifest.xml
new file mode 100644
index 0000000..151ec20
--- /dev/null
+++ b/common/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.common.tests"
+        android:sharedUserId="com.android.uid.test">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- Run tests with "runtest common" -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.common.tests"
+            android:label="Android Common Library Tests" />
+
+</manifest>
diff --git a/tests/AndroidTests/src/com/android/unit_tests/RegexTest.java b/common/tests/src/com/android/common/PatternsTest.java
similarity index 71%
rename from tests/AndroidTests/src/com/android/unit_tests/RegexTest.java
rename to common/tests/src/com/android/common/PatternsTest.java
index 8f55044..a89ad62 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/RegexTest.java
+++ b/common/tests/src/com/android/common/PatternsTest.java
@@ -14,25 +14,24 @@
  * limitations under the License.
  */
 
-package com.android.unit_tests;
+package com.android.common;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.text.util.Regex;
 import junit.framework.TestCase;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class RegexTest extends TestCase {
+public class PatternsTest extends TestCase {
 
     @SmallTest
     public void testTldPattern() throws Exception {
         boolean t;
 
-        t = Regex.TOP_LEVEL_DOMAIN_PATTERN.matcher("com").matches();
+        t = Patterns.TOP_LEVEL_DOMAIN_PATTERN.matcher("com").matches();
         assertTrue("Missed valid TLD", t);
 
-        t = Regex.TOP_LEVEL_DOMAIN_PATTERN.matcher("xer").matches();
+        t = Patterns.TOP_LEVEL_DOMAIN_PATTERN.matcher("xer").matches();
         assertFalse("Matched invalid TLD!", t);
     }
 
@@ -40,19 +39,19 @@
     public void testUrlPattern() throws Exception {
         boolean t;
 
-        t = Regex.WEB_URL_PATTERN.matcher("http://www.google.com").matches();
+        t = Patterns.WEB_URL_PATTERN.matcher("http://www.google.com").matches();
         assertTrue("Valid URL", t);
 
-        t = Regex.WEB_URL_PATTERN.matcher("ftp://www.example.com").matches();
+        t = Patterns.WEB_URL_PATTERN.matcher("ftp://www.example.com").matches();
         assertFalse("Matched invalid protocol", t);
 
-        t = Regex.WEB_URL_PATTERN.matcher("http://www.example.com:8080").matches();
+        t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080").matches();
         assertTrue("Didn't match valid URL with port", t);
 
-        t = Regex.WEB_URL_PATTERN.matcher("http://www.example.com:8080/?foo=bar").matches();
+        t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080/?foo=bar").matches();
         assertTrue("Didn't match valid URL with port and query args", t);
 
-        t = Regex.WEB_URL_PATTERN.matcher("http://www.example.com:8080/~user/?foo=bar").matches();
+        t = Patterns.WEB_URL_PATTERN.matcher("http://www.example.com:8080/~user/?foo=bar").matches();
         assertTrue("Didn't match valid URL with ~", t);
     }
 
@@ -60,10 +59,10 @@
     public void testIpPattern() throws Exception {
         boolean t;
 
-        t = Regex.IP_ADDRESS_PATTERN.matcher("172.29.86.3").matches();
+        t = Patterns.IP_ADDRESS_PATTERN.matcher("172.29.86.3").matches();
         assertTrue("Valid IP", t);
 
-        t = Regex.IP_ADDRESS_PATTERN.matcher("1234.4321.9.9").matches();
+        t = Patterns.IP_ADDRESS_PATTERN.matcher("1234.4321.9.9").matches();
         assertFalse("Invalid IP", t);
     }
 
@@ -71,10 +70,10 @@
     public void testDomainPattern() throws Exception {
         boolean t;
 
-        t = Regex.DOMAIN_NAME_PATTERN.matcher("mail.example.com").matches();
+        t = Patterns.DOMAIN_NAME_PATTERN.matcher("mail.example.com").matches();
         assertTrue("Valid domain", t);
 
-        t = Regex.DOMAIN_NAME_PATTERN.matcher("__+&42.xer").matches();
+        t = Patterns.DOMAIN_NAME_PATTERN.matcher("__+&42.xer").matches();
         assertFalse("Invalid domain", t);
     }
 
@@ -82,10 +81,10 @@
     public void testPhonePattern() throws Exception {
         boolean t;
 
-        t = Regex.PHONE_PATTERN.matcher("(919) 555-1212").matches();
+        t = Patterns.PHONE_PATTERN.matcher("(919) 555-1212").matches();
         assertTrue("Valid phone", t);
 
-        t = Regex.PHONE_PATTERN.matcher("2334 9323/54321").matches();
+        t = Patterns.PHONE_PATTERN.matcher("2334 9323/54321").matches();
         assertFalse("Invalid phone", t);
 
         String[] tests = {
@@ -116,7 +115,7 @@
         };
 
         for (String test : tests) {
-            Matcher m = Regex.PHONE_PATTERN.matcher(test);
+            Matcher m = Patterns.PHONE_PATTERN.matcher(test);
 
             assertTrue("Valid phone " + test, m.find());
         }
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 800ad749..1a8d9b6 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, null /* Handler */);
-        mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
-                MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
 
         mSimWatcher = new SimWatcher(mContext);
         sThis.set(this);
@@ -1076,7 +1075,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;
@@ -1158,7 +1157,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");
             }
@@ -1167,7 +1166,7 @@
         private void unbind() {
             if (mAuthenticator != null) {
                 mAuthenticator = null;
-                mBindHelper.unbind(this);
+                mContext.unbindService(this);
             }
         }
 
@@ -1180,7 +1179,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();
@@ -1190,9 +1189,7 @@
             }
         }
 
-        public abstract void run() throws RemoteException;
-
-        public void onDisconnected() {
+        public void onServiceDisconnected(ComponentName name) {
             mAuthenticator = null;
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
@@ -1201,6 +1198,8 @@
             }
         }
 
+        public abstract void run() throws RemoteException;
+
         public void onTimedOut() {
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
@@ -1270,6 +1269,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 {
@@ -1278,9 +1310,6 @@
         }
 
         public void handleMessage(Message msg) {
-            if (mBindHelper.handleMessage(msg)) {
-                return;
-            }
             switch (msg.what) {
                 case MESSAGE_TIMED_OUT:
                     Session session = (Session)msg.obj;
@@ -1420,16 +1449,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 ("
@@ -1573,6 +1644,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
@@ -1583,7 +1655,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 4282c1b..f4b7258 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,15 +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;
 
 /**
@@ -44,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 (TextUtils.isEmpty(authTokenLabel)) {
-            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;
         }
@@ -111,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..1e04abf 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.DropBoxManager;
 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.IDropBoxManagerService;
+
 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 DropBoxManager mDropBoxManager = 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 getDropBoxManager();
         }
 
         return null;
@@ -1045,7 +1051,7 @@
         }
         return mVibrator;
     }
-  
+
     private AudioManager getAudioManager()
     {
         if (mAudioManager == null) {
@@ -1054,6 +1060,17 @@
         return mAudioManager;
     }
 
+    private DropBoxManager getDropBoxManager() {
+        synchronized (mSync) {
+            if (mDropBoxManager == null) {
+                IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+                IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+                mDropBoxManager = new DropBoxManager(service);
+            }
+        }
+        return mDropBoxManager;
+    }
+
     @Override
     public int checkPermission(String permission, int pid, int uid) {
         if (permission == null) {
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index e5a769b..0757cb0 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -43,7 +43,6 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.text.util.Regex;
 import android.util.AndroidRuntimeException;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -69,6 +68,8 @@
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemSelectedListener;
 
+import com.android.common.Patterns;
+
 import java.util.ArrayList;
 import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicLong;
@@ -823,7 +824,7 @@
                 // The user changed the query, check if it is a URL and if so change the search
                 // button in the soft keyboard to the 'Go' button.
                 int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
-                if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
+                if (Patterns.WEB_URL.matcher(mUserQuery).matches()) {
                     options = options | EditorInfo.IME_ACTION_GO;
                 } else {
                     options = options | EditorInfo.IME_ACTION_SEARCH;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index bd5b07c..d88a214 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -63,7 +63,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 3716274..5903c83 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -751,4 +751,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..d77a6ca 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,15 @@
      * @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.
+     * @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/DropBoxManager.aidl b/core/java/android/os/DropBoxManager.aidl
new file mode 100644
index 0000000..6474ec2
--- /dev/null
+++ b/core/java/android/os/DropBoxManager.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 DropBoxManager.Entry;
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
new file mode 100644
index 0000000..7827676
--- /dev/null
+++ b/core/java/android/os/DropBoxManager.java
@@ -0,0 +1,274 @@
+/*
+ * 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.IDropBoxManagerService;
+
+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>DropBoxManager entries are not sent anywhere directly, but other system
+ * services and debugging tools may scan and upload entries for processing.
+ */
+public class DropBoxManager {
+    private static final String TAG = "DropBoxManager";
+    private final IDropBoxManagerService 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 java.util.zip.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 DropBoxManager(IDropBoxManagerService 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 DropBoxManager() { 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
deleted file mode 100644
index ca41ce5..0000000
--- a/core/java/android/pim/vcard/Constants.java
+++ /dev/null
@@ -1,94 +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.pim.vcard;
-
-/**
- * Constants used in both composer and parser.
- */
-/* 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";
-    
-    // Properties both the current (as of 2009-08-17) ContactsStruct 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";
-    public static final String PROPERTY_X_YAHOO = "X-YAHOO";
-    public static final String PROPERTY_X_ICQ = "X-ICQ";
-    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";
-    // 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";
-    
-    // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
-    //
-    // e.g.
-    // 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..." 
-    // 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."
-    // 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."
-    //
-    // 2) has been the default of VCard exporter/importer in Android, but we can see the other
-    // formats in vCard data emitted by the other softwares/devices.
-    //
-    // 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 ATTR_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 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";
-
-    // 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";
-
-    // DoCoMo specific attribute. 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";
-
-    private Constants() {
-    }
-}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
deleted file mode 100644
index 36e5e23..0000000
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ /dev/null
@@ -1,1367 +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.pim.vcard;
-
-import android.accounts.Account;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
-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.StructuredName;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-
-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;
-
-/**
- * This class bridges between data structure of Contact app and VCard data.
- */
-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);
-        sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
-        sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
-        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);
-    }
-    
-    /**
-     * @hide only for testing
-     */
-    static public class PhoneData {
-        public final int type;
-        public final String data;
-        public final String label;
-        // isPrimary is changable only when there's no appropriate one existing in
-        // the original VCard.
-        public boolean isPrimary;
-        public PhoneData(int type, String data, String label, boolean isPrimary) {
-            this.type = type;
-            this.data = data;
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PhoneData) {
-                return false;
-            }
-            PhoneData phoneData = (PhoneData)obj;
-            return (type == phoneData.type && data.equals(phoneData.data) &&
-                    label.equals(phoneData.label) && isPrimary == phoneData.isPrimary);
-        }
-        
-        @Override
-        public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, isPrimary);
-        }
-    }
-
-    /**
-     * @hide only for testing
-     */
-    static public class EmailData {
-        public final int type;
-        public final String data;
-        // Used only when TYPE is TYPE_CUSTOM.
-        public final String label;
-        // isPrimary is changable only when there's no appropriate one existing in
-        // the original VCard.
-        public boolean isPrimary;
-        public EmailData(int type, String data, String label, boolean isPrimary) {
-            this.type = type;
-            this.data = data;
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof EmailData) {
-                return false;
-            }
-            EmailData emailData = (EmailData)obj;
-            return (type == emailData.type && data.equals(emailData.data) &&
-                    label.equals(emailData.label) && isPrimary == emailData.isPrimary);
-        }
-        
-        @Override
-        public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, isPrimary);
-        }
-    }
-
-    static public class PostalData {
-        // Determined by vCard spec.
-        // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
-        public static final int ADDR_MAX_DATA_SIZE = 7;
-        private final String[] dataArray;
-        public final String pobox;
-        public final String extendedAddress;
-        public final String street;
-        public final String localty;
-        public final String region;
-        public final String postalCode;
-        public final String country;
-
-        public final int type;
-        
-        // Used only when type variable is TYPE_CUSTOM.
-        public final String label;
-
-        // isPrimary is changable only when there's no appropriate one existing in
-        // the original VCard.
-        public boolean isPrimary;
-        public PostalData(int type, List<String> propValueList,
-                String label, boolean isPrimary) {
-            this.type = type;
-            dataArray = new String[ADDR_MAX_DATA_SIZE];
-
-            int size = propValueList.size();
-            if (size > ADDR_MAX_DATA_SIZE) {
-                size = ADDR_MAX_DATA_SIZE;
-            }
-
-            // adr-value    = 0*6(text-value ";") text-value
-            //              ; PO Box, Extended Address, Street, Locality, Region, Postal
-            //              ; Code, Country Name
-            //
-            // Use Iterator assuming List may be LinkedList, though actually it is
-            // always ArrayList in the current implementation.
-            int i = 0;
-            for (String addressElement : propValueList) {
-                dataArray[i] = addressElement;
-                if (++i >= size) {
-                    break;
-                }
-            }
-            while (i < ADDR_MAX_DATA_SIZE) {
-                dataArray[i++] = null;
-            }
-
-            this.pobox = dataArray[0];
-            this.extendedAddress = dataArray[1];
-            this.street = dataArray[2];
-            this.localty = dataArray[3];
-            this.region = dataArray[4];
-            this.postalCode = dataArray[5];
-            this.country = dataArray[6];
-            
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PostalData) {
-                return false;
-            }
-            PostalData postalData = (PostalData)obj;
-            return (Arrays.equals(dataArray, postalData.dataArray) && 
-                    (type == postalData.type &&
-                            (type == StructuredPostal.TYPE_CUSTOM ?
-                                    (label == postalData.label) : true)) &&
-                    (isPrimary == postalData.isPrimary));
-        }
-        
-        public String getFormattedAddress(int vcardType) {
-            StringBuilder builder = new StringBuilder();
-            boolean empty = true;
-            if (VCardConfig.isJapaneseDevice(vcardType)) {
-                // In Japan, the order is reversed.
-                for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) {
-                    String addressPart = dataArray[i];
-                    if (!TextUtils.isEmpty(addressPart)) {
-                        if (!empty) {
-                            builder.append(' ');
-                        }
-                        builder.append(addressPart);
-                        empty = false;
-                    }
-                }
-            } else {
-                for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
-                    String addressPart = dataArray[i];
-                    if (!TextUtils.isEmpty(addressPart)) {
-                        if (!empty) {
-                            builder.append(' ');
-                        }
-                        builder.append(addressPart);
-                        empty = false;
-                    }
-                }
-            }
-
-            return builder.toString().trim();
-        }
-        
-        @Override
-        public String toString() {
-            return String.format("type: %d, label: %s, isPrimary: %s",
-                    type, label, isPrimary);
-        }
-    }
-    
-    /**
-     * @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;
-        // 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,
-                boolean isPrimary) {
-            this.type = type;
-            this.companyName = companyName;
-            this.positionName = positionName;
-            this.isPrimary = isPrimary;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof OrganizationData) {
-                return false;
-            }
-            OrganizationData organization = (OrganizationData)obj;
-            return (type == organization.type && companyName.equals(organization.companyName) &&
-                    positionName.equals(organization.positionName) &&
-                    isPrimary == organization.isPrimary);
-        }
-        
-        @Override
-        public String toString() {
-            return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
-                    type, companyName, positionName, isPrimary);
-        }
-    }
-    
-    static public class ImData {
-        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) {
-            this.type = type;
-            this.data = data;
-            this.label = label;
-            this.isPrimary = isPrimary;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            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);
-        }
-        
-        @Override
-        public String toString() {
-            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
-                    type, data, label, 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 PhotoData(int type, String formatName, byte[] photoBytes) {
-            this.type = type;
-            this.formatName = formatName;
-            this.photoBytes = photoBytes;
-        }
-    }
-    
-    static /* package */ class Property {
-        private String mPropertyName;
-        private Map<String, Collection<String>> mParameterMap =
-            new HashMap<String, Collection<String>>();
-        private List<String> mPropertyValueList = new ArrayList<String>();
-        private byte[] mPropertyBytes;
-        
-        public Property() {
-            clear();
-        }
-        
-        public void setPropertyName(final String propertyName) {
-            mPropertyName = propertyName;
-        }
-        
-        public void addParameter(final String paramName, final String paramValue) {
-            Collection<String> values;
-            if (!mParameterMap.containsKey(paramName)) {
-                if (paramName.equals("TYPE")) {
-                    values = new HashSet<String>();
-                } else {
-                    values = new ArrayList<String>();
-                }
-                mParameterMap.put(paramName, values);
-            } else {
-                values = mParameterMap.get(paramName);
-            }
-            values.add(paramValue);
-        }
-        
-        public void addToPropertyValueList(final String propertyValue) {
-            mPropertyValueList.add(propertyValue);
-        }
-        
-        public void setPropertyBytes(final byte[] propertyBytes) {
-            mPropertyBytes = propertyBytes;
-        }
-
-        public final Collection<String> getParameters(String type) {
-            return mParameterMap.get(type);
-        }
-        
-        public final List<String> getPropertyValueList() {
-            return mPropertyValueList;
-        }
-        
-        public void clear() {
-            mPropertyName = null;
-            mParameterMap.clear();
-            mPropertyValueList.clear();
-        }
-    }
-    
-    private String mFamilyName;
-    private String mGivenName;
-    private String mMiddleName;
-    private String mPrefix;
-    private String mSuffix;
-
-    // Used only when no family nor given name is found.
-    private String mFullName;
-    
-    private String mPhoneticFamilyName;
-    private String mPhoneticGivenName;
-    private String mPhoneticMiddleName;
-    
-    private String mPhoneticFullName;
-
-    private List<String> mNickNameList;
-
-    private String mDisplayName; 
-
-    private String mBirthday;
-    
-    private List<String> mNoteList;
-    private List<PhoneData> mPhoneList;
-    private List<EmailData> mEmailList;
-    private List<PostalData> mPostalList;
-    private List<OrganizationData> mOrganizationList;
-    private List<ImData> mImList;
-    private List<PhotoData> mPhotoList;
-    private List<String> mWebsiteList;
-    
-    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);
-    }
-
-    public ContactStruct(int vcardType) {
-        this(vcardType, null);
-    }
-
-    public ContactStruct(int vcardType, Account account) {
-        mVCardType = vcardType;
-        mAccount = account;
-    }
-
-    /**
-     * @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
-     * @param label lable col of content://contacts/phones
-     */
-    private void addPhone(int type, String data, String label, boolean isPrimary){
-        if (mPhoneList == null) {
-            mPhoneList = new ArrayList<PhoneData>();
-        }
-        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);
-            }
-        }
-
-        PhoneData phoneData = new PhoneData(type,
-                PhoneNumberUtils.formatNumber(builder.toString()),
-                label, isPrimary);
-
-        mPhoneList.add(phoneData);
-    }
-
-    private void addNickName(final String nickName) {
-        if (mNickNameList == null) {
-            mNickNameList = new ArrayList<String>();
-        }
-        mNickNameList.add(nickName);
-    }
-    
-    private void addEmail(int type, String data, String label, boolean isPrimary){
-        if (mEmailList == null) {
-            mEmailList = new ArrayList<EmailData>();
-        }
-        mEmailList.add(new EmailData(type, data, label, isPrimary));
-    }
-    
-    private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
-        if (mPostalList == null) {
-            mPostalList = new ArrayList<PostalData>();
-        }
-        mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
-    }
-    
-    private void addOrganization(int type, final String companyName,
-            final String positionName, boolean isPrimary) {
-        if (mOrganizationList == null) {
-            mOrganizationList = new ArrayList<OrganizationData>();
-        }
-        mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
-    }
-    
-    private void addIm(int type, String data, String label, boolean isPrimary) {
-        if (mImList == null) {
-            mImList = new ArrayList<ImData>();
-        }
-        mImList.add(new ImData(type, data, label, isPrimary));
-    }
-    
-    private void addNote(final String note) {
-        if (mNoteList == null) {
-            mNoteList = new ArrayList<String>(1);
-        }
-        mNoteList.add(note);
-    }
-    
-    private void addPhotoBytes(String formatName, byte[] photoBytes) {
-        if (mPhotoList == null) {
-            mPhotoList = new ArrayList<PhotoData>(1);
-        }
-        final PhotoData photoData = new PhotoData(0, null, photoBytes);
-        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)
-        int size;
-        if (elems == null || (size = elems.size()) < 1) {
-            return;
-        }
-        if (size > 5) {
-            size = 5;
-        }
-
-        switch (size) {
-        // fallthrough
-        case 5:
-            mSuffix = elems.get(4);
-        case 4:
-            mPrefix = elems.get(3);
-        case 3:
-            mMiddleName = elems.get(2);
-        case 2:
-            mGivenName = elems.get(1);
-        default:
-            mFamilyName = elems.get(0);
-        }
-    }
-    
-    /**
-     * Some Japanese mobile phones use this field for phonetic name,
-     *  since vCard 2.1 does not have "SORT-STRING" type.
-     * Also, in some cases, the field has some ';'s in it.
-     * Assume the ';' means the same meaning in N property
-     */
-    @SuppressWarnings("fallthrough")
-    private void handlePhoneticNameFromSound(List<String> elems) {
-        // Family, Given, Middle. (1-3)
-        // This is not from specification but mere assumption. Some Japanese phones use this order.
-        int size;
-        if (elems == null || (size = elems.size()) < 1) {
-            return;
-        }
-        if (size > 3) {
-            size = 3;
-        }
-
-        switch (size) {
-        // fallthrough
-        case 3:
-            mPhoneticMiddleName = elems.get(2);
-        case 2:
-            mPhoneticGivenName = elems.get(1);
-        default:
-            mPhoneticFamilyName = elems.get(0);
-        }
-    }
-
-    public void addProperty(Property property) {
-        String propName = property.mPropertyName;
-        final Map<String, Collection<String>> paramMap = property.mParameterMap;
-        final List<String> propValueList = property.mPropertyValueList;
-        byte[] propBytes = property.mPropertyBytes;
-        
-        if (propValueList.size() == 0) {
-            return;
-        }
-        final String propValue = listToString(propValueList).trim();
-        
-        if (propName.equals("VERSION")) {
-            // vCard version. Ignore this.
-        } else if (propName.equals("FN")) {
-            mFullName = propValue;
-        } else if (propName.equals("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")) {
-            handleNProperty(propValueList);
-        } else if (propName.equals("SORT-STRING")) {
-            mPhoneticFullName = propValue;
-        } else if (propName.equals("NICKNAME") || propName.equals("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 {
-                // Ignore this field since Android cannot understand what it is.
-            }
-        } else if (propName.equals("ADR")) {
-            boolean valuesAreAllEmpty = true;
-            for (String value : propValueList) {
-                if (value.length() > 0) {
-                    valuesAreAllEmpty = false;
-                    break;
-                }
-            }
-            if (valuesAreAllEmpty) {
-                return;
-            }
-
-            int type = -1;
-            String label = "";
-            boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_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;
-                        isPrimary = true;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
-                        type = StructuredPostal.TYPE_HOME;
-                        label = "";
-                    } else if (typeString.equals(Constants.ATTR_TYPE_WORK) || 
-                            typeString.equalsIgnoreCase("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")) {
-                        // We do not have any appropriate way to store this information.
-                    } else {
-                        if (typeString.startsWith("X-") && type < 0) {
-                            typeString = typeString.substring(2);
-                        }
-                        // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
-                        // emit non-standard types. We do not handle their values now.
-                        type = StructuredPostal.TYPE_CUSTOM;
-                        label = typeString;
-                    }
-                }
-            }
-            // We use "HOME" as default
-            if (type < 0) {
-                type = StructuredPostal.TYPE_HOME;
-            }
-
-            addPostal(type, propValueList, label, isPrimary);
-        } else if (propName.equals("EMAIL")) {
-            int type = -1;
-            String label = null;
-            boolean isPrimary = false;
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_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;
-                        isPrimary = true;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
-                        type = Email.TYPE_HOME;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
-                        type = Email.TYPE_WORK;
-                    } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
-                        type = Email.TYPE_MOBILE;
-                    } else {
-                        if (typeString.startsWith("X-") && type < 0) {
-                            typeString = typeString.substring(2);
-                        }
-                        // vCard 3.0 allows iana-token.
-                        // We may have INTERNET (specified in vCard spec),
-                        // SCHOOL, etc.
-                        type = Email.TYPE_CUSTOM;
-                        label = typeString;
-                    }
-                }
-            }
-            if (type < 0) {
-                type = Email.TYPE_OTHER;
-            }
-            addEmail(type, propValue, label, isPrimary);
-        } else if (propName.equals("ORG")) {
-            // vCard specification does not specify other types.
-            int type = Organization.TYPE_WORK;
-            boolean isPrimary = false;
-            
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_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;
-                        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();
-            }
-            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);
-            }
-        } else if (propName.equals("TEL")) {
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
-            Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
-            final int type;
-            final String label;
-            if (typeObject instanceof Integer) {
-                type = (Integer)typeObject;
-                label = null;
-            } else {
-                type = Phone.TYPE_CUSTOM;
-                label = typeObject.toString();
-            }
-            
-            final boolean isPrimary;
-            if (!mPrefIsSet_Phone && typeCollection != null &&
-                    typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
-                mPrefIsSet_Phone = true;
-                isPrimary = true;
-            } else {
-                isPrimary = false;
-            }
-            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);
-            // 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;
-                isPrimary = true;
-            } else {
-                isPrimary = false;
-            }
-            addPhone(type, propValue, label, isPrimary);
-        } else if (sImMap.containsKey(propName)){
-            int type = sImMap.get(propName);
-            boolean isPrimary = false;
-            final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
-            if (typeCollection != null) {
-                for (String typeString : typeCollection) {
-                    if (typeString.equals(Constants.ATTR_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;
-                    }
-                }
-            }
-            if (type < 0) {
-                type = Phone.TYPE_HOME;
-            }
-            addIm(type, propValue, null, isPrimary);
-        } else if (propName.equals("NOTE")) {
-            addNote(propValue);
-        } else if (propName.equals("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")) {
-            mBirthday = propValue;
-        /*} else if (propName.equals("REV")) {                
-            // Revision of this VCard entry. I think we can ignore this.
-        } else if (propName.equals("UID")) {
-        } else if (propName.equals("KEY")) {
-            // Type is X509 or PGP? I don't know how to handle this...
-        } else if (propName.equals("MAILER")) {
-        } else if (propName.equals("TZ")) {
-        } else if (propName.equals("GEO")) {
-        } else if (propName.equals("CLASS")) {
-            // vCard 3.0 only.
-            // e.g. CLASS:CONFIDENTIAL
-        } else if (propName.equals("PROFILE")) {
-            // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
-        } else if (propName.equals("CATEGORIES")) {
-            // VCard 3.0 only.
-            // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
-        } else if (propName.equals("SOURCE")) {
-            // VCard 3.0 only.
-        } else if (propName.equals("PRODID")) {
-            // VCard 3.0 only.
-            // To specify the identifier for the product that created
-            // the vCard object.*/
-        } else {
-            // Unknown X- words and IANA token.
-        }
-    }
-
-    /**
-     * Construct the display name. The constructed data must not be null.
-     */
-    private void constructDisplayName() {
-        if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
-            StringBuilder builder = new StringBuilder();
-            List<String> nameList;
-            switch (VCardConfig.getNameOrderType(mVCardType)) {
-            case VCardConfig.NAME_ORDER_JAPANESE:
-                if (VCardUtils.containsOnlyPrintableAscii(mFamilyName) &&
-                        VCardUtils.containsOnlyPrintableAscii(mGivenName)) {
-                    nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
-                } else {
-                    nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix);
-                }
-                break;
-            case VCardConfig.NAME_ORDER_EUROPE:
-                nameList = Arrays.asList(mPrefix, mMiddleName, mGivenName, mFamilyName, mSuffix);
-                break;
-            default:
-                nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
-                break;
-            }
-            boolean first = true;
-            for (String namePart : nameList) {
-                if (!TextUtils.isEmpty(namePart)) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        builder.append(' ');
-                    }
-                    builder.append(namePart);
-                }
-            }
-            mDisplayName = builder.toString();
-        } else if (!TextUtils.isEmpty(mFullName)) {
-            mDisplayName = mFullName;
-        } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
-                TextUtils.isEmpty(mPhoneticGivenName))) {
-            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
-                    mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
-        } else if (mEmailList != null && mEmailList.size() > 0) {
-            mDisplayName = mEmailList.get(0).data;
-        } else if (mPhoneList != null && mPhoneList.size() > 0) {
-            mDisplayName = mPhoneList.get(0).data;
-        } else if (mPostalList != null && mPostalList.size() > 0) {
-            mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
-        }
-
-        if (mDisplayName == null) {
-            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";
-
-    public void pushIntoContentResolver(ContentResolver resolver) {
-        ArrayList<ContentProviderOperation> operationList =
-            new ArrayList<ContentProviderOperation>();  
-        ContentProviderOperation.Builder builder =
-            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
-        String myGroupsId = null;
-        if (mAccount != null) {
-            builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
-            builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
-
-            // Assume that caller side creates this group if it does not exist.
-            // TODO: refactor this code along with the change in GoogleSource.java
-            if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
-                final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
-                        Groups.SOURCE_ID },
-                        Groups.TITLE + "=?", new String[] {
-                        GOOGLE_MY_CONTACTS_GROUP }, null);
-                try {
-                    if (cursor != null && cursor.moveToFirst()) {
-                        myGroupsId = cursor.getString(0);
-                    }
-                } finally {
-                    if (cursor != null) {
-                        cursor.close();
-                    }
-                }
-            }
-        } else {
-            builder.withValue(RawContacts.ACCOUNT_NAME, null);
-            builder.withValue(RawContacts.ACCOUNT_TYPE, null);
-        }
-        operationList.add(builder.build());
-
-        {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-
-            builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
-            builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
-            builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
-            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);
-
-            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);
-                builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
-
-                builder.withValue(Phone.TYPE, phoneData.type);
-                if (phoneData.type == Phone.TYPE_CUSTOM) {
-                    builder.withValue(Phone.LABEL, phoneData.label);
-                }
-                builder.withValue(Phone.NUMBER, phoneData.data);
-                if (phoneData.isPrimary) {
-                    builder.withValue(Data.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);
-                }
-                operationList.add(builder.build());
-            }
-        }
-        
-        if (mEmailList != null) {
-            for (EmailData emailData : mEmailList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
-                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
-
-                builder.withValue(Email.TYPE, emailData.type);
-                if (emailData.type == Email.TYPE_CUSTOM) {
-                    builder.withValue(Email.LABEL, emailData.label);
-                }
-                builder.withValue(Email.DATA, emailData.data);
-                if (emailData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                }
-                operationList.add(builder.build());
-            }
-        }
-        
-        if (mPostalList != null) {
-            for (PostalData postalData : mPostalList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                VCardUtils.insertStructuredPostalDataUsingContactsStruct(
-                        mVCardType, builder, postalData);
-                operationList.add(builder.build());
-            }
-        }
-        
-        if (mImList != null) {
-            for (ImData imData : mImList) {
-                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.DATA, imData.data);
-                if (imData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                }
-            }
-        }
-        
-        if (mNoteList != null) {
-            for (String note : mNoteList) {
-                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;
-                }
-                operationList.add(builder.build());
-            }
-        }
-
-        if (mWebsiteList != null) {
-            for (String website : mWebsiteList) {
-                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-                builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
-                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);
-                operationList.add(builder.build());
-            }
-        }
-        
-        if (!TextUtils.isEmpty(mBirthday)) {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
-            builder.withValue(Event.START_DATE, mBirthday);
-            builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
-            operationList.add(builder.build());
-        }
-
-        if (myGroupsId != null) {
-            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
-            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
-            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
-            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
-            operationList.add(builder.build());
-        }
-
-        try {
-            resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        } catch (OperationApplicationException e) {
-            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        }
-    }
-
-    public boolean isIgnorable() {
-        return getDisplayName().length() == 0;
-    }
-    
-    private String listToString(List<String> list){
-        final int size = list.size();
-        if (size > 1) {
-            StringBuilder builder = new StringBuilder();
-            int i = 0;
-            for (String type : list) {
-                builder.append(type);
-                if (i < size - 1) {
-                    builder.append(";");
-                }
-            }
-            return builder.toString();
-        } else if (size == 1) {
-            return list.get(0);
-        } else {
-            return "";
-        }
-    }
-}
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
new file mode 100644
index 0000000..875c29e
--- /dev/null
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -0,0 +1,380 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TextUtils especially for Japanese.
+ */
+/* package */ class JapaneseUtils {
+    static private final Map<Character, String> sHalfWidthMap =
+        new HashMap<Character, String>();
+
+    static {
+        // There's no logical mapping rule in Unicode. Sigh.
+        sHalfWidthMap.put('\u3001', "\uFF64");
+        sHalfWidthMap.put('\u3002', "\uFF61");
+        sHalfWidthMap.put('\u300C', "\uFF62");
+        sHalfWidthMap.put('\u300D', "\uFF63");
+        sHalfWidthMap.put('\u301C', "~");
+        sHalfWidthMap.put('\u3041', "\uFF67");
+        sHalfWidthMap.put('\u3042', "\uFF71");
+        sHalfWidthMap.put('\u3043', "\uFF68");
+        sHalfWidthMap.put('\u3044', "\uFF72");
+        sHalfWidthMap.put('\u3045', "\uFF69");
+        sHalfWidthMap.put('\u3046', "\uFF73");
+        sHalfWidthMap.put('\u3047', "\uFF6A");
+        sHalfWidthMap.put('\u3048', "\uFF74");
+        sHalfWidthMap.put('\u3049', "\uFF6B");
+        sHalfWidthMap.put('\u304A', "\uFF75");
+        sHalfWidthMap.put('\u304B', "\uFF76");
+        sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
+        sHalfWidthMap.put('\u304D', "\uFF77");
+        sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
+        sHalfWidthMap.put('\u304F', "\uFF78");
+        sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
+        sHalfWidthMap.put('\u3051', "\uFF79");
+        sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
+        sHalfWidthMap.put('\u3053', "\uFF7A");
+        sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
+        sHalfWidthMap.put('\u3055', "\uFF7B");
+        sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
+        sHalfWidthMap.put('\u3057', "\uFF7C");
+        sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
+        sHalfWidthMap.put('\u3059', "\uFF7D");
+        sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
+        sHalfWidthMap.put('\u305B', "\uFF7E");
+        sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
+        sHalfWidthMap.put('\u305D', "\uFF7F");
+        sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
+        sHalfWidthMap.put('\u305F', "\uFF80");
+        sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
+        sHalfWidthMap.put('\u3061', "\uFF81");
+        sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
+        sHalfWidthMap.put('\u3063', "\uFF6F");
+        sHalfWidthMap.put('\u3064', "\uFF82");
+        sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
+        sHalfWidthMap.put('\u3066', "\uFF83");
+        sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
+        sHalfWidthMap.put('\u3068', "\uFF84");
+        sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
+        sHalfWidthMap.put('\u306A', "\uFF85");
+        sHalfWidthMap.put('\u306B', "\uFF86");
+        sHalfWidthMap.put('\u306C', "\uFF87");
+        sHalfWidthMap.put('\u306D', "\uFF88");
+        sHalfWidthMap.put('\u306E', "\uFF89");
+        sHalfWidthMap.put('\u306F', "\uFF8A");
+        sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
+        sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
+        sHalfWidthMap.put('\u3072', "\uFF8B");
+        sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
+        sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
+        sHalfWidthMap.put('\u3075', "\uFF8C");
+        sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
+        sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
+        sHalfWidthMap.put('\u3078', "\uFF8D");
+        sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
+        sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
+        sHalfWidthMap.put('\u307B', "\uFF8E");
+        sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
+        sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
+        sHalfWidthMap.put('\u307E', "\uFF8F");
+        sHalfWidthMap.put('\u307F', "\uFF90");
+        sHalfWidthMap.put('\u3080', "\uFF91");
+        sHalfWidthMap.put('\u3081', "\uFF92");
+        sHalfWidthMap.put('\u3082', "\uFF93");
+        sHalfWidthMap.put('\u3083', "\uFF6C");
+        sHalfWidthMap.put('\u3084', "\uFF94");
+        sHalfWidthMap.put('\u3085', "\uFF6D");
+        sHalfWidthMap.put('\u3086', "\uFF95");
+        sHalfWidthMap.put('\u3087', "\uFF6E");
+        sHalfWidthMap.put('\u3088', "\uFF96");
+        sHalfWidthMap.put('\u3089', "\uFF97");
+        sHalfWidthMap.put('\u308A', "\uFF98");
+        sHalfWidthMap.put('\u308B', "\uFF99");
+        sHalfWidthMap.put('\u308C', "\uFF9A");
+        sHalfWidthMap.put('\u308D', "\uFF9B");
+        sHalfWidthMap.put('\u308E', "\uFF9C");
+        sHalfWidthMap.put('\u308F', "\uFF9C");
+        sHalfWidthMap.put('\u3090', "\uFF72");
+        sHalfWidthMap.put('\u3091', "\uFF74");
+        sHalfWidthMap.put('\u3092', "\uFF66");
+        sHalfWidthMap.put('\u3093', "\uFF9D");
+        sHalfWidthMap.put('\u309B', "\uFF9E");
+        sHalfWidthMap.put('\u309C', "\uFF9F");
+        sHalfWidthMap.put('\u30A1', "\uFF67");
+        sHalfWidthMap.put('\u30A2', "\uFF71");
+        sHalfWidthMap.put('\u30A3', "\uFF68");
+        sHalfWidthMap.put('\u30A4', "\uFF72");
+        sHalfWidthMap.put('\u30A5', "\uFF69");
+        sHalfWidthMap.put('\u30A6', "\uFF73");
+        sHalfWidthMap.put('\u30A7', "\uFF6A");
+        sHalfWidthMap.put('\u30A8', "\uFF74");
+        sHalfWidthMap.put('\u30A9', "\uFF6B");
+        sHalfWidthMap.put('\u30AA', "\uFF75");
+        sHalfWidthMap.put('\u30AB', "\uFF76");
+        sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
+        sHalfWidthMap.put('\u30AD', "\uFF77");
+        sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
+        sHalfWidthMap.put('\u30AF', "\uFF78");
+        sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
+        sHalfWidthMap.put('\u30B1', "\uFF79");
+        sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
+        sHalfWidthMap.put('\u30B3', "\uFF7A");
+        sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
+        sHalfWidthMap.put('\u30B5', "\uFF7B");
+        sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
+        sHalfWidthMap.put('\u30B7', "\uFF7C");
+        sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
+        sHalfWidthMap.put('\u30B9', "\uFF7D");
+        sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
+        sHalfWidthMap.put('\u30BB', "\uFF7E");
+        sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
+        sHalfWidthMap.put('\u30BD', "\uFF7F");
+        sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
+        sHalfWidthMap.put('\u30BF', "\uFF80");
+        sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
+        sHalfWidthMap.put('\u30C1', "\uFF81");
+        sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
+        sHalfWidthMap.put('\u30C3', "\uFF6F");
+        sHalfWidthMap.put('\u30C4', "\uFF82");
+        sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
+        sHalfWidthMap.put('\u30C6', "\uFF83");
+        sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
+        sHalfWidthMap.put('\u30C8', "\uFF84");
+        sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
+        sHalfWidthMap.put('\u30CA', "\uFF85");
+        sHalfWidthMap.put('\u30CB', "\uFF86");
+        sHalfWidthMap.put('\u30CC', "\uFF87");
+        sHalfWidthMap.put('\u30CD', "\uFF88");
+        sHalfWidthMap.put('\u30CE', "\uFF89");
+        sHalfWidthMap.put('\u30CF', "\uFF8A");
+        sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
+        sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
+        sHalfWidthMap.put('\u30D2', "\uFF8B");
+        sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
+        sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
+        sHalfWidthMap.put('\u30D5', "\uFF8C");
+        sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
+        sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
+        sHalfWidthMap.put('\u30D8', "\uFF8D");
+        sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
+        sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
+        sHalfWidthMap.put('\u30DB', "\uFF8E");
+        sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
+        sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
+        sHalfWidthMap.put('\u30DE', "\uFF8F");
+        sHalfWidthMap.put('\u30DF', "\uFF90");
+        sHalfWidthMap.put('\u30E0', "\uFF91");
+        sHalfWidthMap.put('\u30E1', "\uFF92");
+        sHalfWidthMap.put('\u30E2', "\uFF93");
+        sHalfWidthMap.put('\u30E3', "\uFF6C");
+        sHalfWidthMap.put('\u30E4', "\uFF94");
+        sHalfWidthMap.put('\u30E5', "\uFF6D");
+        sHalfWidthMap.put('\u30E6', "\uFF95");
+        sHalfWidthMap.put('\u30E7', "\uFF6E");
+        sHalfWidthMap.put('\u30E8', "\uFF96");
+        sHalfWidthMap.put('\u30E9', "\uFF97");
+        sHalfWidthMap.put('\u30EA', "\uFF98");
+        sHalfWidthMap.put('\u30EB', "\uFF99");
+        sHalfWidthMap.put('\u30EC', "\uFF9A");
+        sHalfWidthMap.put('\u30ED', "\uFF9B");
+        sHalfWidthMap.put('\u30EE', "\uFF9C");
+        sHalfWidthMap.put('\u30EF', "\uFF9C");
+        sHalfWidthMap.put('\u30F0', "\uFF72");
+        sHalfWidthMap.put('\u30F1', "\uFF74");
+        sHalfWidthMap.put('\u30F2', "\uFF66");
+        sHalfWidthMap.put('\u30F3', "\uFF9D");
+        sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
+        sHalfWidthMap.put('\u30F5', "\uFF76");
+        sHalfWidthMap.put('\u30F6', "\uFF79");
+        sHalfWidthMap.put('\u30FB', "\uFF65");
+        sHalfWidthMap.put('\u30FC', "\uFF70");
+        sHalfWidthMap.put('\uFF01', "!");
+        sHalfWidthMap.put('\uFF02', "\"");
+        sHalfWidthMap.put('\uFF03', "#");
+        sHalfWidthMap.put('\uFF04', "$");
+        sHalfWidthMap.put('\uFF05', "%");
+        sHalfWidthMap.put('\uFF06', "&");
+        sHalfWidthMap.put('\uFF07', "'");
+        sHalfWidthMap.put('\uFF08', "(");
+        sHalfWidthMap.put('\uFF09', ")");
+        sHalfWidthMap.put('\uFF0A', "*");
+        sHalfWidthMap.put('\uFF0B', "+");
+        sHalfWidthMap.put('\uFF0C', ",");
+        sHalfWidthMap.put('\uFF0D', "-");
+        sHalfWidthMap.put('\uFF0E', ".");
+        sHalfWidthMap.put('\uFF0F', "/");
+        sHalfWidthMap.put('\uFF10', "0");
+        sHalfWidthMap.put('\uFF11', "1");
+        sHalfWidthMap.put('\uFF12', "2");
+        sHalfWidthMap.put('\uFF13', "3");
+        sHalfWidthMap.put('\uFF14', "4");
+        sHalfWidthMap.put('\uFF15', "5");
+        sHalfWidthMap.put('\uFF16', "6");
+        sHalfWidthMap.put('\uFF17', "7");
+        sHalfWidthMap.put('\uFF18', "8");
+        sHalfWidthMap.put('\uFF19', "9");
+        sHalfWidthMap.put('\uFF1A', ":");
+        sHalfWidthMap.put('\uFF1B', ";");
+        sHalfWidthMap.put('\uFF1C', "<");
+        sHalfWidthMap.put('\uFF1D', "=");
+        sHalfWidthMap.put('\uFF1E', ">");
+        sHalfWidthMap.put('\uFF1F', "?");
+        sHalfWidthMap.put('\uFF20', "@");
+        sHalfWidthMap.put('\uFF21', "A");
+        sHalfWidthMap.put('\uFF22', "B");
+        sHalfWidthMap.put('\uFF23', "C");
+        sHalfWidthMap.put('\uFF24', "D");
+        sHalfWidthMap.put('\uFF25', "E");
+        sHalfWidthMap.put('\uFF26', "F");
+        sHalfWidthMap.put('\uFF27', "G");
+        sHalfWidthMap.put('\uFF28', "H");
+        sHalfWidthMap.put('\uFF29', "I");
+        sHalfWidthMap.put('\uFF2A', "J");
+        sHalfWidthMap.put('\uFF2B', "K");
+        sHalfWidthMap.put('\uFF2C', "L");
+        sHalfWidthMap.put('\uFF2D', "M");
+        sHalfWidthMap.put('\uFF2E', "N");
+        sHalfWidthMap.put('\uFF2F', "O");
+        sHalfWidthMap.put('\uFF30', "P");
+        sHalfWidthMap.put('\uFF31', "Q");
+        sHalfWidthMap.put('\uFF32', "R");
+        sHalfWidthMap.put('\uFF33', "S");
+        sHalfWidthMap.put('\uFF34', "T");
+        sHalfWidthMap.put('\uFF35', "U");
+        sHalfWidthMap.put('\uFF36', "V");
+        sHalfWidthMap.put('\uFF37', "W");
+        sHalfWidthMap.put('\uFF38', "X");
+        sHalfWidthMap.put('\uFF39', "Y");
+        sHalfWidthMap.put('\uFF3A', "Z");
+        sHalfWidthMap.put('\uFF3B', "[");
+        sHalfWidthMap.put('\uFF3C', "\\");
+        sHalfWidthMap.put('\uFF3D', "]");
+        sHalfWidthMap.put('\uFF3E', "^");
+        sHalfWidthMap.put('\uFF3F', "_");
+        sHalfWidthMap.put('\uFF41', "a");
+        sHalfWidthMap.put('\uFF42', "b");
+        sHalfWidthMap.put('\uFF43', "c");
+        sHalfWidthMap.put('\uFF44', "d");
+        sHalfWidthMap.put('\uFF45', "e");
+        sHalfWidthMap.put('\uFF46', "f");
+        sHalfWidthMap.put('\uFF47', "g");
+        sHalfWidthMap.put('\uFF48', "h");
+        sHalfWidthMap.put('\uFF49', "i");
+        sHalfWidthMap.put('\uFF4A', "j");
+        sHalfWidthMap.put('\uFF4B', "k");
+        sHalfWidthMap.put('\uFF4C', "l");
+        sHalfWidthMap.put('\uFF4D', "m");
+        sHalfWidthMap.put('\uFF4E', "n");
+        sHalfWidthMap.put('\uFF4F', "o");
+        sHalfWidthMap.put('\uFF50', "p");
+        sHalfWidthMap.put('\uFF51', "q");
+        sHalfWidthMap.put('\uFF52', "r");
+        sHalfWidthMap.put('\uFF53', "s");
+        sHalfWidthMap.put('\uFF54', "t");
+        sHalfWidthMap.put('\uFF55', "u");
+        sHalfWidthMap.put('\uFF56', "v");
+        sHalfWidthMap.put('\uFF57', "w");
+        sHalfWidthMap.put('\uFF58', "x");
+        sHalfWidthMap.put('\uFF59', "y");
+        sHalfWidthMap.put('\uFF5A', "z");
+        sHalfWidthMap.put('\uFF5B', "{");
+        sHalfWidthMap.put('\uFF5C', "|");
+        sHalfWidthMap.put('\uFF5D', "}");
+        sHalfWidthMap.put('\uFF5E', "~");
+        sHalfWidthMap.put('\uFF61', "\uFF61");
+        sHalfWidthMap.put('\uFF62', "\uFF62");
+        sHalfWidthMap.put('\uFF63', "\uFF63");
+        sHalfWidthMap.put('\uFF64', "\uFF64");
+        sHalfWidthMap.put('\uFF65', "\uFF65");
+        sHalfWidthMap.put('\uFF66', "\uFF66");
+        sHalfWidthMap.put('\uFF67', "\uFF67");
+        sHalfWidthMap.put('\uFF68', "\uFF68");
+        sHalfWidthMap.put('\uFF69', "\uFF69");
+        sHalfWidthMap.put('\uFF6A', "\uFF6A");
+        sHalfWidthMap.put('\uFF6B', "\uFF6B");
+        sHalfWidthMap.put('\uFF6C', "\uFF6C");
+        sHalfWidthMap.put('\uFF6D', "\uFF6D");
+        sHalfWidthMap.put('\uFF6E', "\uFF6E");
+        sHalfWidthMap.put('\uFF6F', "\uFF6F");
+        sHalfWidthMap.put('\uFF70', "\uFF70");
+        sHalfWidthMap.put('\uFF71', "\uFF71");
+        sHalfWidthMap.put('\uFF72', "\uFF72");
+        sHalfWidthMap.put('\uFF73', "\uFF73");
+        sHalfWidthMap.put('\uFF74', "\uFF74");
+        sHalfWidthMap.put('\uFF75', "\uFF75");
+        sHalfWidthMap.put('\uFF76', "\uFF76");
+        sHalfWidthMap.put('\uFF77', "\uFF77");
+        sHalfWidthMap.put('\uFF78', "\uFF78");
+        sHalfWidthMap.put('\uFF79', "\uFF79");
+        sHalfWidthMap.put('\uFF7A', "\uFF7A");
+        sHalfWidthMap.put('\uFF7B', "\uFF7B");
+        sHalfWidthMap.put('\uFF7C', "\uFF7C");
+        sHalfWidthMap.put('\uFF7D', "\uFF7D");
+        sHalfWidthMap.put('\uFF7E', "\uFF7E");
+        sHalfWidthMap.put('\uFF7F', "\uFF7F");
+        sHalfWidthMap.put('\uFF80', "\uFF80");
+        sHalfWidthMap.put('\uFF81', "\uFF81");
+        sHalfWidthMap.put('\uFF82', "\uFF82");
+        sHalfWidthMap.put('\uFF83', "\uFF83");
+        sHalfWidthMap.put('\uFF84', "\uFF84");
+        sHalfWidthMap.put('\uFF85', "\uFF85");
+        sHalfWidthMap.put('\uFF86', "\uFF86");
+        sHalfWidthMap.put('\uFF87', "\uFF87");
+        sHalfWidthMap.put('\uFF88', "\uFF88");
+        sHalfWidthMap.put('\uFF89', "\uFF89");
+        sHalfWidthMap.put('\uFF8A', "\uFF8A");
+        sHalfWidthMap.put('\uFF8B', "\uFF8B");
+        sHalfWidthMap.put('\uFF8C', "\uFF8C");
+        sHalfWidthMap.put('\uFF8D', "\uFF8D");
+        sHalfWidthMap.put('\uFF8E', "\uFF8E");
+        sHalfWidthMap.put('\uFF8F', "\uFF8F");
+        sHalfWidthMap.put('\uFF90', "\uFF90");
+        sHalfWidthMap.put('\uFF91', "\uFF91");
+        sHalfWidthMap.put('\uFF92', "\uFF92");
+        sHalfWidthMap.put('\uFF93', "\uFF93");
+        sHalfWidthMap.put('\uFF94', "\uFF94");
+        sHalfWidthMap.put('\uFF95', "\uFF95");
+        sHalfWidthMap.put('\uFF96', "\uFF96");
+        sHalfWidthMap.put('\uFF97', "\uFF97");
+        sHalfWidthMap.put('\uFF98', "\uFF98");
+        sHalfWidthMap.put('\uFF99', "\uFF99");
+        sHalfWidthMap.put('\uFF9A', "\uFF9A");
+        sHalfWidthMap.put('\uFF9B', "\uFF9B");
+        sHalfWidthMap.put('\uFF9C', "\uFF9C");
+        sHalfWidthMap.put('\uFF9D', "\uFF9D");
+        sHalfWidthMap.put('\uFF9E', "\uFF9E");
+        sHalfWidthMap.put('\uFF9F', "\uFF9F");
+        sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
+    }
+
+    /**
+     * Return half-width version of that character if possible. Return null if not possible
+     * @param ch input character
+     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+     */
+    public static String tryGetHalfWidthText(char ch) {
+        if (sHalfWidthMap.containsKey(ch)) {
+            return sHalfWidthMap.get(ch);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index e1c4b33..3980940 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -1,64 +1,1890 @@
 /*
  * 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
+ * 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
+ * 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.
+ * 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.pim.vcard;
 
+import android.content.ContentValues;
+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.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-public interface VCardBuilder {
-    void start();
+/**
+ * The class which lets users create their own vCard String.
+ */
+public class VCardBuilder {
+    private static final String LOG_TAG = "VCardBuilder";
 
-    void end();
+    // If you add the other element, please check all the columns are able to be
+    // converted to String.
+    //
+    // e.g. BLOB is not what we can handle here now.
+    private static final Set<String> sAllowedAndroidPropertySet =
+            Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+                    Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+                    Relation.CONTENT_ITEM_TYPE)));
 
-    /** 
-     * BEGIN:VCARD
-     */
-    void startRecord(String type);
+    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;
 
-    /** END:VXX */
-    void endRecord();
+    private static final String VCARD_DATA_VCARD = "VCARD";
+    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
 
-    void startProperty();
+    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 = "=";
 
-    void endProperty();
+    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";
+
+    private final int mVCardType;
+
+    private final boolean mIsV30;
+    private final boolean mIsJapaneseMobilePhone;
+    private final boolean mOnlyOneNoteFieldIsAvailable;
+    private final boolean mIsDoCoMo;
+    private final boolean mShouldUseQuotedPrintable;
+    private final boolean mUsesAndroidProperty;
+    private final boolean mUsesDefactProperty;
+    private final boolean mUsesUtf8;
+    private final boolean mUsesShiftJis;
+    private final boolean mAppendTypeParamName;
+    private final boolean mRefrainsQPToNameProperties;
+    private final boolean mNeedsToConvertPhoneticString;
+
+    private final boolean mShouldAppendCharsetParam;
+
+    private final String mCharsetString;
+    private final String mVCardCharsetParameter;
+
+    private StringBuilder mBuilder;
+    private boolean mEndAppended;
+
+    public VCardBuilder(final int vcardType) {
+        mVCardType = vcardType;
+
+        mIsV30 = VCardConfig.isV30(vcardType);
+        mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
+        mIsDoCoMo = VCardConfig.isDoCoMo(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);
+        mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
+        mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+        mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
+
+        mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+
+        if (mIsDoCoMo) {
+            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).
+            mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+        } else if (mUsesShiftJis) {
+            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;
+            mVCardCharsetParameter = "CHARSET=" + UTF_8;
+        }
+        clear();
+    }
+
+    public void clear() {
+        mBuilder = new StringBuilder();
+        mEndAppended = false;
+        appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        if (mIsV30) {
+            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
+        } else {
+            appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
+        }
+    }
+
+    private boolean containsNonEmptyName(final ContentValues contentValues) {
+        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+        final String phoneticFamilyName =
+                contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+        final String phoneticMiddleName =
+                contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+        final String phoneticGivenName =
+                contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+        return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+                TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+                TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
+                TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
+                TextUtils.isEmpty(displayName));
+    }
+
+    private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
+        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) {
+                // We choose "super primary" ContentValues.
+                primaryContentValues = contentValues;
+                break;
+            } else if (primaryContentValues == null) {
+                // We choose the first "primary" ContentValues
+                // if "super primary" ContentValues does not exist.
+                final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+                if (isPrimary != null && isPrimary > 0 &&
+                        containsNonEmptyName(contentValues)) {
+                    primaryContentValues = contentValues;
+                    // Do not break, since there may be ContentValues with "super primary"
+                    // afterword.
+                } else if (subprimaryContentValues == null &&
+                        containsNonEmptyName(contentValues)) {
+                    subprimaryContentValues = contentValues;
+                }
+            }
+        }
+
+        if (primaryContentValues == null) {
+            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();
+            }
+        }
+
+        return primaryContentValues;
+    }
 
     /**
-     * @param group 
+     * For safety, we'll emit just one value around StructuredName, as external importers
+     * may get confused with multiple "N", "FN", etc. properties, though it is valid in
+     * vCard spec.
      */
-    void propertyGroup(String group);
-    
-    /**
-     * @param name
-     *            N <br>
-     *            N
-     */
-    void propertyName(String name);
+    public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+        if (contentValuesList == null || contentValuesList.isEmpty()) {
+            if (mIsDoCoMo) {
+                appendLine(VCardConstants.PROPERTY_N, "");
+            } else if (mIsV30) {
+                // vCard 3.0 requires "N" and "FN" properties.
+                appendLine(VCardConstants.PROPERTY_N, "");
+                appendLine(VCardConstants.PROPERTY_FN, "");
+            }
+            return this;
+        }
+
+        final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
+        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+
+        if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+            final boolean reallyAppendCharsetParameterToName =
+                    shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
+            final boolean reallyUseQuotedPrintableToName =
+                    (!mRefrainsQPToNameProperties &&
+                            !(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 reallyAppendCharsetParameterToFN =
+                    shouldAppendCharsetParam(formattedName);
+            final boolean reallyUseQuotedPrintableToFN =
+                    !mRefrainsQPToNameProperties &&
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
+            final String encodedFamily;
+            final String encodedGiven;
+            final String encodedMiddle;
+            final String encodedPrefix;
+            final String encodedSuffix;
+            if (reallyUseQuotedPrintableToName) {
+                encodedFamily = encodeQuotedPrintable(familyName);
+                encodedGiven = encodeQuotedPrintable(givenName);
+                encodedMiddle = encodeQuotedPrintable(middleName);
+                encodedPrefix = encodeQuotedPrintable(prefix);
+                encodedSuffix = encodeQuotedPrintable(suffix);
+            } else {
+                encodedFamily = escapeCharacters(familyName);
+                encodedGiven = escapeCharacters(givenName);
+                encodedMiddle = escapeCharacters(middleName);
+                encodedPrefix = escapeCharacters(prefix);
+                encodedSuffix = escapeCharacters(suffix);
+            }
+
+            final String encodedFormattedname =
+                    (reallyUseQuotedPrintableToFN ?
+                            encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+            mBuilder.append(VCardConstants.PROPERTY_N);
+            if (mIsDoCoMo) {
+                if (reallyAppendCharsetParameterToName) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintableToName) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                // DoCoMo phones require that all the elements in the "family name" field.
+                mBuilder.append(formattedName);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+            } else {
+                if (reallyAppendCharsetParameterToName) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintableToName) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(encodedFamily);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(encodedGiven);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(encodedMiddle);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(encodedPrefix);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(encodedSuffix);
+            }
+            mBuilder.append(VCARD_END_OF_LINE);
+
+            // FN property
+            mBuilder.append(VCardConstants.PROPERTY_FN);
+            if (reallyAppendCharsetParameterToFN) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(mVCardCharsetParameter);
+            }
+            if (reallyUseQuotedPrintableToFN) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(VCARD_PARAM_ENCODING_QP);
+            }
+            mBuilder.append(VCARD_DATA_SEPARATOR);
+            mBuilder.append(encodedFormattedname);
+            mBuilder.append(VCARD_END_OF_LINE);
+        } else if (!TextUtils.isEmpty(displayName)) {
+            final boolean reallyUseQuotedPrintableToDisplayName =
+                (!mRefrainsQPToNameProperties &&
+                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
+            final String encodedDisplayName =
+                    reallyUseQuotedPrintableToDisplayName ?
+                            encodeQuotedPrintable(displayName) :
+                                escapeCharacters(displayName);
+
+            mBuilder.append(VCardConstants.PROPERTY_N);
+            if (shouldAppendCharsetParam(displayName)) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(mVCardCharsetParameter);
+            }
+            if (reallyUseQuotedPrintableToDisplayName) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(VCARD_PARAM_ENCODING_QP);
+            }
+            mBuilder.append(VCARD_DATA_SEPARATOR);
+            mBuilder.append(encodedDisplayName);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_END_OF_LINE);
+            mBuilder.append(VCardConstants.PROPERTY_FN);
+
+            // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
+            //       when it would be useful for external importers, assuming no external
+            //       importer allows this vioration.
+            if (shouldAppendCharsetParam(displayName)) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(mVCardCharsetParameter);
+            }
+            mBuilder.append(VCARD_DATA_SEPARATOR);
+            mBuilder.append(encodedDisplayName);
+            mBuilder.append(VCARD_END_OF_LINE);
+        } else if (mIsV30) {
+            // vCard 3.0 specification requires these fields.
+            appendLine(VCardConstants.PROPERTY_N, "");
+            appendLine(VCardConstants.PROPERTY_FN, "");
+        } else if (mIsDoCoMo) {
+            appendLine(VCardConstants.PROPERTY_N, "");
+        }
+
+        appendPhoneticNameFields(contentValues);
+        return this;
+    }
+
+    private void appendPhoneticNameFields(final ContentValues contentValues) {
+        final String phoneticFamilyName;
+        final String phoneticMiddleName;
+        final String phoneticGivenName;
+        {
+            final String tmpPhoneticFamilyName =
+                contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+            final String tmpPhoneticMiddleName =
+                contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+            final String tmpPhoneticGivenName =
+                contentValues.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 (mIsDoCoMo) {
+                mBuilder.append(VCardConstants.PROPERTY_SOUND);
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+                mBuilder.append(VCARD_END_OF_LINE);
+            }
+            return;
+        }
+
+        // Try to emit the field(s) related to phonetic name.
+        if (mIsV30) {
+            final String sortString = VCardUtils
+                    .constructNameFromElements(mVCardType,
+                            phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+            mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
+            if (shouldAppendCharsetParam(sortString)) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(mVCardCharsetParameter);
+            }
+            mBuilder.append(VCARD_DATA_SEPARATOR);
+            mBuilder.append(escapeCharacters(sortString));
+            mBuilder.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 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.
+            //
+            //       Also, DoCoMo's specification requires vCard composer to use just the first
+            //       column.
+            //       i.e.
+            //       o  SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+            //       x  SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+            mBuilder.append(VCardConstants.PROPERTY_SOUND);
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+
+            boolean reallyUseQuotedPrintable =
+                (!mRefrainsQPToNameProperties
+                        && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                phoneticFamilyName)
+                                && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                        phoneticMiddleName)
+                                && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+                                        phoneticGivenName)));
+
+            final String encodedPhoneticFamilyName;
+            final String encodedPhoneticMiddleName;
+            final String encodedPhoneticGivenName;
+            if (reallyUseQuotedPrintable) {
+                encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+                encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+                encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+            } else {
+                encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+                encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+                encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+            }
+
+            if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
+                    encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(mVCardCharsetParameter);
+            }
+            mBuilder.append(VCARD_DATA_SEPARATOR);
+            {
+                boolean first = true;
+                if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
+                    mBuilder.append(encodedPhoneticFamilyName);
+                    first = false;
+                }
+                if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        mBuilder.append(' ');
+                    }
+                    mBuilder.append(encodedPhoneticMiddleName);
+                }
+                if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
+                    if (!first) {
+                        mBuilder.append(' ');
+                    }
+                    mBuilder.append(encodedPhoneticGivenName);
+                }
+            }
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_ITEM_SEPARATOR);
+            mBuilder.append(VCARD_END_OF_LINE);
+        }
+
+        if (mUsesDefactProperty) {
+            if (!TextUtils.isEmpty(phoneticGivenName)) {
+                final boolean reallyUseQuotedPrintable =
+                    (mShouldUseQuotedPrintable &&
+                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+                final String encodedPhoneticGivenName;
+                if (reallyUseQuotedPrintable) {
+                    encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+                } else {
+                    encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+                }
+                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
+                if (shouldAppendCharsetParam(phoneticGivenName)) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintable) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(encodedPhoneticGivenName);
+                mBuilder.append(VCARD_END_OF_LINE);
+            }
+            if (!TextUtils.isEmpty(phoneticMiddleName)) {
+                final boolean reallyUseQuotedPrintable =
+                    (mShouldUseQuotedPrintable &&
+                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+                final String encodedPhoneticMiddleName;
+                if (reallyUseQuotedPrintable) {
+                    encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+                } else {
+                    encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+                }
+                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+                if (shouldAppendCharsetParam(phoneticMiddleName)) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintable) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(encodedPhoneticMiddleName);
+                mBuilder.append(VCARD_END_OF_LINE);
+            }
+            if (!TextUtils.isEmpty(phoneticFamilyName)) {
+                final boolean reallyUseQuotedPrintable =
+                    (mShouldUseQuotedPrintable &&
+                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+                final String encodedPhoneticFamilyName;
+                if (reallyUseQuotedPrintable) {
+                    encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+                } else {
+                    encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+                }
+                mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
+                if (shouldAppendCharsetParam(phoneticFamilyName)) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(mVCardCharsetParameter);
+                }
+                if (reallyUseQuotedPrintable) {
+                    mBuilder.append(VCARD_PARAM_SEPARATOR);
+                    mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                }
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(encodedPhoneticFamilyName);
+                mBuilder.append(VCARD_END_OF_LINE);
+            }
+        }
+    }
+
+    public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
+        final boolean useAndroidProperty;
+        if (mIsV30) {
+            useAndroidProperty = false;
+        } else if (mUsesAndroidProperty) {
+            useAndroidProperty = true;
+        } else {
+            // There's no way to add this field.
+            return this;
+        }
+        if (contentValuesList != null) {
+            for (ContentValues contentValues : contentValuesList) {
+                final String nickname = contentValues.getAsString(Nickname.NAME);
+                if (TextUtils.isEmpty(nickname)) {
+                    continue;
+                }
+                if (useAndroidProperty) {
+                    appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
+                } else {
+                    appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
+                }
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
+        boolean phoneLineExists = false;
+        if (contentValuesList != null) {
+            Set<String> phoneSet = new HashSet<String>();
+            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();
+                }
+                if (TextUtils.isEmpty(phoneNumber)) {
+                    continue;
+                }
+                int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+                if (type == Phone.TYPE_PAGER) {
+                    phoneLineExists = true;
+                    if (!phoneSet.contains(phoneNumber)) {
+                        phoneSet.add(phoneNumber);
+                        appendTelLine(type, label, phoneNumber, isPrimary);
+                    }
+                } else {
+                    // The entry "may" have several phone numbers when the contact entry is
+                    // corrupted because of its original source.
+                    //
+                    // e.g. I encountered the entry like the following.
+                    // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
+                    // This kind of entry is not able to be inserted via Android devices, but
+                    // possible if the source of the data is already corrupted.
+                    List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+                    if (phoneNumberList.isEmpty()) {
+                        continue;
+                    }
+                    phoneLineExists = true;
+                    for (String actualPhoneNumber : phoneNumberList) {
+                        if (!phoneSet.contains(actualPhoneNumber)) {
+                            final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+                            final String formattedPhoneNumber =
+                                    PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
+                            phoneSet.add(actualPhoneNumber);
+                            appendTelLine(type, label, formattedPhoneNumber, isPrimary);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!phoneLineExists && mIsDoCoMo) {
+            appendTelLine(Phone.TYPE_HOME, "", "", false);
+        }
+
+        return this;
+    }
+
+    private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+        List<String> phoneList = new ArrayList<String>();
+
+        StringBuilder builder = new StringBuilder();
+        final int length = phoneNumber.length();
+        for (int i = 0; i < length; i++) {
+            final char ch = phoneNumber.charAt(i);
+            if (Character.isDigit(ch)) {
+                builder.append(ch);
+            } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+                phoneList.add(builder.toString());
+                builder = new StringBuilder();
+            }
+        }
+        if (builder.length() > 0) {
+            phoneList.add(builder.toString());
+        }
+
+        return phoneList;
+    }
+
+    public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
+        boolean emailAddressExists = false;
+        if (contentValuesList != null) {
+            final Set<String> addressSet = new HashSet<String>();
+            for (ContentValues contentValues : contentValuesList) {
+                String emailAddress = contentValues.getAsString(Email.DATA);
+                if (emailAddress != null) {
+                    emailAddress = emailAddress.trim();
+                }
+                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);
+                    appendEmailLine(type, label, emailAddress, isPrimary);
+                }
+            }
+        }
+
+        if (!emailAddressExists && mIsDoCoMo) {
+            appendEmailLine(Email.TYPE_HOME, "", "", false);
+        }
+
+        return this;
+    }
+
+    public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
+        if (contentValuesList == null || contentValuesList.isEmpty()) {
+            if (mIsDoCoMo) {
+                mBuilder.append(VCardConstants.PROPERTY_ADR);
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
+                mBuilder.append(VCARD_DATA_SEPARATOR);
+                mBuilder.append(VCARD_END_OF_LINE);
+            }
+        } else {
+            if (mIsDoCoMo) {
+                appendPostalsForDoCoMo(contentValuesList);
+            } else {
+                appendPostalsForGeneric(contentValuesList);
+            }
+        }
+
+        return this;
+    }
+
+    private static final Map<Integer, Integer> sPostalTypePriorityMap;
+
+    static {
+        sPostalTypePriorityMap = new HashMap<Integer, Integer>();
+        sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
+        sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
+        sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
+        sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
+    }
 
     /**
-     * @param type
-     *            LANGUAGE \ ENCODING <br>
-     *            ;LANGUage= \ ;ENCODING=
+     * Tries to append just one line. If there's no appropriate address
+     * information, append an empty line.
      */
-    void propertyParamType(String type);
+    private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
+        int currentPriority = Integer.MAX_VALUE;
+        int currentType = Integer.MAX_VALUE;
+        ContentValues currentContentValues = null;
+        for (final ContentValues contentValues : contentValuesList) {
+            if (contentValues == null) {
+                continue;
+            }
+            final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+            final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
+            final int priority =
+                    (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
+            if (priority < currentPriority) {
+                currentPriority = priority;
+                currentType = typeAsInteger;
+                currentContentValues = contentValues;
+                if (priority == 0) {
+                    break;
+                }
+            }
+        }
+
+        if (currentContentValues == null) {
+            Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
+            return;
+        }
+
+        final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
+        appendPostalLine(currentType, label, currentContentValues, false, true);
+    }
+
+    private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
+        for (final ContentValues contentValues : contentValuesList) {
+            if (contentValues == null) {
+                continue;
+            }
+            final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+            final int type = (typeAsInteger != null ?
+                    typeAsInteger : 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);
+            appendPostalLine(type, label, contentValues, isPrimary, false);
+        }
+    }
+
+    private static 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;
+        }
+    }
 
     /**
-     * @param value
-     *            FR-EN \ GBK <br>
-     *            FR-EN \ GBK
+     * @return null when there's no information available to construct the data.
      */
-    void propertyParamValue(String value);
+    private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+        // adr-value    = 0*6(text-value ";") text-value
+        //              ; PO Box, Extended Address, Street, Locality, Region, Postal
+        //              ; Code, Country Name
+        final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
+        final String rawNeighborhood = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+        final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
+        final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
+        final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
+        final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
+        final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
+        final String[] rawAddressArray = new String[]{
+                rawPoBox, rawNeighborhood, rawStreet, rawLocality,
+                rawRegion, rawPostalCode, rawCountry};
+        if (!VCardUtils.areAllEmpty(rawAddressArray)) {
+            final boolean reallyUseQuotedPrintable =
+                (mShouldUseQuotedPrintable &&
+                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
+            final boolean appendCharset =
+                !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
+            final String encodedPoBox;
+            final String encodedStreet;
+            final String encodedLocality;
+            final String encodedRegion;
+            final String encodedPostalCode;
+            final String encodedCountry;
+            final String encodedNeighborhood;
 
-    void propertyValues(List<String> values);
+            final String rawLocality2;
+            // This looks inefficient since we encode rawLocality and rawNeighborhood twice,
+            // but this is intentional.
+            //
+            // QP encoding may add line feeds when needed and the result of
+            // - encodeQuotedPrintable(rawLocality + " " + rawNeighborhood)
+            // may be different from
+            // - encodedLocality + " " + encodedNeighborhood.
+            //
+            // We use safer way.
+            if (TextUtils.isEmpty(rawLocality)) {
+                if (TextUtils.isEmpty(rawNeighborhood)) {
+                    rawLocality2 = "";
+                } else {
+                    rawLocality2 = rawNeighborhood;
+                }
+            } else {
+                if (TextUtils.isEmpty(rawNeighborhood)) {
+                    rawLocality2 = rawLocality;
+                } else {
+                    rawLocality2 = rawLocality + " " + rawNeighborhood;
+                }
+            }
+            if (reallyUseQuotedPrintable) {
+                encodedPoBox = encodeQuotedPrintable(rawPoBox);
+                encodedStreet = encodeQuotedPrintable(rawStreet);
+                encodedLocality = encodeQuotedPrintable(rawLocality2);
+                encodedRegion = encodeQuotedPrintable(rawRegion);
+                encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
+                encodedCountry = encodeQuotedPrintable(rawCountry);
+            } else {
+                encodedPoBox = escapeCharacters(rawPoBox);
+                encodedStreet = escapeCharacters(rawStreet);
+                encodedLocality = escapeCharacters(rawLocality2);
+                encodedRegion = escapeCharacters(rawRegion);
+                encodedPostalCode = escapeCharacters(rawPostalCode);
+                encodedCountry = escapeCharacters(rawCountry);
+                encodedNeighborhood = escapeCharacters(rawNeighborhood);
+            }
+            final StringBuffer addressBuffer = new StringBuffer();
+            addressBuffer.append(encodedPoBox);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedStreet);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedLocality);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedRegion);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedPostalCode);
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedCountry);
+            return new PostalStruct(
+                    reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+        } else {  // VCardUtils.areAllEmpty(rawAddressArray) == true
+            // Try to use FORMATTED_ADDRESS instead.
+            final String rawFormattedAddress =
+                contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+            if (TextUtils.isEmpty(rawFormattedAddress)) {
+                return null;
+            }
+            final boolean reallyUseQuotedPrintable =
+                (mShouldUseQuotedPrintable &&
+                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
+            final boolean appendCharset =
+                !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
+            final String encodedFormattedAddress;
+            if (reallyUseQuotedPrintable) {
+                encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
+            } else {
+                encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
+            }
+
+            // We use the second value ("Extended Address") just because Japanese mobile phones
+            // do so. If the other importer expects the value be in the other field, some flag may
+            // be needed.
+            final StringBuffer addressBuffer = new StringBuffer();
+            addressBuffer.append(VCARD_ITEM_SEPARATOR);
+            addressBuffer.append(encodedFormattedAddress);
+            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());
+        }
+    }
+
+    public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            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 = VCardConstants.PARAM_TYPE_HOME;
+                            break;
+                        }
+                        case Im.TYPE_WORK: {
+                            typeAsString = VCardConstants.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;
+                        }
+                    }
+                }
+
+                final 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(VCardConstants.PARAM_TYPE_PREF);
+                }
+
+                appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            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.
+                if (!TextUtils.isEmpty(website)) {
+                    appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_URL, website);
+                }
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            for (ContentValues contentValues : contentValuesList) {
+                String company = contentValues.getAsString(Organization.COMPANY);
+                if (company != null) {
+                    company = company.trim();
+                }
+                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)) {
+                    orgBuilder.append(company);
+                }
+                if (!TextUtils.isEmpty(department)) {
+                    if (orgBuilder.length() > 0) {
+                        orgBuilder.append(';');
+                    }
+                    orgBuilder.append(department);
+                }
+                final String orgline = orgBuilder.toString();
+                appendLine(VCardConstants.PROPERTY_ORG, orgline,
+                        !VCardUtils.containsOnlyPrintableAscii(orgline),
+                        (mShouldUseQuotedPrintable &&
+                                !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
+                if (!TextUtils.isEmpty(title)) {
+                    appendLine(VCardConstants.PROPERTY_TITLE, title,
+                            !VCardUtils.containsOnlyPrintableAscii(title),
+                            (mShouldUseQuotedPrintable &&
+                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+                }
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            for (ContentValues contentValues : contentValuesList) {
+                if (contentValues == null) {
+                    continue;
+                }
+                byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+                if (data == null) {
+                    continue;
+                }
+                final String photoType = VCardUtils.guessImageType(data);
+                if (photoType == null) {
+                    Log.d(LOG_TAG, "Unknown photo type. Ignored.");
+                    continue;
+                }
+                final String photoString = new String(Base64.encodeBase64(data));
+                if (!TextUtils.isEmpty(photoString)) {
+                    appendPhotoLine(photoString, photoType);
+                }
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            if (mOnlyOneNoteFieldIsAvailable) {
+                final StringBuilder noteBuilder = new StringBuilder();
+                boolean first = true;
+                for (final ContentValues contentValues : contentValuesList) {
+                    String note = contentValues.getAsString(Note.NOTE);
+                    if (note == null) {
+                        note = "";
+                    }
+                    if (note.length() > 0) {
+                        if (first) {
+                            first = false;
+                        } else {
+                            noteBuilder.append('\n');
+                        }
+                        noteBuilder.append(note);
+                    }
+                }
+                final String noteStr = noteBuilder.toString();
+                // This means we scan noteStr completely twice, which is redundant.
+                // But for now, we assume this is not so time-consuming..
+                final boolean shouldAppendCharsetInfo =
+                    !VCardUtils.containsOnlyPrintableAscii(noteStr);
+                final boolean reallyUseQuotedPrintable =
+                        (mShouldUseQuotedPrintable &&
+                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+                appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+                        shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+            } else {
+                for (ContentValues contentValues : contentValuesList) {
+                    final String noteStr = contentValues.getAsString(Note.NOTE);
+                    if (!TextUtils.isEmpty(noteStr)) {
+                        final boolean shouldAppendCharsetInfo =
+                                !VCardUtils.containsOnlyPrintableAscii(noteStr);
+                        final boolean reallyUseQuotedPrintable =
+                                (mShouldUseQuotedPrintable &&
+                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+                        appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+                                shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+                    }
+                }
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+        if (contentValuesList != null) {
+            String primaryBirthday = null;
+            String secondaryBirthday = null;
+            for (final ContentValues contentValues : contentValuesList) {
+                if (contentValues == null) {
+                    continue;
+                }
+                final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
+                final int eventType;
+                if (eventTypeAsInteger != null) {
+                    eventType = eventTypeAsInteger;
+                } else {
+                    eventType = Event.TYPE_OTHER;
+                }
+                if (eventType == Event.TYPE_BIRTHDAY) {
+                    final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+                    if (birthdayCandidate == null) {
+                        continue;
+                    }
+                    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;
+                    }
+                } else if (mUsesAndroidProperty) {
+                    // Event types other than Birthday is not supported by vCard.
+                    appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
+                }
+            }
+            if (primaryBirthday != null) {
+                appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+                        primaryBirthday.trim());
+            } else if (secondaryBirthday != null){
+                appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+                        secondaryBirthday.trim());
+            }
+        }
+        return this;
+    }
+
+    public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
+        if (mUsesAndroidProperty && contentValuesList != null) {
+            for (final ContentValues contentValues : contentValuesList) {
+                if (contentValues == null) {
+                    continue;
+                }
+                appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
+            }
+        }
+        return this;
+    }
+
+    public void appendPostalLine(final int type, final String label,
+            final ContentValues contentValues,
+            final boolean isPrimary, final boolean emitLineEveryTime) {
+        final boolean reallyUseQuotedPrintable;
+        final boolean appendCharset;
+        final String addressValue;
+        {
+            PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+            if (postalStruct == null) {
+                if (emitLineEveryTime) {
+                    reallyUseQuotedPrintable = false;
+                    appendCharset = false;
+                    addressValue = "";
+                } else {
+                    return;
+                }
+            } else {
+                reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+                appendCharset = postalStruct.appendCharset;
+                addressValue = postalStruct.addressData;
+            }
+        }
+
+        List<String> parameterList = new ArrayList<String>();
+        if (isPrimary) {
+            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+        }
+        switch (type) {
+            case StructuredPostal.TYPE_HOME: {
+                parameterList.add(VCardConstants.PARAM_TYPE_HOME);
+                break;
+            }
+            case StructuredPostal.TYPE_WORK: {
+                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+                break;
+            }
+            case StructuredPostal.TYPE_CUSTOM: {
+                if (!TextUtils.isEmpty(label)
+                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+                    // We're not sure whether the label is valid in the spec
+                    // ("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.
+                    parameterList.add("X-" + label);
+                }
+                break;
+            }
+            case StructuredPostal.TYPE_OTHER: {
+                break;
+            }
+            default: {
+                Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+                break;
+            }
+        }
+
+        mBuilder.append(VCardConstants.PROPERTY_ADR);
+        if (!parameterList.isEmpty()) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            appendTypeParameters(parameterList);
+        }
+        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.
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(mVCardCharsetParameter);
+        }
+        if (reallyUseQuotedPrintable) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(VCARD_PARAM_ENCODING_QP);
+        }
+        mBuilder.append(VCARD_DATA_SEPARATOR);
+        mBuilder.append(addressValue);
+        mBuilder.append(VCARD_END_OF_LINE);
+    }
+
+    public void appendEmailLine(final int type, final String label,
+            final String rawValue, final boolean isPrimary) {
+        final String typeAsString;
+        switch (type) {
+            case Email.TYPE_CUSTOM: {
+                if (VCardUtils.isMobilePhoneLabel(label)) {
+                    typeAsString = VCardConstants.PARAM_TYPE_CELL;
+                } else if (!TextUtils.isEmpty(label)
+                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+                    typeAsString = "X-" + label;
+                } else {
+                    typeAsString = null;
+                }
+                break;
+            }
+            case Email.TYPE_HOME: {
+                typeAsString = VCardConstants.PARAM_TYPE_HOME;
+                break;
+            }
+            case Email.TYPE_WORK: {
+                typeAsString = VCardConstants.PARAM_TYPE_WORK;
+                break;
+            }
+            case Email.TYPE_OTHER: {
+                typeAsString = null;
+                break;
+            }
+            case Email.TYPE_MOBILE: {
+                typeAsString = VCardConstants.PARAM_TYPE_CELL;
+                break;
+            }
+            default: {
+                Log.e(LOG_TAG, "Unknown Email type: " + type);
+                typeAsString = null;
+                break;
+            }
+        }
+
+        final List<String> parameterList = new ArrayList<String>();
+        if (isPrimary) {
+            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+        }
+        if (!TextUtils.isEmpty(typeAsString)) {
+            parameterList.add(typeAsString);
+        }
+
+        appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
+                rawValue);
+    }
+
+    public void appendTelLine(final Integer typeAsInteger, final String label,
+            final String encodedValue, boolean isPrimary) {
+        mBuilder.append(VCardConstants.PROPERTY_TEL);
+        mBuilder.append(VCARD_PARAM_SEPARATOR);
+
+        final int type;
+        if (typeAsInteger == null) {
+            type = Phone.TYPE_OTHER;
+        } else {
+            type = typeAsInteger;
+        }
+
+        ArrayList<String> parameterList = new ArrayList<String>();
+        switch (type) {
+            case Phone.TYPE_HOME: {
+                parameterList.addAll(
+                        Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
+                break;
+            }
+            case Phone.TYPE_WORK: {
+                parameterList.addAll(
+                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
+                break;
+            }
+            case Phone.TYPE_FAX_HOME: {
+                parameterList.addAll(
+                        Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
+                break;
+            }
+            case Phone.TYPE_FAX_WORK: {
+                parameterList.addAll(
+                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
+                break;
+            }
+            case Phone.TYPE_MOBILE: {
+                parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+                break;
+            }
+            case Phone.TYPE_PAGER: {
+                if (mIsDoCoMo) {
+                    // Not sure about the reason, but previous implementation had
+                    // used "VOICE" instead of "PAGER"
+                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+                } else {
+                    parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+                }
+                break;
+            }
+            case Phone.TYPE_OTHER: {
+                parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+                break;
+            }
+            case Phone.TYPE_CAR: {
+                parameterList.add(VCardConstants.PARAM_TYPE_CAR);
+                break;
+            }
+            case Phone.TYPE_COMPANY_MAIN: {
+                // There's no relevant field in vCard (at least 2.1).
+                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+                isPrimary = true;
+                break;
+            }
+            case Phone.TYPE_ISDN: {
+                parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
+                break;
+            }
+            case Phone.TYPE_MAIN: {
+                isPrimary = true;
+                break;
+            }
+            case Phone.TYPE_OTHER_FAX: {
+                parameterList.add(VCardConstants.PARAM_TYPE_FAX);
+                break;
+            }
+            case Phone.TYPE_TELEX: {
+                parameterList.add(VCardConstants.PARAM_TYPE_TLX);
+                break;
+            }
+            case Phone.TYPE_WORK_MOBILE: {
+                parameterList.addAll(
+                        Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
+                break;
+            }
+            case Phone.TYPE_WORK_PAGER: {
+                parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+                // See above.
+                if (mIsDoCoMo) {
+                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+                } else {
+                    parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+                }
+                break;
+            }
+            case Phone.TYPE_MMS: {
+                parameterList.add(VCardConstants.PARAM_TYPE_MSG);
+                break;
+            }
+            case Phone.TYPE_CUSTOM: {
+                if (TextUtils.isEmpty(label)) {
+                    // Just ignore the custom type.
+                    parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+                } else if (VCardUtils.isMobilePhoneLabel(label)) {
+                    parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+                } else {
+                    final String upperLabel = label.toUpperCase();
+                    if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
+                        parameterList.add(upperLabel);
+                    } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+                        // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+                        //       "TYPE=" string.
+                        parameterList.add("X-" + label);
+                    }
+                }
+                break;
+            }
+            case Phone.TYPE_RADIO:
+            case Phone.TYPE_TTY_TDD:
+            default: {
+                break;
+            }
+        }
+
+        if (isPrimary) {
+            parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+        }
+
+        if (parameterList.isEmpty()) {
+            appendUncommonPhoneType(mBuilder, type);
+        } else {
+            appendTypeParameters(parameterList);
+        }
+
+        mBuilder.append(VCARD_DATA_SEPARATOR);
+        mBuilder.append(encodedValue);
+        mBuilder.append(VCARD_END_OF_LINE);
+    }
+
+    /**
+     * Appends phone type string which may not be available in some devices.
+     */
+    private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
+        if (mIsDoCoMo) {
+            // The previous implementation for DoCoMo had been conservative
+            // about miscellaneous types.
+            builder.append(VCardConstants.PARAM_TYPE_VOICE);
+        } else {
+            String phoneType = VCardUtils.getPhoneTypeString(type);
+            if (phoneType != null) {
+                appendTypeParameter(phoneType);
+            } else {
+                Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+            }
+        }
+    }
+
+    /**
+     * @param encodedValue Must be encoded by BASE64 
+     * @param photoType
+     */
+    public void appendPhotoLine(final String encodedValue, final String photoType) {
+        StringBuilder tmpBuilder = new StringBuilder();
+        tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
+        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+        if (mIsV30) {
+            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+        } else {
+            tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
+        }
+        tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+        appendTypeParameter(tmpBuilder, photoType);
+        tmpBuilder.append(VCARD_DATA_SEPARATOR);
+        tmpBuilder.append(encodedValue);
+
+        final String tmpStr = tmpBuilder.toString();
+        tmpBuilder = new StringBuilder();
+        int lineCount = 0;
+        final int length = tmpStr.length();
+        final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
+                - VCARD_END_OF_LINE.length();
+        final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
+        int maxNum = maxNumForFirstLine;
+        for (int i = 0; i < length; i++) {
+            tmpBuilder.append(tmpStr.charAt(i));
+            lineCount++;
+            if (lineCount > maxNum) {
+                tmpBuilder.append(VCARD_END_OF_LINE);
+                tmpBuilder.append(VCARD_WS);
+                maxNum = maxNumInGeneral;
+                lineCount = 0;
+            }
+        }
+        mBuilder.append(tmpBuilder.toString());
+        mBuilder.append(VCARD_END_OF_LINE);
+        mBuilder.append(VCARD_END_OF_LINE);
+    }
+
+    public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+        List<String> rawValueList = new ArrayList<String>();
+        rawValueList.add(mimeType);
+        final List<String> columnNameList;
+        if (!sAllowedAndroidPropertySet.contains(mimeType)) {
+            return;
+        }
+
+        for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
+            String value = contentValues.getAsString("data" + i);
+            if (value == null) {
+                value = "";
+            }
+            rawValueList.add(value);
+        }
+
+        appendLineWithCharsetAndQPDetection(
+                VCardConstants.PROPERTY_X_ANDROID_CUSTOM, rawValueList);
+    }
+
+    public void appendLineWithCharsetAndQPDetection(final String propertyName,
+            final String rawValue) {
+        appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
+    }
+
+    private void appendLineWithCharsetAndQPDetection(
+            final String propertyName, final List<String> rawValueList) {
+        appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
+    }
+
+    public void appendLineWithCharsetAndQPDetection(final String propertyName,
+            final List<String> parameterList, final String rawValue) {
+        final boolean needCharset =
+                !VCardUtils.containsOnlyPrintableAscii(rawValue);
+        final boolean reallyUseQuotedPrintable =
+                (mShouldUseQuotedPrintable &&
+                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue));
+        appendLine(propertyName, parameterList,
+                rawValue, needCharset, reallyUseQuotedPrintable);
+    }
+
+    public void appendLineWithCharsetAndQPDetection(final String propertyName,
+            final List<String> parameterList, final List<String> rawValueList) {
+        boolean needCharset = false;
+        boolean reallyUseQuotedPrintable = false;
+        for (String rawValue : rawValueList) {
+            if (!needCharset && mShouldUseQuotedPrintable &&
+                    !VCardUtils.containsOnlyPrintableAscii(rawValue)) {
+                needCharset = true;
+            }
+            if (!reallyUseQuotedPrintable &&
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue)) {
+                reallyUseQuotedPrintable = true;
+            }
+            if (needCharset && reallyUseQuotedPrintable) {
+                break;
+            }
+        }
+
+        appendLine(propertyName, parameterList, rawValueList,
+                needCharset, reallyUseQuotedPrintable);
+    }
+
+    /**
+     * Appends one line with a given property name and value.  
+     */
+    public void appendLine(final String propertyName, final String rawValue) {
+        appendLine(propertyName, rawValue, false, false);
+    }
+
+    public void appendLine(final String propertyName, final List<String> rawValueList) {
+        appendLine(propertyName, rawValueList, false, false);
+    }
+
+    public void appendLine(final String propertyName,
+            final String rawValue, final boolean needCharset, boolean needQuotedPrintable) {
+        appendLine(propertyName, null, rawValue, needCharset, needQuotedPrintable);
+    }
+
+    public void appendLine(final String propertyName, final List<String> parameterList,
+            final String rawValue) {
+        appendLine(propertyName, parameterList, rawValue, false, false);
+    }
+
+    public void appendLine(final String propertyName, final List<String> parameterList,
+            final String rawValue, final boolean needCharset, boolean needQuotedPrintable) {
+        mBuilder.append(propertyName);
+        if (parameterList != null && parameterList.size() > 0) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            appendTypeParameters(parameterList);
+        }
+        if (needCharset) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(mVCardCharsetParameter);
+        }
+
+        final String encodedValue;
+        if (needQuotedPrintable) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(VCARD_PARAM_ENCODING_QP);
+            encodedValue = encodeQuotedPrintable(rawValue);
+        } 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.
+            encodedValue = escapeCharacters(rawValue);
+        }
+
+        mBuilder.append(VCARD_DATA_SEPARATOR);
+        mBuilder.append(encodedValue);
+        mBuilder.append(VCARD_END_OF_LINE);
+    }
+
+    public void appendLine(final String propertyName, final List<String> rawValueList,
+            final boolean needCharset, boolean needQuotedPrintable) {
+        appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
+    }
+
+    public void appendLine(final String propertyName, final List<String> parameterList,
+            final List<String> rawValueList, final boolean needCharset,
+            final boolean needQuotedPrintable) {
+        mBuilder.append(propertyName);
+        if (parameterList != null && parameterList.size() > 0) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            appendTypeParameters(parameterList);
+        }
+        if (needCharset) {
+            mBuilder.append(VCARD_PARAM_SEPARATOR);
+            mBuilder.append(mVCardCharsetParameter);
+        }
+
+        mBuilder.append(VCARD_DATA_SEPARATOR);
+        boolean first = true;
+        for (String rawValue : rawValueList) {
+            final String encodedValue;
+            if (needQuotedPrintable) {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+                mBuilder.append(VCARD_PARAM_ENCODING_QP);
+                encodedValue = encodeQuotedPrintable(rawValue);
+            } else {
+                // TODO: one line may be too huge, which may be invalid in vCard 3.0
+                //        (which says "When generating a content line, lines longer than
+                //        75 characters SHOULD be folded"), though several
+                //        (even well-known) applications do not care this.
+                encodedValue = escapeCharacters(rawValue);
+            }
+
+            if (first) {
+                first = false;
+            } else {
+                mBuilder.append(VCARD_ITEM_SEPARATOR);
+            }
+            mBuilder.append(encodedValue);
+        }
+        mBuilder.append(VCARD_END_OF_LINE);
+    }
+
+    /**
+     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+     */
+    private void appendTypeParameters(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.
+        boolean first = true;
+        for (final String typeValue : types) {
+            // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
+            //       we don't emit that kind of vCard 3.0 specific type since there should be
+            //       high probabilyty in which external importers cannot understand them.
+            //
+            // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
+            //      are quoted.)
+            if (!VCardUtils.isV21Word(typeValue)) {
+                continue;
+            }
+            if (first) {
+                first = false;
+            } else {
+                mBuilder.append(VCARD_PARAM_SEPARATOR);
+            }
+            appendTypeParameter(typeValue);
+        }
+    }
+
+    /**
+     * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+     */
+    private void appendTypeParameter(final String type) {
+        appendTypeParameter(mBuilder, type);
+    }
+
+    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 || mAppendTypeParamName) && !mIsDoCoMo) {
+            builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
+        }
+        builder.append(type);
+    }
+
+    /**
+     * 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 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.
+     *
+     * 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 shouldAppendCharsetParam(String...propertyValueList) {
+        if (!mShouldAppendCharsetParam) {
+            return false;
+        }
+        for (String propertyValue : propertyValueList) {
+            if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String encodeQuotedPrintable(final String str) {
+        if (TextUtils.isEmpty(str)) {
+            return "";
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        int index = 0;
+        int lineCount = 0;
+        byte[] strArray = null;
+
+        try {
+            strArray = str.getBytes(mCharsetString);
+        } catch (UnsupportedEncodingException e) {
+            Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+                    + "Try default charset");
+            strArray = str.getBytes();
+        }
+        while (index < strArray.length) {
+            builder.append(String.format("=%02X", strArray[index]));
+            index += 1;
+            lineCount += 3;
+
+            if (lineCount >= 67) {
+                // Specification requires CRLF must be inserted before the
+                // length of the line
+                // becomes more than 76.
+                // Assuming that the next character is a multi-byte character,
+                // it will become
+                // 6 bytes.
+                // 76 - 6 - 3 = 67
+                builder.append("=\r\n");
+                lineCount = 0;
+            }
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * 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.
+     *
+     * Note that Quoted-Printable string must not be input here.
+     */
+    @SuppressWarnings("fallthrough")
+    private String escapeCharacters(final String unescaped) {
+        if (TextUtils.isEmpty(unescaped)) {
+            return "";
+        }
+
+        final StringBuilder tmpBuilder = new StringBuilder();
+        final int length = unescaped.length();
+        for (int i = 0; i < length; i++) {
+            final char ch = unescaped.charAt(i);
+            switch (ch) {
+                case ';': {
+                    tmpBuilder.append('\\');
+                    tmpBuilder.append(';');
+                    break;
+                }
+                case '\r': {
+                    if (i + 1 < length) {
+                        char nextChar = unescaped.charAt(i);
+                        if (nextChar == '\n') {
+                            break;
+                        } else {
+                            // fall through
+                        }
+                    } else {
+                        // fall through
+                    }
+                }
+                case '\n': {
+                    // In vCard 2.1, there's no specification about this, while
+                    // vCard 3.0 explicitly requires this should be encoded to "\n".
+                    tmpBuilder.append("\\n");
+                    break;
+                }
+                case '\\': {
+                    if (mIsV30) {
+                        tmpBuilder.append("\\\\");
+                        break;
+                    } else {
+                        // fall through
+                    }
+                }
+                case '<':
+                case '>': {
+                    if (mIsDoCoMo) {
+                        tmpBuilder.append('\\');
+                        tmpBuilder.append(ch);
+                    } else {
+                        tmpBuilder.append(ch);
+                    }
+                    break;
+                }
+                case ',': {
+                    if (mIsV30) {
+                        tmpBuilder.append("\\,");
+                    } else {
+                        tmpBuilder.append(ch);
+                    }
+                    break;
+                }
+                default: {
+                    tmpBuilder.append(ch);
+                    break;
+                }
+            }
+        }
+        return tmpBuilder.toString();
+    }
+
+    @Override
+    public String toString() {
+        if (!mEndAppended) {
+            if (mIsDoCoMo) {
+                appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+                appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
+                appendLine(VCardConstants.PROPERTY_X_NO, "");
+                appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
+            }
+            appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
+            mEndAppended = true;
+        }
+        return mBuilder.toString();
+    }
 }
diff --git a/core/java/android/pim/vcard/VCardBuilderCollection.java b/core/java/android/pim/vcard/VCardBuilderCollection.java
deleted file mode 100644
index e3985b6..0000000
--- a/core/java/android/pim/vcard/VCardBuilderCollection.java
+++ /dev/null
@@ -1,99 +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.pim.vcard;
-
-import java.util.Collection;
-import java.util.List;
-
-public class VCardBuilderCollection implements VCardBuilder {
-
-    private final Collection<VCardBuilder> mVCardBuilderCollection;
-    
-    public VCardBuilderCollection(Collection<VCardBuilder> vBuilderCollection) {
-        mVCardBuilderCollection = vBuilderCollection; 
-    }
-    
-    public Collection<VCardBuilder> getVCardBuilderBaseCollection() {
-        return mVCardBuilderCollection;
-    }
-    
-    public void start() {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.start();
-        }
-    }
-    
-    public void end() {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.end();
-        }
-    }
-
-    public void startRecord(String type) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.startRecord(type);
-        }
-    }
-    
-    public void endRecord() {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.endRecord();
-        }
-    }
-
-    public void startProperty() {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.startProperty();
-        }
-    }
-
-    
-    public void endProperty() {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.endProperty();
-        }
-    }
-
-    public void propertyGroup(String group) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.propertyGroup(group);
-        }
-    }
-
-    public void propertyName(String name) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.propertyName(name);
-        }
-    }
-
-    public void propertyParamType(String type) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.propertyParamType(type);
-        }
-    }
-
-    public void propertyParamValue(String value) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.propertyParamValue(value);
-        }
-    }
-
-    public void propertyValues(List<String> values) {
-        for (VCardBuilder builder : mVCardBuilderCollection) {
-            builder.propertyValues(values);
-        }
-    }
-}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 7807595..033ef89 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -38,11 +38,10 @@
 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.telephony.PhoneNumberUtils;
-import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.CharsetUtils;
@@ -55,13 +54,12 @@
 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;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * <p>
@@ -74,19 +72,37 @@
  * 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 LOG_TAG = "VCardComposer";
 
-    private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+    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";
@@ -97,31 +113,61 @@
     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 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;
 
     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, VCardConstants.PROPERTY_X_AIM);
+        sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+        sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+        sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+        sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+        sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+        // Google talk is a special case.
     }
 
     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()}.
+     * The input OutputStream object is closed() on {@link #onTerminate()}.
      * Must not close the stream outside.
      * </p>
      */
@@ -213,109 +259,23 @@
         }
     }
 
-    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;
     private final ContentResolver mContentResolver;
 
-    // Convenient member variables about the restriction of the vCard format.
-    // Used for not calling the same methods returning same results.
-    private final boolean mIsV30;
-    private final boolean mIsJapaneseMobilePhone;
-    private final boolean mOnlyOneNoteFieldIsAvailable;
     private final boolean mIsDoCoMo;
-    private final boolean mUsesQuotedPrintable;
-    private final boolean mUsesAndroidProperty;
-    private final boolean mUsesDefactProperty;
-    private final boolean mUsesUtf8;
     private final boolean mUsesShiftJis;
-    private final boolean mUsesQPToPrimaryProperties;
-
     private Cursor mCursor;
     private int mIdColumn;
 
     private final String mCharsetString;
-    private final String mVCardAttributeCharset;
     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,
@@ -336,110 +296,83 @@
     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(final Context context, final int vcardType,
+            final 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);
-        mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
-        mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
         mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
-        mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
         mHandlerList = new ArrayList<OneEntryHandler>();
 
         if (mIsDoCoMo) {
-            mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-            // 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;
+            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;
         } 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;
         } else {
-            mCharsetString = "UTF-8";
-            mVCardAttributeCharset = "CHARSET=UTF-8";
+            mCharsetString = 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);
-
-        if (!TextUtils.isEmpty(phoneNumber)) {
-            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()}.
+     * Must be called 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());
@@ -458,13 +391,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;
@@ -513,8 +452,7 @@
         } catch (OutOfMemoryError error) {
             // Maybe some data (e.g. photo) is too big to have in memory. But it
             // should be rare.
-            Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: "
-                    + name);
+            Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: " + name);
             System.gc();
             // TODO: should tell users what happened?
             return true;
@@ -541,89 +479,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>>();
@@ -640,8 +495,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) {
@@ -669,38 +523,19 @@
             return "";
         }
 
-        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);
-        }
-
-        appendStructuredNames(builder, contentValuesListMap);
-        appendNickNames(builder, contentValuesListMap);
-        appendPhones(builder, contentValuesListMap);
-        appendEmails(builder, contentValuesListMap);
-        appendPostals(builder, contentValuesListMap);
-        appendIms(builder, contentValuesListMap);
-        appendWebsites(builder, contentValuesListMap);
-        appendBirthday(builder, contentValuesListMap);
-        appendOrganizations(builder, contentValuesListMap);
-        if (mNeedPhotoForVCard) {
-            appendPhotos(builder, contentValuesListMap);
-        }
-        appendNotes(builder, contentValuesListMap);
-        // TODO: GroupMembership
-
-        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, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
+        final VCardBuilder builder = new VCardBuilder(mVCardType);
+        builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+                .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+                .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+                .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+                .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+                .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+                .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+                .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+                .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+                .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+                .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+                .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
         return builder.toString();
     }
 
@@ -713,8 +548,7 @@
             try {
                 mCursor.close();
             } catch (SQLiteException e) {
-                Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
-                        + e.getMessage());
+                Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
             }
             mCursor = null;
         }
@@ -750,1302 +584,98 @@
         return mErrorReason;
     }
 
-    private void appendStructuredNames(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(StructuredName.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null && contentValuesList.size() > 0) {
-            appendStructuredNamesInternal(builder, contentValuesList);
-        } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-        } else if (mIsV30) {
-            // vCard 3.0 requires "N" and "FN" properties.
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+    /**
+     * This static function is to compose vCard for phone own number
+     */
+    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+            String phoneNumber, boolean vcardVer21) {
+        final int vcardType = (vcardVer21 ?
+                VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8 :
+                    VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8);
+        final VCardBuilder builder = new VCardBuilder(vcardType);
+        boolean needCharset = false;
+        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+            needCharset = true;
         }
-    }
+        builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false);
+        builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false);
 
-    private boolean containsNonEmptyName(ContentValues contentValues) {
-        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
-        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
-        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
-        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
-        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
-        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
-        return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
-                TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
-                TextUtils.isEmpty(suffix) && TextUtils.isEmpty(displayName));
-    }
-
-    private void appendStructuredNamesInternal(final StringBuilder builder,
-            final List<ContentValues> contentValuesList) {
-        // For safety, we'll emit just one value around StructuredName, as external importers
-        // 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) {
-                // We choose "super primary" ContentValues.
-                primaryContentValues = contentValues;
-                break;
-            } else if (primaryContentValues == null) {
-                // We choose the first "primary" ContentValues
-                // if "super primary" ContentValues does not exist.
-                Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
-                if (isPrimary != null && isPrimary > 0 &&
-                        containsNonEmptyName(contentValues)) {
-                    primaryContentValues = contentValues;
-                    // Do not break, since there may be ContentValues with "super primary"
-                    // afterword.
-                } else if (subprimaryContentValues == null &&
-                        containsNonEmptyName(contentValues)) {
-                    subprimaryContentValues = contentValues;
-                }
-            }
+        if (!TextUtils.isEmpty(phoneNumber)) {
+            String label = Integer.toString(phonetype);
+            builder.appendTelLine(phonetype, label, phoneNumber, false);
         }
 
-        if (primaryContentValues == null) {
-            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);
-
-        if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
-            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);
-                encodedMiddle = encodeQuotedPrintable(middleName);
-                encodedPrefix = encodeQuotedPrintable(prefix);
-                encodedSuffix = encodeQuotedPrintable(suffix);
-            } else {
-                encodedFamily = escapeCharacters(familyName);
-                encodedGiven = escapeCharacters(givenName);
-                encodedMiddle = escapeCharacters(middleName);
-                encodedPrefix = escapeCharacters(prefix);
-                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);
-            }
-            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);
-
-            // FN property
-            builder.append(VCARD_PROPERTY_FULL_NAME);
-            if (shouldAppendCharsetAttribute(encodedFullname)) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(mVCardAttributeCharset);
-            }
-            if (reallyUseQuotedPrintableToFullname) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
-            }
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodedFullname);
-            builder.append(VCARD_COL_SEPARATOR);
-        } else if (!TextUtils.isEmpty(displayName)) {
-            final boolean reallyUseQuotedPrintableToDisplayName =
-                (mUsesQPToPrimaryProperties &&
-                        !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);
-            }
-            if (reallyUseQuotedPrintableToDisplayName) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
-            }
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodedDisplayName);
-            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);
-        } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-        } else if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
-        }
-
-        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);
-            }
-
-            if (mIsV30) {
-                final String sortString = VCardUtils
-                        .constructNameFromElements(mVCardType,
-                                phoneticFamilyName,
-                                phoneticMiddleName,
-                                phoneticGivenName);
-                builder.append(VCARD_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);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedSortString);
-                builder.append(VCARD_COL_SEPARATOR);
-            } else {
-                // 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
-                //       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);
-
-                boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticFamilyName) &&
-                              VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticMiddleName) &&
-                              VCardUtils.containsOnlyNonCrLfPrintableAscii(
-                                    phoneticGivenName)));
-
-                final String encodedPhoneticFamilyName;
-                final String encodedPhoneticMiddleName;
-                final String encodedPhoneticGivenName;
-                if (reallyUseQuotedPrintable) {
-                    encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
-                    encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
-                    encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
-                } else {
-                    encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
-                    encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
-                    encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
-                }
-
-                if (shouldAppendCharsetAttribute(Arrays.asList(
-                        encodedPhoneticFamilyName, encodedPhoneticMiddleName,
-                        encodedPhoneticGivenName))) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(mVCardAttributeCharset);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedPhoneticFamilyName);
-                builder.append(VCARD_ITEM_SEPARATOR);
-                builder.append(encodedPhoneticGivenName);
-                builder.append(VCARD_ITEM_SEPARATOR);
-                builder.append(encodedPhoneticMiddleName);
-                builder.append(VCARD_ITEM_SEPARATOR);
-                builder.append(VCARD_ITEM_SEPARATOR);
-                builder.append(VCARD_COL_SEPARATOR);
-            }
-        } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_SOUND);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(Constants.ATTR_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);
-        }
-
-        if (mUsesDefactProperty) {
-            if (!TextUtils.isEmpty(phoneticGivenName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !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);
-                }
-                if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedPhoneticGivenName);
-                builder.append(VCARD_COL_SEPARATOR);
-            }
-            if (!TextUtils.isEmpty(phoneticMiddleName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !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);
-                }
-                if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedPhoneticMiddleName);
-                builder.append(VCARD_COL_SEPARATOR);
-            }
-            if (!TextUtils.isEmpty(phoneticFamilyName)) {
-                final boolean reallyUseQuotedPrintable =
-                    (mUsesQPToPrimaryProperties &&
-                            !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);
-                }
-                if (reallyUseQuotedPrintable) {
-                    builder.append(VCARD_ATTR_SEPARATOR);
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                }
-                builder.append(VCARD_DATA_SEPARATOR);
-                builder.append(encodedPhoneticFamilyName);
-                builder.append(VCARD_COL_SEPARATOR);
-            }
-        }
-    }
-
-    private void appendNickNames(final StringBuilder builder,
-            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;
-            }
-
-            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);
-            }
-        }
-    }
-
-    private void appendPhones(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(Phone.CONTENT_ITEM_TYPE);
-        boolean phoneLineExists = false;
-        if (contentValuesList != null) {
-            Set<String> phoneSet = new HashSet<String>();
-            for (ContentValues contentValues : contentValuesList) {
-                final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
-                final String label = contentValues.getAsString(Phone.LABEL);
-                String phoneNumber = contentValues.getAsString(Phone.NUMBER);
-                if (phoneNumber != null) {
-                    phoneNumber = phoneNumber.trim();
-                }
-                if (TextUtils.isEmpty(phoneNumber)) {
-                    continue;
-                }
-                int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
-
-                phoneLineExists = true;
-                if (type == Phone.TYPE_PAGER) {
-                    phoneLineExists = true;
-                    if (!phoneSet.contains(phoneNumber)) {
-                        phoneSet.add(phoneNumber);
-                        appendVCardTelephoneLine(builder, type, label, phoneNumber);
-                    }
-                } else {
-                    // The entry "may" have several phone numbers when the contact entry is
-                    // corrupted because of its original source.
-                    //
-                    // e.g. I encountered the entry like the following.
-                    // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
-                    // This kind of entry is not able to be inserted via Android devices, but
-                    // possible if the source of the data is already corrupted.
-                    List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
-                    if (phoneNumberList.isEmpty()) {
-                        continue;
-                    }
-                    phoneLineExists = true;
-                    for (String actualPhoneNumber : phoneNumberList) {
-                        if (!phoneSet.contains(actualPhoneNumber)) {
-                            final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
-                            SpannableStringBuilder tmpBuilder =
-                                new SpannableStringBuilder(actualPhoneNumber);
-                            PhoneNumberUtils.formatNumber(tmpBuilder, format);
-                            final String formattedPhoneNumber = tmpBuilder.toString();
-                            phoneSet.add(actualPhoneNumber);
-                            appendVCardTelephoneLine(builder, type, label, formattedPhoneNumber);
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!phoneLineExists && mIsDoCoMo) {
-            appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
-        }
-    }
-
-    private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
-        List<String> phoneList = new ArrayList<String>();
-
-        StringBuilder builder = new StringBuilder();
-        final int length = phoneNumber.length();
-        for (int i = 0; i < length; i++) {
-            final char ch = phoneNumber.charAt(i);
-            if (Character.isDigit(ch)) {
-                builder.append(ch);
-            } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
-                phoneList.add(builder.toString());
-                builder = new StringBuilder();
-            }
-        }
-        if (builder.length() > 0) {
-            phoneList.add(builder.toString());
-        }
-
-        return phoneList;
-    }
-
-    private void appendEmails(final StringBuilder builder,
-            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>();
-            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();
-                }
-                if (TextUtils.isEmpty(emailAddress)) {
-                    continue;
-                }
-                emailAddressExists = true;
-                if (!addressSet.contains(emailAddress)) {
-                    addressSet.add(emailAddress);
-                    appendVCardEmailLine(builder, type, label, emailAddress);
-                }
-            }
-        }
-
-        if (!emailAddressExists && mIsDoCoMo) {
-            appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
-        }
-    }
-
-    private void appendPostals(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(StructuredPostal.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            if (mIsDoCoMo) {
-                appendPostalsForDoCoMo(builder, contentValuesList);
-            } else {
-                appendPostalsForGeneric(builder, contentValuesList);
-            }
-        } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_ADR);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(Constants.ATTR_TYPE_HOME);
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
-        }
+        return builder.toString();
     }
 
     /**
-     * Tries to append just one line. If there's no appropriate address
-     * information, append an empty line.
+     * Format according to RFC 2445 DATETIME type.
+     * The format is: ("%Y%m%dT%H%M%SZ").
      */
-    private void appendPostalsForDoCoMo(final StringBuilder builder,
-            final List<ContentValues> contentValuesList) {
-        // TODO: from old, inefficient code. fix this.
-        if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
-                StructuredPostal.TYPE_HOME)) {
-            return;
-        }
-        if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
-                StructuredPostal.TYPE_WORK)) {
-            return;
-        }
-        if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
-                StructuredPostal.TYPE_OTHER)) {
-            return;
-        }
-        if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
-                StructuredPostal.TYPE_CUSTOM)) {
-            return;
-        }
-
-        Log.w(LOG_TAG,
-                "Should not come here. Must have at least one postal data.");
-    }
-
-    private boolean appendPostalsForDoCoMoInternal(final StringBuilder builder,
-            final List<ContentValues> contentValuesList, Integer preferedType) {
-        for (ContentValues contentValues : contentValuesList) {
-            final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
-            final String label = contentValues.getAsString(StructuredPostal.LABEL);
-            if (type == preferedType) {
-                appendVCardPostalLine(builder, type, label, contentValues);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    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);
-            }
-        }
-    }
-
-    private void appendIms(final StringBuilder builder,
-            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);
-                    }
-                    // TODO: add "X-GOOGLE TALK" case...
-                }
-            }
-        }
-    }
-
-    private void appendWebsites(final StringBuilder builder,
-            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);
-                }
-            }
-        }
-    }
-
-    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);
-            if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
-                return;
-            }
-            // 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();
-            }
-            if (!TextUtils.isEmpty(birthday)) {
-                appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
-            }
-        }
-    }
-
-    private void appendOrganizations(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(Organization.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                String company = contentValues
-                        .getAsString(Organization.COMPANY);
-                if (company != null) {
-                    company = company.trim();
-                }
-                String title = contentValues
-                        .getAsString(Organization.TITLE);
-                if (title != null) {
-                    title = title.trim();
-                }
-
-                if (!TextUtils.isEmpty(company)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
-                            !VCardUtils.containsOnlyPrintableAscii(company),
-                            (mUsesQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
-                }
-                if (!TextUtils.isEmpty(title)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
-                            !VCardUtils.containsOnlyPrintableAscii(title),
-                            (mUsesQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
-                }
-            }
-        }
-    }
-
-    private void appendPhotos(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList = contentValuesListMap
-                .get(Photo.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            for (ContentValues contentValues : contentValuesList) {
-                byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
-                if (data == null) {
-                    continue;
-                }
-                final String photoType;
-                // Use some heuristics for guessing the format of the image.
-                // TODO: there should be some general API for detecting the file format.
-                if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
-                        && data[2] == 'F') {
-                    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...
-                    photoType = "PNG";
-                } else if (data.length >= 2 && data[0] == (byte) 0xff
-                        && data[1] == (byte) 0xd8) {
-                    photoType = "JPEG";
-                } else {
-                    Log.d(LOG_TAG, "Unknown photo type. Ignore.");
-                    continue;
-                }
-                final String photoString = VCardUtils.encodeBase64(data);
-                if (photoString.length() > 0) {
-                    appendVCardPhotoLine(builder, photoString, photoType);
-                }
-            }
-        }
-    }
-
-    private void appendNotes(final StringBuilder builder,
-            final Map<String, List<ContentValues>> contentValuesListMap) {
-        final List<ContentValues> contentValuesList =
-            contentValuesListMap.get(Note.CONTENT_ITEM_TYPE);
-        if (contentValuesList != null) {
-            if (mOnlyOneNoteFieldIsAvailable) {
-                StringBuilder noteBuilder = new StringBuilder();
-                boolean first = true;
-                for (ContentValues contentValues : contentValuesList) {
-                    String note = contentValues.getAsString(Note.NOTE);
-                    if (note == null) {
-                        note = "";
-                    }
-                    if (note.length() > 0) {
-                        if (first) {
-                            first = false;
-                        } else {
-                            noteBuilder.append('\n');
-                        }
-                        noteBuilder.append(note);
-                    }
-                }
-                final String noteStr = noteBuilder.toString();
-                // This means we scan noteStr completely twice, which is redundant.
-                // But for now, we assume this is not so time-consuming..
-                final boolean shouldAppendCharsetInfo =
-                    !VCardUtils.containsOnlyPrintableAscii(noteStr);
-                final boolean reallyUseQuotedPrintable =
-                        (mUsesQuotedPrintable &&
-                            !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
-                        shouldAppendCharsetInfo, reallyUseQuotedPrintable);
-            } else {
-                for (ContentValues contentValues : contentValuesList) {
-                    final String noteStr = contentValues.getAsString(Note.NOTE);
-                    if (!TextUtils.isEmpty(noteStr)) {
-                        final boolean shouldAppendCharsetInfo =
-                                !VCardUtils.containsOnlyPrintableAscii(noteStr);
-                        final boolean reallyUseQuotedPrintable =
-                                (mUsesQuotedPrintable &&
-                                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                        appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
-                                shouldAppendCharsetInfo, reallyUseQuotedPrintable);
-                    }
-                }
-            }
-        }
+    private final String toRfc2455Format(final long millSecs) {
+        Time startDate = new Time();
+        startDate.set(millSecs);
+        String date = startDate.format2445();
+        return date + FLAG_TIMEZONE_UTC;
     }
 
     /**
-     * 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.
-     *
-     * Note that Quoted-Printable string must not be input here.
+     * 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.
      */
-    @SuppressWarnings("fallthrough")
-    private String escapeCharacters(final String unescaped) {
-        if (TextUtils.isEmpty(unescaped)) {
-            return "";
-        }
-
-        final StringBuilder tmpBuilder = new StringBuilder();
-        final int length = unescaped.length();
-        for (int i = 0; i < length; i++) {
-            char ch = unescaped.charAt(i);
-            switch (ch) {
-                case ';': {
-                    tmpBuilder.append('\\');
-                    tmpBuilder.append(';');
-                    break;
-                }
-                case '\r': {
-                    if (i + 1 < length) {
-                        char nextChar = unescaped.charAt(i);
-                        if (nextChar == '\n') {
-                            continue;
-                        } else {
-                            // fall through
-                        }
-                    } else {
-                        // fall through
-                    }
-                }
-                case '\n': {
-                    // In vCard 2.1, there's no specification about this, while
-                    // vCard 3.0 explicitly requires this should be encoded to "\n".
-                    tmpBuilder.append("\\n");
-                    break;
-                }
-                case '\\': {
-                    if (mIsV30) {
-                        tmpBuilder.append("\\\\");
-                        break;
-                    } else {
-                        // fall through
-                    }
-                }
-                case '<':
-                case '>': {
-                    if (mIsDoCoMo) {
-                        tmpBuilder.append('\\');
-                        tmpBuilder.append(ch);
-                    } else {
-                        tmpBuilder.append(ch);
-                    }
-                    break;
-                }
-                case ',': {
-                    if (mIsV30) {
-                        tmpBuilder.append("\\,");
-                    } else {
-                        tmpBuilder.append(ch);
-                    }
-                    break;
-                }
-                default: {
-                    tmpBuilder.append(ch);
-                    break;
-                }
-            }
-        }
-        return tmpBuilder.toString();
-    }
-
-    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);
-        if (mIsV30) {
-            tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
-        } else {
-            tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
-        }
-        tmpBuilder.append(VCARD_ATTR_SEPARATOR);
-        appendTypeAttribute(tmpBuilder, photoType);
-        tmpBuilder.append(VCARD_DATA_SEPARATOR);
-        tmpBuilder.append(encodedData);
-
-        final String tmpStr = tmpBuilder.toString();
-        tmpBuilder = new StringBuilder();
-        int lineCount = 0;
-        int length = tmpStr.length();
-        for (int i = 0; i < length; i++) {
-            tmpBuilder.append(tmpStr.charAt(i));
-            lineCount++;
-            if (lineCount > 72) {
-                tmpBuilder.append(VCARD_COL_SEPARATOR);
-                tmpBuilder.append(VCARD_WS);
-                lineCount = 0;
-            }
-        }
-        builder.append(tmpBuilder.toString());
-        builder.append(VCARD_COL_SEPARATOR);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    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);
-
-        // 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;
-        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;
-                }
-                if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
-                    actuallyUseQuotedPrintable = 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);
-                } else {
-                    dataArray[i] = escapeCharacters(data);
-                }
-            }
-        }
-
-        final int typeAsPrimitive;
-        if (typeAsObject == null) {
-            typeAsPrimitive = StructuredPostal.TYPE_OTHER;
-        } else {
-            typeAsPrimitive = typeAsObject;
-        }
-
-        String typeAsString = null;
-        switch (typeAsPrimitive) {
-            case StructuredPostal.TYPE_HOME: {
-                typeAsString = Constants.ATTR_TYPE_HOME;
+    private void tryAppendCallHistoryTimeStampField(final VCardBuilder 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 StructuredPostal.TYPE_WORK: {
-                typeAsString = Constants.ATTR_TYPE_WORK;
+            case Calls.OUTGOING_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
                 break;
             }
-            case StructuredPostal.TYPE_CUSTOM: {
-                if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
-                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                    // We're not sure whether the label is valid in the spec
-                    // ("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);
-                }
-                break;
-            }
-            case StructuredPostal.TYPE_OTHER: {
+            case Calls.MISSED_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
                 break;
             }
             default: {
-                Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
-                break;
+                Log.w(LOG_TAG, "Call log type not correct.");
+                return;
             }
         }
 
-        // Attribute(s).
-
-        {
-            boolean shouldAppendAttrSeparator = false;
-            if (typeAsString != null) {
-                appendTypeAttribute(builder, typeAsString);
-                shouldAppendAttrSeparator = 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 (actuallyUseQuotedPrintable) {
-                    if (shouldAppendAttrSeparator) {
-                        builder.append(VCARD_ATTR_SEPARATOR);
-                    }
-                    builder.append(VCARD_ATTR_ENCODING_QP);
-                    shouldAppendAttrSeparator = 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);
+        final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+        builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP,
+                Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong));
     }
 
-    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;
+    private String createOneCallLogEntryInternal() {
+        final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+        String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+        if (TextUtils.isEmpty(name)) {
+            name = mCursor.getString(NUMBER_COLUMN_INDEX);
         }
+        final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+        builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false);
+        builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false);
 
-        final String typeAsString;
-        switch (typeAsPrimitive) {
-            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;
-                } else if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
-                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                    typeAsString = "X-" + label;
-                } else {
-                    typeAsString = DEFAULT_EMAIL_TYPE;
-                }
-                break;
-            }
-            case Email.TYPE_HOME: {
-                typeAsString = Constants.ATTR_TYPE_HOME;
-                break;
-            }
-            case Email.TYPE_WORK: {
-                typeAsString = Constants.ATTR_TYPE_WORK;
-                break;
-            }
-            case Email.TYPE_OTHER: {
-                typeAsString = DEFAULT_EMAIL_TYPE;
-                break;
-            }
-            case Email.TYPE_MOBILE: {
-                typeAsString = Constants.ATTR_TYPE_CELL;
-                break;
-            }
-            default: {
-                Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
-                typeAsString = DEFAULT_EMAIL_TYPE;
-                break;
-            }
+        final String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+        final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+        String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+        if (TextUtils.isEmpty(label)) {
+            label = Integer.toString(type);
         }
-
-        builder.append(VCARD_ATTR_SEPARATOR);
-        appendTypeAttribute(builder, typeAsString);
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(data);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    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 int typeAsPrimitive;
-        if (typeAsObject == null) {
-            typeAsPrimitive = Phone.TYPE_OTHER;
-        } else {
-            typeAsPrimitive = typeAsObject;
-        }
-
-        switch (typeAsPrimitive) {
-        case Phone.TYPE_HOME:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
-            break;
-        case Phone.TYPE_WORK:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
-            break;
-        case Phone.TYPE_FAX_HOME:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
-            break;
-        case Phone.TYPE_FAX_WORK:
-            appendTypeAttributes(builder, Arrays.asList(
-                    Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
-            break;
-        case Phone.TYPE_MOBILE:
-            builder.append(Constants.ATTR_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);
-            } else {
-                appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
-            }
-            break;
-        case Phone.TYPE_OTHER:
-            appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
-            break;
-        case Phone.TYPE_CUSTOM:
-            if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
-                        && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
-                appendTypeAttribute(builder, "X-" + label);
-            } else {
-                // Just ignore the custom type.
-                appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
-            }
-            break;
-        default:
-            appendUncommonPhoneType(builder, typeAsPrimitive);
-            break;
-        }
-
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(encodedData);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    /**
-     * Appends phone type string which may not be available in some devices.
-     */
-    private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
-        if (mIsDoCoMo) {
-            // The previous implementation for DoCoMo had been conservative
-            // about miscellaneous types.
-            builder.append(Constants.ATTR_TYPE_VOICE);
-        } else {
-            String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
-            if (phoneAttribute != null) {
-                appendTypeAttribute(builder, phoneAttribute);
-            } else {
-                Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
-            }
-        }
-    }
-
-    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,
-            boolean needQuotedPrintable) {
-        builder.append(field);
-        if (needCharset) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(mVCardAttributeCharset);
-        }
-
-        final String encodedData;
-        if (needQuotedPrintable) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_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);
-        }
-
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(encodedData);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private void appendTypeAttributes(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.
-        boolean first = true;
-        for (String type : types) {
-            if (first) {
-                first = false;
-            } else {
-                builder.append(VCARD_ATTR_SEPARATOR);
-            }
-            appendTypeAttribute(builder, type);
-        }
-    }
-
-    private void appendTypeAttribute(final StringBuilder builder, final String type) {
-        // 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);
-        }
-        builder.append(type);
-    }
-
-    /**
-     * Returns true when the property line should contain charset attribute
-     * 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
-     * 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...
-     *
-     * 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) {
-        return (!VCardUtils.containsOnlyPrintableAscii(propertyValue) &&
-                        (!mIsV30 || !mUsesUtf8));
-    }
-
-    private boolean shouldAppendCharsetAttribute(final List<String> propertyValueList) {
-        boolean shouldAppendBasically = false;
-        for (String propertyValue : propertyValueList) {
-            if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
-                shouldAppendBasically = true;
-                break;
-            }
-        }
-        return shouldAppendBasically && (!mIsV30 || !mUsesUtf8);
-    }
-
-    private String encodeQuotedPrintable(String str) {
-        if (TextUtils.isEmpty(str)) {
-            return "";
-        }
-        {
-            // Replace "\n" and "\r" with "\r\n".
-            StringBuilder tmpBuilder = new StringBuilder();
-            int length = str.length();
-            for (int i = 0; i < length; i++) {
-                char ch = str.charAt(i);
-                if (ch == '\r') {
-                    if (i + 1 < length && str.charAt(i + 1) == '\n') {
-                        i++;
-                    }
-                    tmpBuilder.append("\r\n");
-                } else if (ch == '\n') {
-                    tmpBuilder.append("\r\n");
-                } else {
-                    tmpBuilder.append(ch);
-                }
-            }
-            str = tmpBuilder.toString();
-        }
-
-        final StringBuilder tmpBuilder = new StringBuilder();
-        int index = 0;
-        int lineCount = 0;
-        byte[] strArray = null;
-
-        try {
-            strArray = str.getBytes(mCharsetString);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
-                    + "Try default charset");
-            strArray = str.getBytes();
-        }
-        while (index < strArray.length) {
-            tmpBuilder.append(String.format("=%02X", strArray[index]));
-            index += 1;
-            lineCount += 3;
-
-            if (lineCount >= 67) {
-                // Specification requires CRLF must be inserted before the
-                // length of the line
-                // becomes more than 76.
-                // Assuming that the next character is a multi-byte character,
-                // it will become
-                // 6 bytes.
-                // 76 - 6 - 3 = 67
-                tmpBuilder.append("=\r\n");
-                lineCount = 0;
-            }
-        }
-
-        return tmpBuilder.toString();
+        builder.appendTelLine(type, label, number, false);
+        tryAppendCallHistoryTimeStampField(builder);
+        return builder.toString();
     }
 }
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..ecd089a 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,16 +15,19 @@
  */
 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 {
-    // TODO: may be better to make the instance of this available and stop using static methods and
-    //        one integer. 
+    private static final String LOG_TAG = "VCardConfig";
 
     /* package */ static final int LOG_LEVEL_NONE = 0;
     /* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
@@ -34,15 +37,18 @@
 
     /* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
 
+    /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
+    /* package */ static final int PARSE_TYPE_APPLE = 1;
+    /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;  // For Japanese mobile phones.
+    /* package */ static final int PARSE_TYPE_FOMA = 3;  // For Japanese FOMA mobile phones.
+    /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+
     // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
     // decode the unicode to the original charset. If not, this setting will cause some bug. 
     public static final String DEFAULT_CHARSET = "iso-8859-1";
     
-    // 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 ...
 
@@ -54,7 +60,8 @@
     // 0x10 is reserved for safety
     
     private static final int FLAG_CHARSET_UTF8 = 0;
-    private static final int FLAG_CHARSET_SHIFT_JIS = 0x20;
+    private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
+    private static final int FLAG_CHARSET_MASK = 0xF00;
 
     /**
      * The flag indicating the vCard composer will add some "X-" properties used only in Android
@@ -95,96 +102,196 @@
     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 related names 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 properties with Quoted-Printable encoding.
+     * </P>
+     * <P>
+     * Again, we should not use this flag at all for complying vCard 2.1 spec.
+     * </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_NAME_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 toward TYPE params
+     * every time possible. The default behavior does not emit it and is valid in the spec.
+     * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
+     * </P>
+     * <P>
+     * Detail:
+     * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
+     * </p>
+     * <P>
+     * e.g.<BR />
+     * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
+     * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
+     * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
+     * </P>
+     * <P>
+     * 2) had been the default of VCard exporter/importer in Android, but it is found that
+     * some external exporter is not able to parse the type format like 2) but only 3).
+     * </P>
+     * <P>
+     * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
+     * strings (which should be rare though), please use this flag.
+     * </P>
+     * <P>
+     * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
+     * </P>
+     */
+    public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+    //// The followings are VCard types available from importer/exporter. ////
+
+    /**
+     * <P>
+     * Generic vCard format with the vCard 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 for example.
+     * </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 fully 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";
     
     /**
-     * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+     * <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,111 +300,137 @@
     /* 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_NAME_PROPERTIES);
+
+    /* package */ 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";
+    /* package */ 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);
+    public static int getVCardTypeFromString(final String vcardTypeString) {
+        final String loweredKey = vcardTypeString.toLowerCase();
+        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;
         }
     }
 
-    public static boolean isV30(int vcardType) {
+    public static boolean isV30(final int vcardType) {
         return ((vcardType & FLAG_V30) != 0);  
     }
 
-    public static boolean usesQuotedPrintable(int vcardType) {
+    public static boolean shouldUseQuotedPrintable(final int vcardType) {
         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(final int vcardType) {
+        return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
     }
 
-    public static boolean usesUtf8(int vcardType) {
-        return ((vcardType & FLAG_CHARSET_UTF8) != 0);
+    public static boolean usesShiftJis(final int vcardType) {
+        return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
     }
 
-    public static boolean usesShiftJis(int vcardType) {
-        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) {
+    public static int getNameOrderType(final int vcardType) {
         return vcardType & NAME_ORDER_MASK;
     }
 
-    public static boolean usesAndroidSpecificProperty(int vcardType) {
+    public static boolean usesAndroidSpecificProperty(final int vcardType) {
         return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
     }
 
-    public static boolean usesDefactProperty(int vcardType) {
+    public static boolean usesDefactProperty(final int vcardType) {
         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 shouldRefrainQPToNameProperties(final int vcardType) {
+       return (!shouldUseQuotedPrintable(vcardType) ||
+               ((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
+    }
+
+    public static boolean appendTypeParamName(final 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(final int vcardType) {
+        // TODO: Some mask will be required so that this method wrongly interpret
+        //        Japanese"-like" vCard type.
+        //        e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
+        return sJapaneseMobileTypeSet.contains(vcardType);
+    }
+
+    public static boolean needsToConvertPhoneticString(final int vcardType) {
+        return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+    }
+
+    public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
+        return vcardType == VCARD_TYPE_DOCOMO;
+    }
+
+    public static boolean isDoCoMo(final int vcardType) {
+        return ((vcardType & FLAG_DOCOMO) != 0);
     }
 
     private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
new file mode 100644
index 0000000..8c07126
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -0,0 +1,152 @@
+/*
+ * 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.pim.vcard;
+
+/**
+ * Constants used in both exporter and importer code.
+ */
+public class VCardConstants {
+    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";
+    
+    // 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";
+    public static final String PROPERTY_X_YAHOO = "X-YAHOO";
+    public static final String PROPERTY_X_ICQ = "X-ICQ";
+    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";
+
+    // 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";
+
+    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";
+
+    // 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 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.
+    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";
+
+    // 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";
+
+    // 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 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";
+    }
+
+    /* package */ static final int MAX_DATA_COLUMN = 15;
+
+    /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
+    static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
+
+    private VCardConstants() {
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
new file mode 100644
index 0000000..20eee84
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -0,0 +1,1411 @@
+/*
+ * 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.pim.vcard;
+
+import android.accounts.Account;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Groups;
+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.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class bridges between data structure of Contact app and VCard data.
+ */
+public class VCardEntry {
+    private static final String LOG_TAG = "VCardEntry";
+
+    private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+    private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
+    private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
+
+    private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
+
+    static {
+        sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+        sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+        sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+        sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+        sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+        sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+        sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+        sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+                Im.PROTOCOL_GOOGLE_TALK);
+    }
+
+    static public class PhoneData {
+        public final int type;
+        public final String data;
+        public final String label;
+        // isPrimary is changable only when there's no appropriate one existing in
+        // the original VCard.
+        public boolean isPrimary;
+        public PhoneData(int type, String data, String label, boolean isPrimary) {
+            this.type = type;
+            this.data = data;
+            this.label = label;
+            this.isPrimary = isPrimary;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PhoneData)) {
+                return false;
+            }
+            PhoneData phoneData = (PhoneData)obj;
+            return (type == phoneData.type && data.equals(phoneData.data) &&
+                    label.equals(phoneData.label) && isPrimary == phoneData.isPrimary);
+        }
+        
+        @Override
+        public String toString() {
+            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+                    type, data, label, isPrimary);
+        }
+    }
+
+    static public class EmailData {
+        public final int type;
+        public final String data;
+        // Used only when TYPE is TYPE_CUSTOM.
+        public final String label;
+        // isPrimary is changable only when there's no appropriate one existing in
+        // the original VCard.
+        public boolean isPrimary;
+        public EmailData(int type, String data, String label, boolean isPrimary) {
+            this.type = type;
+            this.data = data;
+            this.label = label;
+            this.isPrimary = isPrimary;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof EmailData)) {
+                return false;
+            }
+            EmailData emailData = (EmailData)obj;
+            return (type == emailData.type && data.equals(emailData.data) &&
+                    label.equals(emailData.label) && isPrimary == emailData.isPrimary);
+        }
+        
+        @Override
+        public String toString() {
+            return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+                    type, data, label, isPrimary);
+        }
+    }
+
+    static public class PostalData {
+        // Determined by vCard spec.
+        // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+        public static final int ADDR_MAX_DATA_SIZE = 7;
+        private final String[] dataArray;
+        public final String pobox;
+        public final String extendedAddress;
+        public final String street;
+        public final String localty;
+        public final String region;
+        public final String postalCode;
+        public final String country;
+        public final int type;
+        public final String label;
+        public boolean isPrimary;
+
+        public PostalData(final int type, final List<String> propValueList,
+                final String label, boolean isPrimary) {
+            this.type = type;
+            dataArray = new String[ADDR_MAX_DATA_SIZE];
+
+            int size = propValueList.size();
+            if (size > ADDR_MAX_DATA_SIZE) {
+                size = ADDR_MAX_DATA_SIZE;
+            }
+
+            // adr-value = 0*6(text-value ";") text-value
+            //           ; PO Box, Extended Address, Street, Locality, Region, Postal
+            //           ; Code, Country Name
+            //
+            // Use Iterator assuming List may be LinkedList, though actually it is
+            // always ArrayList in the current implementation.
+            int i = 0;
+            for (String addressElement : propValueList) {
+                dataArray[i] = addressElement;
+                if (++i >= size) {
+                    break;
+                }
+            }
+            while (i < ADDR_MAX_DATA_SIZE) {
+                dataArray[i++] = null;
+            }
+
+            this.pobox = dataArray[0];
+            this.extendedAddress = dataArray[1];
+            this.street = dataArray[2];
+            this.localty = dataArray[3];
+            this.region = dataArray[4];
+            this.postalCode = dataArray[5];
+            this.country = dataArray[6];
+            this.label = label;
+            this.isPrimary = isPrimary;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PostalData)) {
+                return false;
+            }
+            final PostalData postalData = (PostalData)obj;
+            return (Arrays.equals(dataArray, postalData.dataArray) && 
+                    (type == postalData.type &&
+                            (type == StructuredPostal.TYPE_CUSTOM ?
+                                    (label == postalData.label) : true)) &&
+                    (isPrimary == postalData.isPrimary));
+        }
+
+        public String getFormattedAddress(final int vcardType) {
+            StringBuilder builder = new StringBuilder();
+            boolean empty = true;
+            if (VCardConfig.isJapaneseDevice(vcardType)) {
+                // In Japan, the order is reversed.
+                for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) {
+                    String addressPart = dataArray[i];
+                    if (!TextUtils.isEmpty(addressPart)) {
+                        if (!empty) {
+                            builder.append(' ');
+                        } else {
+                            empty = false;
+                        }
+                        builder.append(addressPart);
+                    }
+                }
+            } else {
+                for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
+                    String addressPart = dataArray[i];
+                    if (!TextUtils.isEmpty(addressPart)) {
+                        if (!empty) {
+                            builder.append(' ');
+                        } else {
+                            empty = false;
+                        }
+                        builder.append(addressPart);
+                    }
+                }
+            }
+
+            return builder.toString().trim();
+        }
+        
+        @Override
+        public String toString() {
+            return String.format("type: %d, label: %s, isPrimary: %s",
+                    type, label, isPrimary);
+        }
+    }
+
+    static public class OrganizationData {
+        public final int type;
+        // non-final is Intentional: 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;
+        public boolean isPrimary;
+
+        public OrganizationData(int type,
+                String companyName,
+                String departmentName,
+                String titleName,
+                boolean isPrimary) {
+            this.type = type;
+            this.companyName = companyName;
+            this.departmentName = departmentName;
+            this.titleName = titleName;
+            this.isPrimary = isPrimary;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof OrganizationData)) {
+                return false;
+            }
+            OrganizationData organization = (OrganizationData)obj;
+            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, 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 boolean isPrimary;
+
+        public ImData(final int protocol, final String customProtocol, final int type,
+                final String data, final boolean isPrimary) {
+            this.protocol = protocol;
+            this.customProtocol = customProtocol;
+            this.type = type;
+            this.data = data;
+            this.isPrimary = isPrimary;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ImData)) {
+                return false;
+            }
+            ImData imData = (ImData)obj;
+            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, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+                    type, protocol, customProtocol, data, isPrimary);
+        }
+    }
+    
+    public static 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, 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);
+        }
+    }
+    
+    /* package */ static class Property {
+        private String mPropertyName;
+        private Map<String, Collection<String>> mParameterMap =
+            new HashMap<String, Collection<String>>();
+        private List<String> mPropertyValueList = new ArrayList<String>();
+        private byte[] mPropertyBytes;
+
+        public void setPropertyName(final String propertyName) {
+            mPropertyName = propertyName;
+        }
+        
+        public void addParameter(final String paramName, final String paramValue) {
+            Collection<String> values;
+            if (!mParameterMap.containsKey(paramName)) {
+                if (paramName.equals("TYPE")) {
+                    values = new HashSet<String>();
+                } else {
+                    values = new ArrayList<String>();
+                }
+                mParameterMap.put(paramName, values);
+            } else {
+                values = mParameterMap.get(paramName);
+            }
+            values.add(paramValue);
+        }
+        
+        public void addToPropertyValueList(final String propertyValue) {
+            mPropertyValueList.add(propertyValue);
+        }
+        
+        public void setPropertyBytes(final byte[] propertyBytes) {
+            mPropertyBytes = propertyBytes;
+        }
+
+        public final Collection<String> getParameters(String type) {
+            return mParameterMap.get(type);
+        }
+        
+        public final List<String> getPropertyValueList() {
+            return mPropertyValueList;
+        }
+        
+        public void clear() {
+            mPropertyName = null;
+            mParameterMap.clear();
+            mPropertyValueList.clear();
+            mPropertyBytes = null;
+        }
+    }
+    
+    private String mFamilyName;
+    private String mGivenName;
+    private String mMiddleName;
+    private String mPrefix;
+    private String mSuffix;
+
+    // Used only when no family nor given name is found.
+    private String mFullName;
+    
+    private String mPhoneticFamilyName;
+    private String mPhoneticGivenName;
+    private String mPhoneticMiddleName;
+    
+    private String mPhoneticFullName;
+
+    private List<String> mNickNameList;
+
+    private String mDisplayName; 
+
+    private String mBirthday;
+    
+    private List<String> mNoteList;
+    private List<PhoneData> mPhoneList;
+    private List<EmailData> mEmailList;
+    private List<PostalData> mPostalList;
+    private List<OrganizationData> mOrganizationList;
+    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;
+
+    public VCardEntry() {
+        this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+    }
+
+    public VCardEntry(int vcardType) {
+        this(vcardType, null);
+    }
+
+    public VCardEntry(int vcardType, Account account) {
+        mVCardType = vcardType;
+        mAccount = account;
+    }
+
+    private void addPhone(int type, String data, String label, boolean isPrimary) {
+        if (mPhoneList == null) {
+            mPhoneList = new ArrayList<PhoneData>();
+        }
+        final StringBuilder builder = new StringBuilder();
+        final String trimed = data.trim();
+        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, formattedNumber, label, isPrimary);
+        mPhoneList.add(phoneData);
+    }
+
+    private void addNickName(final String nickName) {
+        if (mNickNameList == null) {
+            mNickNameList = new ArrayList<String>();
+        }
+        mNickNameList.add(nickName);
+    }
+    
+    private void addEmail(int type, String data, String label, boolean isPrimary){
+        if (mEmailList == null) {
+            mEmailList = new ArrayList<EmailData>();
+        }
+        mEmailList.add(new EmailData(type, data, label, isPrimary));
+    }
+    
+    private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
+        if (mPostalList == null) {
+            mPostalList = new ArrayList<PostalData>(0);
+        }
+        mPostalList.add(new PostalData(type, propValueList, label, 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,
+                departmentName, titleName, isPrimary));
+    }
+
+    private static final List<String> sEmptyList =
+            Collections.unmodifiableList(new ArrayList<String>(0));
+
+    /**
+     * Set "ORG" related values to the appropriate data. If there's more than one
+     * {@link 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
+     * {@link OrganizationData} object, a new {@link 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);
+    }
+
+    /**
+     * 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(protocol, customProtocol, type, propValue, isPrimary));
+    }
+    
+    private void addNote(final String note) {
+        if (mNoteList == null) {
+            mNoteList = new ArrayList<String>(1);
+        }
+        mNoteList.add(note);
+    }
+    
+    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, isPrimary);
+        mPhotoList.add(photoData);
+    }
+
+    @SuppressWarnings("fallthrough")
+    private void handleNProperty(List<String> elems) {
+        // Family, Given, Middle, Prefix, Suffix. (1 - 5)
+        int size;
+        if (elems == null || (size = elems.size()) < 1) {
+            return;
+        }
+        if (size > 5) {
+            size = 5;
+        }
+
+        switch (size) {
+            // fallthrough
+            case 5: mSuffix = elems.get(4);
+            case 4: mPrefix = elems.get(3);
+            case 3: mMiddleName = elems.get(2);
+            case 2: mGivenName = elems.get(1);
+            default: mFamilyName = elems.get(0);
+        }
+    }
+
+    /**
+     * Note: Some Japanese mobile phones use this field for phonetic name,
+     *       since vCard 2.1 does not have "SORT-STRING" type.
+     *       Also, in some cases, the field has some ';'s in it.
+     *       Assume the ';' means the same meaning in N property
+     */
+    @SuppressWarnings("fallthrough")
+    private void handlePhoneticNameFromSound(List<String> elems) {
+        if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+                TextUtils.isEmpty(mPhoneticMiddleName) &&
+                TextUtils.isEmpty(mPhoneticGivenName))) {
+            // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
+            // Ignore "SOUND;X-IRMC-N".
+            return;
+        }
+
+        int size;
+        if (elems == null || (size = elems.size()) < 1) {
+            return;
+        }
+
+        // Assume that the order is "Family, Given, Middle".
+        // This is not from specification but mere assumption. Some Japanese phones use this order.
+        if (size > 3) {
+            size = 3;
+        }
+
+        if (elems.get(0).length() > 0) {
+            boolean onlyFirstElemIsNonEmpty = true;
+            for (int i = 1; i < size; i++) {
+                if (elems.get(i).length() > 0) {
+                    onlyFirstElemIsNonEmpty = false;
+                    break;
+                }
+            }
+            if (onlyFirstElemIsNonEmpty) {
+                final String[] namesArray = elems.get(0).split(" ");
+                final int nameArrayLength = namesArray.length;
+                if (nameArrayLength == 3) {
+                    // Assume the string is "Family Middle Given".
+                    mPhoneticFamilyName = namesArray[0];
+                    mPhoneticMiddleName = namesArray[1];
+                    mPhoneticGivenName = namesArray[2];
+                } else if (nameArrayLength == 2) {
+                    // Assume the string is "Family Given" based on the Japanese mobile
+                    // phones' preference.
+                    mPhoneticFamilyName = namesArray[0];
+                    mPhoneticGivenName = namesArray[1];
+                } else {
+                    mPhoneticFullName = elems.get(0);
+                }
+                return;
+            }
+        }
+
+        switch (size) {
+            // fallthrough
+            case 3: mPhoneticMiddleName = elems.get(2);
+            case 2: mPhoneticGivenName = elems.get(1);
+            default: mPhoneticFamilyName = elems.get(0);
+        }
+    }
+
+    public void addProperty(final Property property) {
+        final String propName = property.mPropertyName;
+        final Map<String, Collection<String>> paramMap = property.mParameterMap;
+        final List<String> propValueList = property.mPropertyValueList;
+        byte[] propBytes = property.mPropertyBytes;
+        
+        if (propValueList.size() == 0) {
+            return;
+        }
+        final String propValue = listToString(propValueList).trim();
+        
+        if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
+            // vCard version. Ignore this.
+        } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
+            mFullName = propValue;
+        } else if (propName.equals(VCardConstants.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(VCardConstants.PROPERTY_N)) {
+            handleNProperty(propValueList);
+        } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
+            mPhoneticFullName = propValue;
+        } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
+                propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
+            addNickName(propValue);
+        } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
+            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            if (typeCollection != null
+                    && typeCollection.contains(VCardConstants.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(VCardConstants.PROPERTY_ADR)) {
+            boolean valuesAreAllEmpty = true;
+            for (String value : propValueList) {
+                if (value.length() > 0) {
+                    valuesAreAllEmpty = false;
+                    break;
+                }
+            }
+            if (valuesAreAllEmpty) {
+                return;
+            }
+
+            int type = -1;
+            String label = "";
+            boolean isPrimary = false;
+            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            if (typeCollection != null) {
+                for (String typeString : typeCollection) {
+                    typeString = typeString.toUpperCase();
+                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+                        isPrimary = true;
+                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
+                        type = StructuredPostal.TYPE_HOME;
+                        label = "";
+                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) || 
+                            typeString.equalsIgnoreCase(VCardConstants.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(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
+                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
+                            typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
+                        // We do not have any appropriate way to store this information.
+                    } else {
+                        if (typeString.startsWith("X-") && type < 0) {
+                            typeString = typeString.substring(2);
+                        }
+                        // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
+                        // emit non-standard types. We do not handle their values now.
+                        type = StructuredPostal.TYPE_CUSTOM;
+                        label = typeString;
+                    }
+                }
+            }
+            // We use "HOME" as default
+            if (type < 0) {
+                type = StructuredPostal.TYPE_HOME;
+            }
+
+            addPostal(type, propValueList, label, isPrimary);
+        } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
+            int type = -1;
+            String label = null;
+            boolean isPrimary = false;
+            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            if (typeCollection != null) {
+                for (String typeString : typeCollection) {
+                    typeString = typeString.toUpperCase();
+                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+                        isPrimary = true;
+                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
+                        type = Email.TYPE_HOME;
+                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
+                        type = Email.TYPE_WORK;
+                    } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
+                        type = Email.TYPE_MOBILE;
+                    } else {
+                        if (typeString.startsWith("X-") && type < 0) {
+                            typeString = typeString.substring(2);
+                        }
+                        // vCard 3.0 allows iana-token.
+                        // We may have INTERNET (specified in vCard spec),
+                        // SCHOOL, etc.
+                        type = Email.TYPE_CUSTOM;
+                        label = typeString;
+                    }
+                }
+            }
+            if (type < 0) {
+                type = Email.TYPE_OTHER;
+            }
+            addEmail(type, propValue, label, isPrimary);
+        } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
+            // vCard specification does not specify other types.
+            final int type = Organization.TYPE_WORK;
+            boolean isPrimary = false;
+            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            if (typeCollection != null) {
+                for (String typeString : typeCollection) {
+                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+                        isPrimary = true;
+                    }
+                }
+            }
+            handleOrgValue(type, propValueList, isPrimary);
+        } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
+            handleTitleValue(propValue);
+        } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
+            // This conflicts with TITLE. Ignore for now...
+            // handleTitleValue(propValue);
+        } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
+                propName.equals(VCardConstants.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 {
+                final Collection<String> typeCollection = paramMap.get("TYPE");
+                String formatName = null;
+                boolean isPrimary = false;
+                if (typeCollection != null) {
+                    for (String typeValue : typeCollection) {
+                        if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
+                            isPrimary = true;
+                        } else if (formatName == null){
+                            formatName = typeValue;
+                        }
+                    }
+                }
+                addPhotoBytes(formatName, propBytes, isPrimary);
+            }
+        } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            final Object typeObject =
+                VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
+            final int type;
+            final String label;
+            if (typeObject instanceof Integer) {
+                type = (Integer)typeObject;
+                label = null;
+            } else {
+                type = Phone.TYPE_CUSTOM;
+                label = typeObject.toString();
+            }
+            
+            final boolean isPrimary;
+            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
+                isPrimary = true;
+            } else {
+                isPrimary = false;
+            }
+            addPhone(type, propValue, label, isPrimary);
+        } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+            // The phone number available via Skype.
+            Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            final int type = Phone.TYPE_OTHER;
+            final boolean isPrimary;
+            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
+                isPrimary = true;
+            } else {
+                isPrimary = false;
+            }
+            addPhone(type, propValue, null, isPrimary);
+        } else if (sImMap.containsKey(propName)) {
+            final int protocol = sImMap.get(propName);
+            boolean isPrimary = false;
+            int type = -1;
+            final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+            if (typeCollection != null) {
+                for (String typeString : typeCollection) {
+                    if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
+                        isPrimary = true;
+                    } else if (type < 0) {
+                        if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
+                            type = Im.TYPE_HOME;
+                        } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
+                            type = Im.TYPE_WORK;
+                        }
+                    }
+                }
+            }
+            if (type < 0) {
+                type = Phone.TYPE_HOME;
+            }
+            addIm(protocol, null, type, propValue, isPrimary);
+        } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
+            addNote(propValue);
+        } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
+            if (mWebsiteList == null) {
+                mWebsiteList = new ArrayList<String>(1);
+            }
+            mWebsiteList.add(propValue);
+        } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
+            mBirthday = propValue;
+        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
+            mPhoneticGivenName = propValue;
+        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
+            mPhoneticMiddleName = propValue;
+        } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
+            mPhoneticFamilyName = propValue;
+        } else if (propName.equals(VCardConstants.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")) {
+        } else if (propName.equals("KEY")) {
+            // Type is X509 or PGP? I don't know how to handle this...
+        } else if (propName.equals("MAILER")) {
+        } else if (propName.equals("TZ")) {
+        } else if (propName.equals("GEO")) {
+        } else if (propName.equals("CLASS")) {
+            // vCard 3.0 only.
+            // e.g. CLASS:CONFIDENTIAL
+        } else if (propName.equals("PROFILE")) {
+            // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
+        } else if (propName.equals("CATEGORIES")) {
+            // VCard 3.0 only.
+            // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
+        } else if (propName.equals("SOURCE")) {
+            // VCard 3.0 only.
+        } else if (propName.equals("PRODID")) {
+            // VCard 3.0 only.
+            // To specify the identifier for the product that created
+            // the vCard object.*/
+        } else {
+            // Unknown X- words and IANA token.
+        }
+    }
+
+    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() {
+        // FullName (created via "FN" or "NAME" field) is prefered.
+        if (!TextUtils.isEmpty(mFullName)) {
+            mDisplayName = mFullName;
+        } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+                    mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
+        } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+                TextUtils.isEmpty(mPhoneticGivenName))) {
+            mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+                    mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
+        } else if (mEmailList != null && mEmailList.size() > 0) {
+            mDisplayName = mEmailList.get(0).data;
+        } else if (mPhoneList != null && mPhoneList.size() > 0) {
+            mDisplayName = mPhoneList.get(0).data;
+        } else if (mPostalList != null && mPostalList.size() > 0) {
+            mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
+        }
+
+        if (mDisplayName == null) {
+            mDisplayName = "";
+        }
+    }
+
+    /**
+     * Consolidate several fielsds (like mName) using name candidates, 
+     */
+    public void consolidateFields() {
+        constructDisplayName();
+
+        if (mPhoneticFullName != null) {
+            mPhoneticFullName = mPhoneticFullName.trim();
+        }
+    }
+
+    public void pushIntoContentResolver(ContentResolver resolver) {
+        ArrayList<ContentProviderOperation> operationList =
+            new ArrayList<ContentProviderOperation>();  
+        ContentProviderOperation.Builder builder =
+            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
+        String myGroupsId = null;
+        if (mAccount != null) {
+            builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
+            builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
+
+            // Assume that caller side creates this group if it does not exist.
+            if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
+                final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
+                        Groups.SOURCE_ID },
+                        Groups.TITLE + "=?", new String[] {
+                        GOOGLE_MY_CONTACTS_GROUP }, null);
+                try {
+                    if (cursor != null && cursor.moveToFirst()) {
+                        myGroupsId = cursor.getString(0);
+                    }
+                } finally {
+                    if (cursor != null) {
+                        cursor.close();
+                    }
+                }
+            }
+        } else {
+            builder.withValue(RawContacts.ACCOUNT_NAME, null);
+            builder.withValue(RawContacts.ACCOUNT_TYPE, null);
+        }
+        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);
+
+            builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
+            builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
+            builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
+            builder.withValue(StructuredName.PREFIX, mPrefix);
+            builder.withValue(StructuredName.SUFFIX, mSuffix);
+
+            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) {
+            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);
+                operationList.add(builder.build());
+            }
+        }
+
+        if (mPhoneList != null) {
+            for (PhoneData phoneData : mPhoneList) {
+                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+                builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+
+                builder.withValue(Phone.TYPE, phoneData.type);
+                if (phoneData.type == Phone.TYPE_CUSTOM) {
+                    builder.withValue(Phone.LABEL, phoneData.label);
+                }
+                builder.withValue(Phone.NUMBER, phoneData.data);
+                if (phoneData.isPrimary) {
+                    builder.withValue(Phone.IS_PRIMARY, 1);
+                }
+                operationList.add(builder.build());
+            }
+        }
+
+        if (mOrganizationList != null) {
+            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);
+                builder.withValue(Organization.TYPE, organizationData.type);
+                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);
+                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+
+                builder.withValue(Email.TYPE, emailData.type);
+                if (emailData.type == Email.TYPE_CUSTOM) {
+                    builder.withValue(Email.LABEL, emailData.label);
+                }
+                builder.withValue(Email.DATA, emailData.data);
+                if (emailData.isPrimary) {
+                    builder.withValue(Data.IS_PRIMARY, 1);
+                }
+                operationList.add(builder.build());
+            }
+        }
+        
+        if (mPostalList != null) {
+            for (PostalData postalData : mPostalList) {
+                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                VCardUtils.insertStructuredPostalDataUsingContactsStruct(
+                        mVCardType, builder, postalData);
+                operationList.add(builder.build());
+            }
+        }
+        
+        if (mImList != null) {
+            for (ImData imData : mImList) {
+                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);
+                builder.withValue(Im.PROTOCOL, imData.protocol);
+                if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+                    builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
+                }
+                if (imData.isPrimary) {
+                    builder.withValue(Data.IS_PRIMARY, 1);
+                }
+            }
+        }
+        
+        if (mNoteList != null) {
+            for (String note : mNoteList) {
+                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) {
+            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 (photoData.isPrimary) {
+                    builder.withValue(Photo.IS_PRIMARY, 1);
+                }
+                operationList.add(builder.build());
+            }
+        }
+
+        if (mWebsiteList != null) {
+            for (String website : mWebsiteList) {
+                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
+                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_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);
+            builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
+            builder.withValue(Event.START_DATE, mBirthday);
+            builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
+            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 > VCardConstants.MAX_DATA_COLUMN + 1) {
+                    size = VCardConstants.MAX_DATA_COLUMN + 1;
+                    customPropertyList =
+                        customPropertyList.subList(0, VCardConstants.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);
+            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
+            operationList.add(builder.build());
+        }
+
+        try {
+            resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        } catch (OperationApplicationException e) {
+            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        }
+    }
+
+    public static VCardEntry buildFromResolver(ContentResolver resolver) {
+        return buildFromResolver(resolver, Contacts.CONTENT_URI);
+    }
+
+    public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
+
+        return null;
+    }
+
+    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;
+    }
+    
+    private String listToString(List<String> list){
+        final int size = list.size();
+        if (size > 1) {
+            StringBuilder builder = new StringBuilder();
+            int i = 0;
+            for (String type : list) {
+                builder.append(type);
+                if (i < size - 1) {
+                    builder.append(";");
+                }
+            }
+            return builder.toString();
+        } else if (size == 1) {
+            return list.get(0);
+        } else {
+            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/EntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
similarity index 62%
rename from core/java/android/pim/vcard/EntryCommitter.java
rename to core/java/android/pim/vcard/VCardEntryCommitter.java
index 3f1655d..ebbbab6 100644
--- a/core/java/android/pim/vcard/EntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -19,28 +19,36 @@
 import android.util.Log;
 
 /**
- * EntryHandler implementation which commits the entry to Contacts Provider 
+ * <P>
+ * {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
+ * </P>
+ * <P>
+ * Note:<BR />
+ * Each vCard may contain big photo images encoded by BASE64,
+ * If we store all vCard entries in memory, OutOfMemoryError may be thrown.
+ * Thus, this class push each VCard entry into ContentResolver immediately.
+ * </P>
  */
-public class EntryCommitter implements EntryHandler {
-    public static String LOG_TAG = "vcard.EntryComitter";
+public class VCardEntryCommitter implements VCardEntryHandler {
+    public static String LOG_TAG = "VCardEntryComitter";
 
-    private ContentResolver mContentResolver;
+    private final ContentResolver mContentResolver;
     private long mTimeToCommit;
-    
-    public EntryCommitter(ContentResolver resolver) {
+
+    public VCardEntryCommitter(ContentResolver resolver) {
         mContentResolver = resolver;
     }
 
-    public void onParsingStart() {
+    public void onStart() {
     }
-    
-    public void onParsingEnd() {
+
+    public void onEnd() {
         if (VCardConfig.showPerformanceLog()) {
             Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
         }
     }
 
-    public void onEntryCreated(final ContactStruct contactStruct) {
+    public void onEntryCreated(final VCardEntry contactStruct) {
         long start = System.currentTimeMillis();
         contactStruct.pushIntoContentResolver(mContentResolver);
         mTimeToCommit += System.currentTimeMillis() - start;
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
similarity index 69%
rename from core/java/android/pim/vcard/VCardDataBuilder.java
rename to core/java/android/pim/vcard/VCardEntryConstructor.java
index d2026d0..290ca2b 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -30,123 +30,105 @@
 import java.util.Collection;
 import java.util.List;
 
-/**
- * VBuilder for VCard. VCard may contain big photo images encoded by BASE64,
- * If we store all VNode entries in memory like VDataBuilder.java,
- * OutOfMemoryError may be thrown. Thus, this class push each VCard entry into
- * ContentResolver immediately.
- */
-public class VCardDataBuilder implements VCardBuilder {
-    static private String LOG_TAG = "VCardDataBuilder"; 
-    
+public class VCardEntryConstructor implements VCardInterpreter {
+    private static String LOG_TAG = "VCardEntryConstructor";
+
     /**
      * If there's no other information available, this class uses this charset for encoding
-     * byte arrays.
+     * byte arrays to String.
      */
-    static public String TARGET_CHARSET = "UTF-8"; 
-    
-    private ContactStruct.Property mCurrentProperty = new ContactStruct.Property();
-    private ContactStruct mCurrentContactStruct;
+    /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
+
+    private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
+    private VCardEntry mCurrentContactStruct;
     private String mParamType;
     
     /**
-     * The charset using which VParser parses the text.
+     * The charset using which {@link VCardInterpreter} parses the text.
      */
-    private String mSourceCharset;
-    
+    private String mInputCharset;
+
     /**
      * The charset with which byte array is encoded to String.
      */
-    private String mTargetCharset;
-    private boolean mStrictLineBreakParsing;
-    
+    final private String mCharsetForDecodedBytes;
+    final private boolean mStrictLineBreakParsing;
     final private int mVCardType;
     final private Account mAccount;
     
-    // Just for testing.
+    /** For measuring performance. */
     private long mTimePushIntoContentResolver;
-    
-    private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
-    
-    public VCardDataBuilder() {
-        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+
+    final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+
+    public VCardEntryConstructor() {
+        this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
     }
 
-    /**
-     * @hide 
-     */
-    public VCardDataBuilder(int vcardType) {
+    public VCardEntryConstructor(final int vcardType) {
         this(null, null, false, vcardType, null);
     }
 
-    /**
-     * @hide 
-     */
-    public VCardDataBuilder(String charset,
-            boolean strictLineBreakParsing, int vcardType, Account account) {
+    public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
+            final int vcardType, final Account account) {
         this(null, charset, strictLineBreakParsing, vcardType, account);
     }
-    
-    /**
-     * @hide
-     */
-    public VCardDataBuilder(String sourceCharset,
-            String targetCharset,
-            boolean strictLineBreakParsing,
-            int vcardType,
-            Account account) {
-        if (sourceCharset != null) {
-            mSourceCharset = sourceCharset;
+
+    public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
+            final boolean strictLineBreakParsing, final int vcardType,
+            final Account account) {
+        if (inputCharset != null) {
+            mInputCharset = inputCharset;
         } else {
-            mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+            mInputCharset = VCardConfig.DEFAULT_CHARSET;
         }
-        if (targetCharset != null) {
-            mTargetCharset = targetCharset;
+        if (charsetForDetodedBytes != null) {
+            mCharsetForDecodedBytes = charsetForDetodedBytes;
         } else {
-            mTargetCharset = TARGET_CHARSET;
+            mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
         }
         mStrictLineBreakParsing = strictLineBreakParsing;
         mVCardType = vcardType;
         mAccount = account;
     }
-    
-    public void addEntryHandler(EntryHandler entryHandler) {
+
+    public void addEntryHandler(VCardEntryHandler entryHandler) {
         mEntryHandlers.add(entryHandler);
     }
     
     public void start() {
-        for (EntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onParsingStart();
+        for (VCardEntryHandler entryHandler : mEntryHandlers) {
+            entryHandler.onStart();
         }
     }
 
     public void end() {
-        for (EntryHandler entryHandler : mEntryHandlers) {
-            entryHandler.onParsingEnd();
+        for (VCardEntryHandler entryHandler : mEntryHandlers) {
+            entryHandler.onEnd();
         }
     }
 
     /**
+     * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
+     */
+    public void clear() {
+        mCurrentContactStruct = null;
+        mCurrentProperty = new VCardEntry.Property();
+    }
+
+    /**
      * Assume that VCard is not nested. In other words, this code does not accept 
      */
-    public void startRecord(String type) {
-        // TODO: add the method clear() instead of using null for reducing GC?
+    public void startEntry() {
         if (mCurrentContactStruct != null) {
-            // This means startRecord() is called inside startRecord() - endRecord() block.
-            // TODO: should throw some Exception
             Log.e(LOG_TAG, "Nested VCard code is not supported now.");
         }
-        if (!type.equalsIgnoreCase("VCARD")) {
-            // TODO: add test case for this
-            Log.e(LOG_TAG, "This is not VCARD!");
-        }
-
-        mCurrentContactStruct = new ContactStruct(mVCardType, mAccount);
+        mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
     }
 
-    public void endRecord() {
+    public void endEntry() {
         mCurrentContactStruct.consolidateFields();
-        for (EntryHandler entryHandler : mEntryHandlers) {
+        for (VCardEntryHandler entryHandler : mEntryHandlers) {
             entryHandler.onEntryCreated(mCurrentContactStruct);
         }
         mCurrentContactStruct = null;
@@ -165,9 +147,8 @@
     }
 
     public void propertyGroup(String group) {
-        // ContactStruct does not support Group.
     }
-    
+
     public void propertyParamType(String type) {
         if (mParamType != null) {
             Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -184,26 +165,26 @@
         mCurrentProperty.addParameter(mParamType, value);
         mParamType = null;
     }
-    
-    private String encodeString(String originalString, String targetCharset) {
-        if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+
+    private String encodeString(String originalString, String charsetForDecodedBytes) {
+        if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
             return originalString;
         }
-        Charset charset = Charset.forName(mSourceCharset);
+        Charset charset = Charset.forName(mInputCharset);
         ByteBuffer byteBuffer = charset.encode(originalString);
         // byteBuffer.array() "may" return byte array which is larger than
         // byteBuffer.remaining(). Here, we keep on the safe side.
         byte[] bytes = new byte[byteBuffer.remaining()];
         byteBuffer.get(bytes);
         try {
-            return new String(bytes, targetCharset);
+            return new String(bytes, charsetForDecodedBytes);
         } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+            Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
             return null;
         }
     }
-    
-    private String handleOneValue(String value, String targetCharset, String encoding) {
+
+    private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
         if (encoding != null) {
             if (encoding.equals("BASE64") || encoding.equals("B")) {
                 mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
@@ -269,9 +250,9 @@
                 }
                 byte[] bytes;
                 try {
-                    bytes = builder.toString().getBytes(mSourceCharset);
+                    bytes = builder.toString().getBytes(mInputCharset);
                 } catch (UnsupportedEncodingException e1) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+                    Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
                     bytes = builder.toString().getBytes();
                 }
                 
@@ -283,38 +264,37 @@
                 }
 
                 try {
-                    return new String(bytes, targetCharset);
+                    return new String(bytes, charsetForDecodedBytes);
                 } catch (UnsupportedEncodingException e) {
-                    Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+                    Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
                     return new String(bytes);
                 }
             }
             // Unknown encoding. Fall back to default.
         }
-        return encodeString(value, targetCharset);
+        return encodeString(value, charsetForDecodedBytes);
     }
     
     public void propertyValues(List<String> values) {
-        if (values == null || values.size() == 0) {
+        if (values == null || values.isEmpty()) {
             return;
         }
 
         final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
-        String charset =
+        final String charset =
             ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
-        String targetCharset = CharsetUtils.nameForDefaultVendor(charset); 
-        
         final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
-        String encoding =
+        final String encoding =
             ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-        
-        if (targetCharset == null || targetCharset.length() == 0) {
-            targetCharset = mTargetCharset;
+
+        String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
+        if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
+            charsetForDecodedBytes = mCharsetForDecodedBytes;
         }
-        
-        for (String value : values) {
+
+        for (final String value : values) {
             mCurrentProperty.addToPropertyValueList(
-                    handleOneValue(value, targetCharset, encoding));
+                    handleOneValue(value, charsetForDecodedBytes, encoding));
         }
     }
 
diff --git a/core/java/android/pim/vcard/VCardEntryCounter.java b/core/java/android/pim/vcard/VCardEntryCounter.java
index f99b46c..7bab50d 100644
--- a/core/java/android/pim/vcard/VCardEntryCounter.java
+++ b/core/java/android/pim/vcard/VCardEntryCounter.java
@@ -17,29 +17,32 @@
 
 import java.util.List;
 
-public class VCardEntryCounter implements VCardBuilder {
+/**
+ * The class which just counts the number of vCard entries in the specified input.
+ */
+public class VCardEntryCounter implements VCardInterpreter {
     private int mCount;
-    
+
     public int getCount() {
         return mCount;
     }
-    
+
     public void start() {
     }
-    
+
     public void end() {
     }
 
-    public void startRecord(String type) {
+    public void startEntry() {
     }
 
-    public void endRecord() {
+    public void endEntry() {
         mCount++;
     }
-    
+
     public void startProperty() {
     }
-    
+
     public void endProperty() {
     }
 
@@ -57,4 +60,4 @@
 
     public void propertyValues(List<String> values) {
     }    
-}
\ No newline at end of file
+}
diff --git a/core/java/android/pim/vcard/EntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
similarity index 73%
rename from core/java/android/pim/vcard/EntryHandler.java
rename to core/java/android/pim/vcard/VCardEntryHandler.java
index 7fb8114..83a67fe 100644
--- a/core/java/android/pim/vcard/EntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,23 +16,23 @@
 package android.pim.vcard;
 
 /**
- * Unlike {@link VCardBuilder}, this (and {@link VCardDataBuilder}) assumes
- * "each VCard entry should be correctly parsed and passed to each EntryHandler object",
+ * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
+ * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
  */
-public interface EntryHandler {
+public interface VCardEntryHandler {
     /**
      * Called when the parsing started.
      */
-    public void onParsingStart();
+    public void onStart();
 
     /**
      * The method called when one VCard entry is successfully created
      */
-    public void onEntryCreated(final ContactStruct entry);
+    public void onEntryCreated(final VCardEntry entry);
 
     /**
      * Called when the parsing ended.
      * Able to be use this method for showing performance log, etc.
      */
-    public void onParsingEnd();
+    public void onEnd();
 }
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
new file mode 100644
index 0000000..b5237c0
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -0,0 +1,102 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.List;
+
+/**
+ * <P>
+ * The interface which should be implemented by the classes which have to analyze each
+ * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * </P>
+ * <P>
+ * Here, there are several terms specific to vCard (and this library).
+ * </P>
+ * <P>
+ * The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
+ * and end with "END:VCARD".
+ * </P>
+ * <P>
+ * The term "property" is one line in vCard entry, which consists of "group", "property name",
+ * "parameter(param) names and values", and "property values".
+ * </P>
+ * <P>
+ * e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
+ * </P>
+ */
+public interface VCardInterpreter {
+    /**
+     * Called when vCard interpretation started.
+     */
+    void start();
+
+    /**
+     * Called when vCard interpretation finished.
+     */
+    void end();
+
+    /** 
+     * Called when parsing one vCard entry started.
+     * More specifically, this method is called when "BEGIN:VCARD" is read.
+     */
+    void startEntry();
+
+    /**
+     * Called when parsing one vCard entry ended.
+     * More specifically, this method is called when "END:VCARD" is read.
+     * Note that {@link #startEntry()} may be called since
+     * vCard (especially 2.1) allows nested vCard.
+     */
+    void endEntry();
+
+    /**
+     * Called when reading one property started.
+     */
+    void startProperty();
+
+    /**
+     * Called when reading one property ended.
+     */
+    void endProperty();
+
+    /**
+     * @param group A group name. This method may be called more than once or may not be
+     * called at all, depending on how many gruoups are appended to the property.
+     */
+    void propertyGroup(String group);
+
+    /**
+     * @param name A property name like "N", "FN", "ADR", etc.
+     */
+    void propertyName(String name);
+
+    /**
+     * @param type A parameter name like "ENCODING", "CHARSET", etc.
+     */
+    void propertyParamType(String type);
+
+    /**
+     * @param value A parameter value. This method may be called without
+     * {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
+     */
+    void propertyParamValue(String value);
+
+    /**
+     * @param values List of property values. The size of values would be 1 unless
+     * coressponding property name is "N", "ADR", or "ORG".
+     */
+    void propertyValues(List<String> values);
+}
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
new file mode 100644
index 0000000..99f81f7
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -0,0 +1,102 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The {@link VCardInterpreter} implementation which aggregates more than one
+ * {@link VCardInterpreter} objects and make a user object treat them as one
+ * {@link VCardInterpreter} object.
+ */
+public class VCardInterpreterCollection implements VCardInterpreter {
+    private final Collection<VCardInterpreter> mInterpreterCollection;
+    
+    public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
+        mInterpreterCollection = interpreterCollection;
+    }
+
+    public Collection<VCardInterpreter> getCollection() {
+        return mInterpreterCollection;
+    }
+
+    public void start() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.start();
+        }
+    }
+
+    public void end() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.end();
+        }
+    }
+
+    public void startEntry() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.startEntry();
+        }
+    }
+
+    public void endEntry() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.endEntry();
+        }
+    }
+
+    public void startProperty() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.startProperty();
+        }
+    }
+
+    public void endProperty() {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.endProperty();
+        }
+    }
+
+    public void propertyGroup(String group) {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.propertyGroup(group);
+        }
+    }
+
+    public void propertyName(String name) {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.propertyName(name);
+        }
+    }
+
+    public void propertyParamType(String type) {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.propertyParamType(type);
+        }
+    }
+
+    public void propertyParamValue(String value) {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.propertyParamValue(value);
+        }
+    }
+
+    public void propertyValues(List<String> values) {
+        for (VCardInterpreter builder : mInterpreterCollection) {
+            builder.propertyValues(values);
+        }
+    }
+}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index b5e5049..57c52a6 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -21,63 +21,74 @@
 import java.io.InputStream;
 
 public abstract class VCardParser {
-
+    protected final int mParseType;
     protected boolean mCanceled;
-    
+
+    public VCardParser() {
+        this(VCardConfig.PARSE_TYPE_UNKNOWN);
+    }
+
+    public VCardParser(int parseType) {
+        mParseType = parseType;
+    }
+
     /**
+     * <P>
      * Parses the given stream and send the VCard data into VCardBuilderBase object.
-     * 
+     * </P.
+     * <P>
      * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
      * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
      * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
      * In some exreme case, some VCard may have different charsets in one VCard (though
      * we do not see any device which emits such kind of malicious data)
-     * 
+     * </P>
+     * <P>
      * 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,
-     * 
-     * We recommend you to use VCardSourceDetector and detect which kind of source the
+     * </P>
+     * <P>
+     * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
      * VCard comes from and explicitly specify a charset using the result.
-     *       
+     * </P>
+     *
      * @param is The source to parse.
-     * @param builder The VCardBuilderBase object which used to construct data. If you want to
-     * include multiple VCardBuilderBase objects in this field, consider using
-     * {#link VCardBuilderCollection} class.
+     * @param interepreter A {@link VCardInterpreter} object which used to construct data.
      * @return Returns true for success. Otherwise returns false.
      * @throws IOException, VCardException
      */
-    public abstract boolean parse(InputStream is, VCardBuilder builder)
+    public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
             throws IOException, VCardException;
     
     /**
+     * <P>
      * The method variants which accept charset.
-     * 
+     * </P>
+     * <P>
      * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
      * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
      * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
-     * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification
-     * (e.g. W53K).
-     *  
+     * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
+     * </P>
+     *
      * @param is The source to parse.
      * @param charset Charset to be used.
      * @param builder The VCardBuilderBase object.
      * @return Returns true when successful. Otherwise returns false.
      * @throws IOException, VCardException
      */
-    public abstract boolean parse(InputStream is, String charset, VCardBuilder builder)
+    public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
             throws IOException, VCardException;
     
     /**
      * The method variants which tells this object the operation is already canceled.
-     * XXX: Is this really necessary?
-     * @hide 
      */
     public abstract void parse(InputStream is, String charset,
-            VCardBuilder builder, boolean canceled)
+            VCardInterpreter builder, boolean canceled)
         throws IOException, VCardException;
     
     /**
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 11b3888..ec569d4 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,11 +15,11 @@
  */
 package android.pim.vcard;
 
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
 import android.pim.vcard.exception.VCardException;
 import android.pim.vcard.exception.VCardInvalidCommentLineException;
 import android.pim.vcard.exception.VCardInvalidLineException;
 import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardNotSupportedException;
 import android.pim.vcard.exception.VCardVersionException;
 import android.util.Log;
 
@@ -31,13 +31,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Set;
 
 /**
- * This class is used to parse vcard. Please refer to vCard Specification 2.1.
+ * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
  */
 public class VCardParser_V21 extends VCardParser {
-    private static final String LOG_TAG = "vcard.VCardParser_V21";
-    
+    private static final String LOG_TAG = "VCardParser_V21";
+
     /** Store the known-type */
     private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
             Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
@@ -52,7 +53,7 @@
     /** Store the known-value */
     private static final HashSet<String> sKnownValueSet = new HashSet<String>(
             Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-        
+
     /** Store the property names available in vCard 2.1 */
     private static final HashSet<String> sAvailablePropertyNameSetV21 =
         new HashSet<String>(Arrays.asList(
@@ -72,7 +73,7 @@
     private String mPreviousLine;
     
     /** The builder to build parsed data */
-    protected VCardBuilder mBuilder = null;
+    protected VCardInterpreter mBuilder = null;
 
     /** 
      * The encoding type. "Encoding" in vCard is different from "Charset".
@@ -82,7 +83,7 @@
     
     protected final String sDefaultEncoding = "8BIT";
     
-    // Should not directly read a line from this. Use getLine() instead.
+    // Should not directly read a line from this object. Use getLine() instead.
     protected BufferedReader mReader;
     
     // In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
@@ -91,9 +92,10 @@
     
     // In order to reduce warning message as much as possible, we hold the value which made Logger
     // emit a warning message.
-    protected HashSet<String> mWarningValueMap = new HashSet<String>();
-    
-    // Just for debugging
+    protected Set<String> mUnknownTypeMap = new HashSet<String>();
+    protected Set<String> mUnknownValueMap = new HashSet<String>();
+
+    // For measuring performance.
     private long mTimeTotal;
     private long mTimeReadStartRecord;
     private long mTimeReadEndRecord;
@@ -106,23 +108,28 @@
     private long mTimeHandleMiscPropertyValue;
     private long mTimeHandleQuotedPrintable;
     private long mTimeHandleBase64;
-    
-    /**
-     * Create a new VCard parser.
-     */
+
     public VCardParser_V21() {
-        super();
+        this(VCardConfig.PARSE_TYPE_UNKNOWN);
     }
 
     public VCardParser_V21(VCardSourceDetector detector) {
-        super();
-        if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) {
+        this(detector.getEstimatedType());
+    }
+
+    /**
+     * TODO: Merge detector and parser mode.
+     */
+    public VCardParser_V21(int parseType) {
+        super(parseType);
+        if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
             mNestCount = 1;
         }
     }
-    
+
     /**
-     * Parse the file at the given position
+     * Parses the file at the given position.
+     *
      * vcard_file = [wsls] vcard [wsls]
      */
     protected void parseVCardFile() throws IOException, VCardException {
@@ -146,18 +153,22 @@
         }
     }
 
-    protected String getVersion() {
-        return "2.1";
+    protected int getVersion() {
+        return VCardConfig.FLAG_V21;
     }
-    
+
+    protected String getVersionString() {
+        return VCardConstants.VERSION_V21;
+    }
+
     /**
      * @return true when the propertyName is a valid property name.
      */
     protected boolean isValidPropertyName(String propertyName) {
         if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
                 propertyName.startsWith("X-")) && 
-                !mWarningValueMap.contains(propertyName)) {
-            mWarningValueMap.add(propertyName);
+                !mUnknownTypeMap.contains(propertyName)) {
+            mUnknownTypeMap.add(propertyName);
             Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
         }
         return true;
@@ -194,11 +205,11 @@
             }
         }
     }
-    
+
     /**
-     *  vcard        = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
-     *                 items *CRLF
-     *                 "END" [ws] ":" [ws] "VCARD"
+     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+     *         items *CRLF
+     *         "END" [ws] ":" [ws] "VCARD"
      */
     private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
         boolean allowGarbage = false;
@@ -219,7 +230,7 @@
         long start;
         if (mBuilder != null) {
             start = System.currentTimeMillis();
-            mBuilder.startRecord("VCARD");
+            mBuilder.startEntry();
             mTimeReadStartRecord += System.currentTimeMillis() - start;
         }
         start = System.currentTimeMillis();
@@ -228,7 +239,7 @@
         readEndVCard(true, false);
         if (mBuilder != null) {
             start = System.currentTimeMillis();
-            mBuilder.endRecord();
+            mBuilder.endEntry();
             mTimeReadEndRecord += System.currentTimeMillis() - start;
         }
         return true;
@@ -239,8 +250,7 @@
      * @throws IOException
      * @throws VCardException
      */
-    protected boolean readBeginVCard(boolean allowGarbage)
-            throws IOException, VCardException {
+    protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
         String line;
         do {
             while (true) {
@@ -255,8 +265,9 @@
             int length = strArray.length;
 
             // Though vCard 2.1/3.0 specification does not allow lower cases,
-            // some data may have them, so we allow it (Actually, previous code
-            // had explicitly allowed "BEGIN:vCard" though there's no example).
+            // vCard file emitted by some external vCard expoter have such invalid Strings.
+            // So we allow it.
+            // e.g. BEGIN:vCard
             if (length == 2 &&
                     strArray[0].trim().equalsIgnoreCase("BEGIN") &&
                     strArray[1].trim().equalsIgnoreCase("VCARD")) {
@@ -279,7 +290,7 @@
     /**
      * The arguments useCache and allowGarbase are usually true and false accordingly when
      * this function is called outside this function itself. 
-     * 
+     *
      * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
      * is used.
      * @param allowGarbage When true, ignore non "END:VCARD" line.
@@ -322,7 +333,6 @@
      *       / item
      */
     protected void parseItems() throws IOException, VCardException {
-        /* items *CRLF item / item */
         boolean ended = false;
         
         if (mBuilder != null) {
@@ -363,12 +373,12 @@
      *      / [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;
 
-        String line = getNonEmptyLine();
+        final String line = getNonEmptyLine();
         long start = System.currentTimeMillis();
 
         String[] propertyNameAndValue = separateLineAndHandleGroup(line);
@@ -399,9 +409,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);
@@ -409,23 +420,22 @@
             return false;
         }
         
-        throw new VCardException("Unknown property name: \"" + 
-                propertyName + "\"");
+        throw new VCardException("Unknown property name: \"" + propertyName + "\"");
     }
 
     static private final int STATE_GROUP_OR_PROPNAME = 0;
     static private final int STATE_PARAMS = 1;
-    // vCard 3.1 specification allows double-quoted param-value, while vCard 2.1 does not.
+    // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
     // This is just for safety.
     static private final int STATE_PARAMS_IN_DQUOTE = 2;
-    
+
     protected String[] separateLineAndHandleGroup(String line) throws VCardException {
-        int length = line.length();
         int state = STATE_GROUP_OR_PROPNAME;
         int nameIndex = 0;
 
-        String[] propertyNameAndValue = new String[2];
+        final String[] propertyNameAndValue = new String[2];
 
+        final int length = line.length();
         if (length > 0 && line.charAt(0) == '#') {
             throw new VCardInvalidCommentLineException();
         }
@@ -433,82 +443,84 @@
         for (int i = 0; i < length; i++) {
             char ch = line.charAt(i); 
             switch (state) {
-            case STATE_GROUP_OR_PROPNAME:
-                if (ch == ':') { 
-                    String propertyName = line.substring(nameIndex, i);
-                    if (propertyName.equalsIgnoreCase("END")) {
-                        mPreviousLine = line;
-                        return null;
+                case STATE_GROUP_OR_PROPNAME: {
+                    if (ch == ':') {
+                        final String propertyName = line.substring(nameIndex, i);
+                        if (propertyName.equalsIgnoreCase("END")) {
+                            mPreviousLine = line;
+                            return null;
+                        }
+                        if (mBuilder != null) {
+                            mBuilder.propertyName(propertyName);
+                        }
+                        propertyNameAndValue[0] = propertyName;
+                        if (i < length - 1) {
+                            propertyNameAndValue[1] = line.substring(i + 1);
+                        } else {
+                            propertyNameAndValue[1] = "";
+                        }
+                        return propertyNameAndValue;
+                    } else if (ch == '.') {
+                        String groupName = line.substring(nameIndex, i);
+                        if (mBuilder != null) {
+                            mBuilder.propertyGroup(groupName);
+                        }
+                        nameIndex = i + 1;
+                    } else if (ch == ';') {
+                        String propertyName = line.substring(nameIndex, i);
+                        if (propertyName.equalsIgnoreCase("END")) {
+                            mPreviousLine = line;
+                            return null;
+                        }
+                        if (mBuilder != null) {
+                            mBuilder.propertyName(propertyName);
+                        }
+                        propertyNameAndValue[0] = propertyName;
+                        nameIndex = i + 1;
+                        state = STATE_PARAMS;
                     }
-                    if (mBuilder != null) {
-                        mBuilder.propertyName(propertyName);
-                    }
-                    propertyNameAndValue[0] = propertyName; 
-                    if (i < length - 1) {
-                        propertyNameAndValue[1] = line.substring(i + 1); 
-                    } else {
-                        propertyNameAndValue[1] = "";
-                    }
-                    return propertyNameAndValue;
-                } else if (ch == '.') {
-                    String groupName = line.substring(nameIndex, i);
-                    if (mBuilder != null) {
-                        mBuilder.propertyGroup(groupName);
-                    }
-                    nameIndex = i + 1;
-                } else if (ch == ';') {
-                    String propertyName = line.substring(nameIndex, i);
-                    if (propertyName.equalsIgnoreCase("END")) {
-                        mPreviousLine = line;
-                        return null;
-                    }
-                    if (mBuilder != null) {
-                        mBuilder.propertyName(propertyName);
-                    }
-                    propertyNameAndValue[0] = propertyName;
-                    nameIndex = i + 1;
-                    state = STATE_PARAMS;
+                    break;
                 }
-                break;
-            case STATE_PARAMS:
-                if (ch == '"') {
-                    state = STATE_PARAMS_IN_DQUOTE;
-                } else if (ch == ';') { 
-                    handleParams(line.substring(nameIndex, i));
-                    nameIndex = i + 1;
-                } else if (ch == ':') {
-                    handleParams(line.substring(nameIndex, i));
-                    if (i < length - 1) {
-                        propertyNameAndValue[1] = line.substring(i + 1);
-                    } else {
-                        propertyNameAndValue[1] = "";
+                case STATE_PARAMS: {
+                    if (ch == '"') {
+                        state = STATE_PARAMS_IN_DQUOTE;
+                    } else if (ch == ';') {
+                        handleParams(line.substring(nameIndex, i));
+                        nameIndex = i + 1;
+                    } else if (ch == ':') {
+                        handleParams(line.substring(nameIndex, i));
+                        if (i < length - 1) {
+                            propertyNameAndValue[1] = line.substring(i + 1);
+                        } else {
+                            propertyNameAndValue[1] = "";
+                        }
+                        return propertyNameAndValue;
                     }
-                    return propertyNameAndValue;
+                    break;
                 }
-                break;
-            case STATE_PARAMS_IN_DQUOTE:
-                if (ch == '"') {
-                    state = STATE_PARAMS;
+                case STATE_PARAMS_IN_DQUOTE: {
+                    if (ch == '"') {
+                        state = STATE_PARAMS;
+                    }
+                    break;
                 }
-                break;
             }
         }
         
         throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
     }
-    
-    
+
     /**
-     * params      = ";" [ws] paramlist
-     * paramlist   = paramlist [ws] ";" [ws] param
-     *             / param
-     * param       = "TYPE" [ws] "=" [ws] ptypeval
-     *             / "VALUE" [ws] "=" [ws] pvalueval
-     *             / "ENCODING" [ws] "=" [ws] pencodingval
-     *             / "CHARSET" [ws] "=" [ws] charsetval
-     *             / "LANGUAGE" [ws] "=" [ws] langval
-     *             / "X-" word [ws] "=" [ws] word
-     *             / knowntype
+     * params     = ";" [ws] paramlist
+     * paramlist  = paramlist [ws] ";" [ws] param
+     *            / param
+     * param      = "TYPE" [ws] "=" [ws] ptypeval
+     *            / "VALUE" [ws] "=" [ws] pvalueval
+     *            / "ENCODING" [ws] "=" [ws] pencodingval
+     *            / "CHARSET" [ws] "=" [ws] charsetval
+     *            / "LANGUAGE" [ws] "=" [ws] langval
+     *            / "X-" word [ws] "=" [ws] word
+     *            / knowntype
      */
     protected void handleParams(String params) throws VCardException {
         String[] strArray = params.split("=", 2);
@@ -531,19 +543,27 @@
                 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(final String ptypeval) {
         String upperTypeValue = ptypeval;
         if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) && 
-                !mWarningValueMap.contains(ptypeval)) {
-            mWarningValueMap.add(ptypeval);
-            Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval);
+                !mUnknownTypeMap.contains(ptypeval)) {
+            mUnknownTypeMap.add(ptypeval);
+            Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
         }
         if (mBuilder != null) {
             mBuilder.propertyParamType("TYPE");
@@ -554,15 +574,16 @@
     /**
      * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
      */
-    protected void handleValue(final String pvalueval) throws VCardException {
-        if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
-                pvalueval.startsWith("X-")) {
-            if (mBuilder != null) {
-                mBuilder.propertyParamType("VALUE");
-                mBuilder.propertyParamValue(pvalueval);
-            }
-        } else {
-            throw new VCardException("Unknown value \"" + pvalueval + "\"");
+    protected void handleValue(final String pvalueval) {
+        if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
+                pvalueval.startsWith("X-") &&
+                !mUnknownValueMap.contains(pvalueval)) {
+            mUnknownValueMap.add(pvalueval);
+            Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
+        }
+        if (mBuilder != null) {
+            mBuilder.propertyParamType("VALUE");
+            mBuilder.propertyParamValue(pvalueval);
         }
     }
     
@@ -583,8 +604,8 @@
     }
     
     /**
-     * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
-     * but some vCard contains other charset, so we allow them. 
+     * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+     * but today's vCard often contains other charset, so we allow them.
      */
     protected void handleCharset(String charsetval) {
         if (mBuilder != null) {
@@ -592,7 +613,7 @@
             mBuilder.propertyParamValue(charsetval);
         }
     }
-    
+
     /**
      * See also Section 7.1 of RFC 1521
      */
@@ -630,12 +651,12 @@
             mBuilder.propertyParamValue(paramValue);
         }
     }
-    
-    protected void handlePropertyValue(String propertyName, String propertyValue) throws
-            IOException, VCardException {
+
+    protected void handlePropertyValue(String propertyName, String propertyValue)
+            throws IOException, VCardException {
         if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
-            long start = System.currentTimeMillis();
-            String result = getQuotedPrintable(propertyValue);
+            final long start = System.currentTimeMillis();
+            final String result = getQuotedPrintable(propertyValue);
             if (mBuilder != null) {
                 ArrayList<String> v = new ArrayList<String>();
                 v.add(result);
@@ -644,11 +665,11 @@
             mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
         } else if (mEncoding.equalsIgnoreCase("BASE64") ||
                 mEncoding.equalsIgnoreCase("B")) {
-            long start = System.currentTimeMillis();
+            final long start = System.currentTimeMillis();
             // It is very rare, but some BASE64 data may be so big that
             // OutOfMemoryError occurs. To ignore such cases, use try-catch.
             try {
-                String result = getBase64(propertyValue);
+                final String result = getBase64(propertyValue);
                 if (mBuilder != null) {
                     ArrayList<String> v = new ArrayList<String>();
                     v.add(result);
@@ -668,7 +689,7 @@
                 Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
             }
 
-            long start = System.currentTimeMillis();
+            final long start = System.currentTimeMillis();
             if (mBuilder != null) {
                 ArrayList<String> v = new ArrayList<String>();
                 v.add(maybeUnescapeText(propertyValue));
@@ -772,70 +793,46 @@
         }
 
         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.
-     * 
-     * item     = ...
-     *          / [groups "."] "AGENT"
-     *            [params] ":" vcard CRLF
-     * vcard    = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
-     *            items *CRLF "END" [ws] ":" [ws] "VCARD"
-     * 
+     *
+     * item  = ...
+     *       / [groups "."] "AGENT"
+     *         [params] ":" vcard CRLF
+     * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+     *         items *CRLF "END" [ws] ":" [ws] "VCARD"
      */
-    protected void handleAgent(String propertyValue) throws VCardException {
-        throw new VCardNotSupportedException("AGENT Property is not supported now.");
-        /* This is insufficient support. Also, AGENT Property is very rare.
-           Ignore it for now.
-
-        String[] strArray = propertyValue.split(":", 2);
-        if (!(strArray.length == 2 ||
-                strArray[0].trim().equalsIgnoreCase("BEGIN") && 
-                strArray[1].trim().equalsIgnoreCase("VCARD"))) {
-            throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\"");
+    protected void handleAgent(final String propertyValue) throws VCardException {
+        if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+            // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+            return;
+        } else {
+            throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
         }
-        parseItems();
-        readEndVCard();
-        */
+        // TODO: Support AGENT property.
     }
     
     /**
      * For vCard 3.0.
      */
-    protected String maybeUnescapeText(String text) {
+    protected String maybeUnescapeText(final 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) {
+    protected String maybeUnescapeCharacter(final char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(final 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.
@@ -847,14 +844,17 @@
     }
     
     @Override
-    public boolean parse(InputStream is, VCardBuilder builder)
+    public boolean parse(final InputStream is, final VCardInterpreter builder)
             throws IOException, VCardException {
         return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
     }
     
     @Override
-    public boolean parse(InputStream is, String charset, VCardBuilder builder)
+    public boolean parse(InputStream is, String charset, VCardInterpreter 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);
@@ -882,7 +882,7 @@
     }
     
     @Override
-    public void parse(InputStream is, String charset, VCardBuilder builder, boolean canceled)
+    public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
             throws IOException, VCardException {
         mCanceled = canceled;
         parse(is, charset, builder);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..4ecfe97 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -23,12 +23,12 @@
 import java.util.HashSet;
 
 /**
- * This class is used to parse vcard3.0. <br>
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
+ * The class used to parse vCard 3.0.
+ * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
  */
 public class VCardParser_V30 extends VCardParser_V21 {
-    private static final String LOG_TAG = "vcard.VCardParser_V30";
-    
+    private static final String LOG_TAG = "VCardParser_V30";
+
     private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
             Arrays.asList(
                     "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", 
@@ -46,31 +46,64 @@
     private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
 
     private String mPreviousLine;
-    
+
     private boolean mEmittedAgentWarning = false;
-    
-    @Override
-    protected String getVersion() {
-        return Constants.VERSION_V30;
+
+    /**
+     * 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;
+    }
+
+    public VCardParser_V30(int parseMode) {
+        super(parseMode);
+        mStrictParsing = false;
+    }
+
+    @Override
+    protected int getVersion() {
+        return VCardConfig.FLAG_V30;
+    }
+
+    @Override
+    protected String getVersionString() {
+        return VCardConstants.VERSION_V30;
+    }
+
     @Override
     protected boolean isValidPropertyName(String propertyName) {
         if (!(sAcceptablePropsWithParam.contains(propertyName) ||
                 acceptablePropsWithoutParam.contains(propertyName) ||
                 propertyName.startsWith("X-")) &&
-                !mWarningValueMap.contains(propertyName)) {
-            mWarningValueMap.add(propertyName);
+                !mUnknownTypeMap.contains(propertyName)) {
+            mUnknownTypeMap.add(propertyName);
             Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
         }
         return true;
     }
-    
+
     @Override
     protected boolean isValidEncoding(String encoding) {
         return sAcceptableEncodingV30.contains(encoding.toUpperCase());
     }
-    
+
     @Override
     protected String getLine() throws IOException {
         if (mPreviousLine != null) {
@@ -152,17 +185,17 @@
     
     
     /**
-     * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF
-     *         1*(contentline)
+     * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+     *         1 * (contentline)
      *         ;A vCard object MUST include the VERSION, FN and N types.
-     *         [group "."] "END" ":" "VCARD" 1*CRLF
+     *         [group "."] "END" ":" "VCARD" 1 * CRLF
      */
     @Override
     protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
         // TODO: vCard 3.0 supports group.
         return super.readBeginVCard(allowGarbage);
     }
-    
+
     @Override
     protected void readEndVCard(boolean useCache, boolean allowGarbage)
             throws IOException, VCardException {
@@ -189,17 +222,21 @@
             }
         }
     }
-    
+
     @Override
     protected void handleAnyParam(String paramName, String paramValue) {
-        // vCard 3.0 accept comma-separated multiple values, but
-        // current PropertyNode does not accept it.
-        // For now, we do not split the values.
-        //
-        // 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
      *  
@@ -224,7 +261,7 @@
 
     @Override
     protected void handleAgent(String propertyValue) {
-        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0.
+        // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
         //
         // e.g.
         // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
@@ -239,13 +276,13 @@
         // AGENT;VALUE=uri:
         //  CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
         //
-        // This is not VCARD. Should we support this?
-        // throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
+        // This is not vCard. Should we support this?
+        //
+        // Just ignore the line for now, since we cannot know how to handle it...
         if (!mEmittedAgentWarning) {
             Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
             mEmittedAgentWarning = true;
         }
-        // Just ignore the line for now, since we cannot know how to handle it...
     }
     
     /**
@@ -256,7 +293,7 @@
     protected String getBase64(String firstString) throws IOException, VCardException {
         StringBuilder builder = new StringBuilder();
         builder.append(firstString);
-        
+
         while (true) {
             String line = getLine();
             if (line == null) {
@@ -280,10 +317,14 @@
      *              ; \\ encodes \, \n or \N encodes newline
      *              ; \; encodes ;, \, encodes ,
      *              
-     * Note: Apple escape ':' into '\:' while does not escape '\'
-     */ 
+     * Note: Apple escapes ':' into '\:' while does not escape '\'
+     */
     @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/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7e2be2b..7297c50 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -25,15 +25,7 @@
  * Currently this implementation is very premature.
  * @hide
  */
-public class VCardSourceDetector implements VCardBuilder {
-    // Should only be used in package. 
-    static final int TYPE_UNKNOWN = 0;
-    static final int TYPE_APPLE = 1;
-    static final int TYPE_JAPANESE_MOBILE_PHONE = 2;  // Used in Japanese mobile phones.
-    static final int TYPE_FOMA = 3;  // Used in some Japanese FOMA mobile phones.
-    static final int TYPE_WINDOWS_MOBILE_JP = 4;
-    // TODO: Excel, etc.
-
+public class VCardSourceDetector implements VCardInterpreter {
     private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
             "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
             "X-ABADR", "X-ABUID"));
@@ -51,7 +43,7 @@
             "X-SD-DESCRIPTION"));
     private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
     
-    private int mType = TYPE_UNKNOWN;
+    private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
     // Some mobile phones (like FOMA) tells us the charset of the data.
     private boolean mNeedParseSpecifiedCharset;
     private String mSpecifiedCharset;
@@ -62,7 +54,7 @@
     public void end() {
     }
 
-    public void startRecord(String type) {
+    public void startEntry() {
     }    
 
     public void startProperty() {
@@ -72,7 +64,7 @@
     public void endProperty() {
     }
 
-    public void endRecord() {
+    public void endEntry() {
     }
 
     public void propertyGroup(String group) {
@@ -80,21 +72,21 @@
     
     public void propertyName(String name) {
         if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
-            mType = TYPE_FOMA;
+            mType = VCardConfig.PARSE_TYPE_FOMA;
             mNeedParseSpecifiedCharset = true;
             return;
         }
-        if (mType != TYPE_UNKNOWN) {
+        if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
             return;
         }
         if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = TYPE_WINDOWS_MOBILE_JP;
+            mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
         } else if (FOMA_SIGNS.contains(name)) {
-            mType = TYPE_FOMA;
+            mType = VCardConfig.PARSE_TYPE_FOMA;
         } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
-            mType = TYPE_JAPANESE_MOBILE_PHONE;
+            mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
         } else if (APPLE_SIGNS.contains(name)) {
-            mType = TYPE_APPLE;
+            mType = VCardConfig.PARSE_TYPE_APPLE;
         }
     }
 
@@ -110,7 +102,7 @@
         }
     }
 
-    int getType() {
+    /* package */ int getEstimatedType() {
         return mType;
     }
     
@@ -124,14 +116,14 @@
             return mSpecifiedCharset;
         }
         switch (mType) {
-        case TYPE_WINDOWS_MOBILE_JP:
-        case TYPE_FOMA:
-        case TYPE_JAPANESE_MOBILE_PHONE:
-            return "SHIFT_JIS";
-        case TYPE_APPLE:
-            return "UTF-8";
-        default:
-            return null;
+            case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
+            case VCardConfig.PARSE_TYPE_FOMA:
+            case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+                return "SHIFT_JIS";
+            case VCardConfig.PARSE_TYPE_APPLE:
+                return "UTF-8";
+            default:
+                return null;
         }
     }
 }
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index dd44288..80457bb 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -16,16 +16,19 @@
 package android.pim.vcard;
 
 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.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -33,58 +36,81 @@
  * Utilities for VCard handling codes.
  */
 public class VCardUtils {
-    /*
-     * TODO: some of methods in this class should be placed to the more appropriate place...
-     */
-
     // 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 Set<String> sPhoneTypesUnknownToContactsSet;
+    private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+    private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+    private static final Set<String> sMobilePhoneLabelSet;
+
     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, VCardConstants.PARAM_TYPE_CAR);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+        sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.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(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.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(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
+                Phone.TYPE_CALLBACK);
+        sKnownPhoneTypeMap_StoI.put(
+                VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
+                Phone.TYPE_TTY_TDD);
+        sKnownPhoneTypeMap_StoI.put(VCardConstants.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);
+        sPhoneTypesUnknownToContactsSet = new HashSet<String>();
+        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
+        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
+        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
+        sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
+
+        sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
+                VCardConstants.PROPERTY_X_GOOGLE_TALK);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
+        sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
+
+        // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
+        // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
+        // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
+        // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
+        sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
+                "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
+                "\uFF79\uFF72\uFF80\uFF72"));
     }
-    
-    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,18 +118,37 @@
         
         if (types != null) {
             for (String typeString : types) {
-                typeString = typeString.toUpperCase(); 
-                if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+                if (typeString == null) {
+                    continue;
+                }
+                typeString = typeString.toUpperCase();
+                if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
                     hasPref = true;
-                } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+                } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
                     isFax = true;
                 } else {
                     if (typeString.startsWith("X-") && type < 0) {
                         typeString = typeString.substring(2);
                     }
-                    Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+                    if (typeString.length() == 0) {
+                        continue;
+                    }
+                    final 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;
@@ -134,37 +179,54 @@
             return type;
         }
     }
-    
-    public static boolean isValidPhoneAttribute(String phoneAttribute, 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));
+
+    @SuppressWarnings("deprecation")
+    public static boolean isMobilePhoneLabel(final String label) {
+        // 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.
+        return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
+                || sMobilePhoneLabelSet.contains(label));
     }
-    
-    public static String[] sortNameElements(int vcardType,
-            String familyName, String middleName, String givenName) {
-        String[] list = new String[3];
-        switch (VCardConfig.getNameOrderType(vcardType)) {
-        case VCardConfig.NAME_ORDER_JAPANESE:
-            // TODO: Should handle Ascii case?
-            list[0] = familyName;
-            list[1] = middleName;
-            list[2] = givenName; 
-            break;
-        case VCardConfig.NAME_ORDER_EUROPE:
-            list[0] = middleName;
-            list[1] = givenName;
-            list[2] = familyName;
-            break;
-        default:
-            list[0] = givenName;
-            list[1] = middleName;
-            list[2] = familyName;
-            break;
+
+    public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
+        return sPhoneTypesUnknownToContactsSet.contains(label);
+    }
+
+    public static String getPropertyNameForIm(final int protocol) {
+        return sKnownImPropNameMap_ItoS.get(protocol);
+    }
+
+    public static String[] sortNameElements(final int vcardType,
+            final String familyName, final String middleName, final String givenName) {
+        final String[] list = new String[3];
+        final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
+        switch (nameOrderType) {
+            case VCardConfig.NAME_ORDER_JAPANESE: {
+                if (containsOnlyPrintableAscii(familyName) &&
+                        containsOnlyPrintableAscii(givenName)) {
+                    list[0] = givenName;
+                    list[1] = middleName;
+                    list[2] = familyName;
+                } else {
+                    list[0] = familyName;
+                    list[1] = middleName;
+                    list[2] = givenName;
+                }
+                break;
+            }
+            case VCardConfig.NAME_ORDER_EUROPE: {
+                list[0] = middleName;
+                list[1] = givenName;
+                list[2] = familyName;
+                break;
+            }
+            default: {
+                list[0] = givenName;
+                list[1] = middleName;
+                list[2] = familyName;
+                break;
+            }
         }
         return list;
     }
@@ -181,12 +243,11 @@
      * Inserts postal data into the builder object.
      * 
      * 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 #getVCardPostalElements(ContentValues)}
+     * So some conversion may be performed in this method.
      */
     public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
             final ContentProviderOperation.Builder builder,
-            final ContactStruct.PostalData postalData) {
+            final VCardEntry.PostalData postalData) {
         builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
         builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
 
@@ -195,9 +256,22 @@
             builder.withValue(StructuredPostal.LABEL, postalData.label);
         }
 
+        final String streetString;
+        if (TextUtils.isEmpty(postalData.street)) {
+            if (TextUtils.isEmpty(postalData.extendedAddress)) {
+                streetString = null;
+            } else {
+                streetString = postalData.extendedAddress;
+            }
+        } else {
+            if (TextUtils.isEmpty(postalData.extendedAddress)) {
+                streetString = postalData.street;
+            } else {
+                streetString = postalData.street + " " + postalData.extendedAddress;
+            }
+        }
         builder.withValue(StructuredPostal.POBOX, postalData.pobox);
-        // Extended address is dropped since there's no relevant entry in ContactsContract.
-        builder.withValue(StructuredPostal.STREET, postalData.street);
+        builder.withValue(StructuredPostal.STREET, streetString);
         builder.withValue(StructuredPostal.CITY, postalData.localty);
         builder.withValue(StructuredPostal.REGION, postalData.region);
         builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
@@ -209,71 +283,24 @@
             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,
-     * android.content.ContentProviderOperation.Builder,
-     * android.pim.vcard.ContactStruct.PostalData)}
-     */
-    public static String[] getVCardPostalElements(ContentValues contentValues) {
-        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] = "";
-        dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
-        if (dataArray[2] == null) {
-            dataArray[2] = "";
-        }
-        // Assume that localty == city
-        dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
-        if (dataArray[3] == null) {
-            dataArray[3] = "";
-        }
-        String region = contentValues.getAsString(StructuredPostal.REGION);
-        if (!TextUtils.isEmpty(region)) {
-            dataArray[4] = region;
-        } else {
-            dataArray[4] = "";
-        }
-        dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
-        if (dataArray[5] == null) {
-            dataArray[5] = "";
-        }
-        dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
-        if (dataArray[6] == null) {
-            dataArray[6] = "";
-        }
 
-        return dataArray;
-    }
-    
-    public static String constructNameFromElements(int nameOrderType,
-            String familyName, String middleName, String givenName) {
-        return constructNameFromElements(nameOrderType, familyName, middleName, givenName,
+    public static String constructNameFromElements(final int vcardType,
+            final String familyName, final String middleName, final String givenName) {
+        return constructNameFromElements(vcardType, familyName, middleName, givenName,
                 null, null);
     }
 
-    public static String constructNameFromElements(int nameOrderType,
-            String familyName, String middleName, String givenName,
-            String prefix, String suffix) {
-        StringBuilder builder = new StringBuilder();
-        String[] nameList = sortNameElements(nameOrderType,
-                familyName, middleName, givenName);
+    public static String constructNameFromElements(final int vcardType,
+            final String familyName, final String middleName, final String givenName,
+            final String prefix, final String suffix) {
+        final StringBuilder builder = new StringBuilder();
+        final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
         boolean first = true;
         if (!TextUtils.isEmpty(prefix)) {
             first = false;
             builder.append(prefix);
         }
-        for (String namePart : nameList) {
+        for (final String namePart : nameList) {
             if (!TextUtils.isEmpty(namePart)) {
                 if (first) {
                     first = false;
@@ -291,19 +318,52 @@
         }
         return builder.toString();
     }
-    
-    public static boolean containsOnlyPrintableAscii(String str) {
-        if (TextUtils.isEmpty(str)) {
+
+    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(final String...values) {
+        if (values == null) {
             return true;
         }
-        
-        final int length = str.length();
         final int asciiFirst = 0x20;
-        final int asciiLast = 0x126;
-        for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
-            int c = str.codePointAt(i);
-            if (c < asciiFirst || asciiLast < c) {
-                return false;
+        final int asciiLast = 0x7E;  // included
+        for (final String value : values) {
+            if (TextUtils.isEmpty(value)) {
+                continue;
+            }
+            final int length = value.length();
+            for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+                final int c = value.codePointAt(i);
+                if (!((asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n')) {
+                    return false;
+                }
             }
         }
         return true;
@@ -314,17 +374,50 @@
      * or not, which is required by vCard 2.1.
      * See the definition of "7bit" in vCard 2.1 spec for more information.
      */
-    public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
-        if (TextUtils.isEmpty(str)) {
+    public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
+        if (values == null) {
             return true;
         }
-
-        final int length = str.length();
         final int asciiFirst = 0x20;
-        final int asciiLast = 0x126;
-        for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
-            int c = str.codePointAt(i);
-            if (c < asciiFirst || asciiLast < c || c == '\n' || c == '\r') {
+        final int asciiLast = 0x7E;  // included
+        for (final String value : values) {
+            if (TextUtils.isEmpty(value)) {
+                continue;
+            }
+            final int length = value.length();
+            for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+                final int c = value.codePointAt(i);
+                if (!(asciiFirst <= c && c <= asciiLast)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
+        new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
+
+    /**
+     * <P>
+     * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
+     * </P>
+     * <P>
+     * vCard 2.1 specifies:<BR />
+     * word = &lt;any printable 7bit us-ascii except []=:., &gt;
+     * </P>
+     */
+    public static boolean isV21Word(final String value) {
+        if (TextUtils.isEmpty(value)) {
+            return true;
+        }
+        final int asciiFirst = 0x20;
+        final int asciiLast = 0x7E;  // included
+        final int length = value.length();
+        for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+            final int c = value.codePointAt(i);
+            if (!(asciiFirst <= c && c <= asciiLast) ||
+                    sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
                 return false;
             }
         }
@@ -340,86 +433,46 @@
      *       such kind of input but must never output it unless the target is very specific
      *       to the device which is able to parse the malformed input. 
      */
-    public static boolean containsOnlyAlphaDigitHyphen(String str) {
-        if (TextUtils.isEmpty(str)) {
+    public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
+        if (values == null) {
             return true;
         }
-
-        final int lowerAlphabetFirst = 0x41;  // included ('A')
-        final int lowerAlphabetLast = 0x5b;  // not included ('[')
-        final int upperAlphabetFirst = 0x61;  // included ('a')
-        final int upperAlphabetLast = 0x7b;  // included ('{')
-        final int digitFirst = 0x30;  // included ('0')
-        final int digitLast = 0x39;  // included ('9')
+        final int upperAlphabetFirst = 0x41;  // A
+        final int upperAlphabetAfterLast = 0x5b;  // [
+        final int lowerAlphabetFirst = 0x61;  // a
+        final int lowerAlphabetAfterLast = 0x7b;  // {
+        final int digitFirst = 0x30;  // 0
+        final int digitAfterLast = 0x3A;  // :
         final int hyphen = '-';
-        final int length = str.length();
-        for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
-            int codepoint = str.codePointAt(i);
-            if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetLast) ||
-                    (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetLast) ||
-                    (digitFirst <= codepoint && codepoint < digitLast) ||
+        for (final String str : values) {
+            if (TextUtils.isEmpty(str)) {
+                continue;
+            }
+            final int length = str.length();
+            for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+                int codepoint = str.codePointAt(i);
+                if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
+                    (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
+                    (digitFirst <= codepoint && codepoint < digitAfterLast) ||
                     (codepoint == hyphen))) {
-                return false;
+                    return false;
+                }
             }
         }
         return true;
     }
     
-    // TODO: Replace wth the method in Base64 class.
-    private static char PAD = '=';
-    private static final char[] ENCODE64 = {
-        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
-        'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
-        'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
-        'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
-    };
-
-    static public String encodeBase64(byte[] data) {
-        if (data == null) {
-            return "";
-        }
-
-        char[] charBuffer = new char[(data.length + 2) / 3 * 4];
-        int position = 0;
-        int _3byte = 0;
-        for (int i=0; i<data.length-2; i+=3) {
-            _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
-            charBuffer[position++] = ENCODE64[_3byte & 0x3F];
-        }
-        switch(data.length % 3) {
-        case 1: // [111111][11 0000][0000 00][000000]
-            _3byte = ((data[data.length-1] & 0xFF) << 16);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = PAD;
-            charBuffer[position++] = PAD;
-            break;
-        case 2: // [111111][11 1111][1111 00][000000]
-            _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
-            charBuffer[position++] = PAD;
-            break;
-        }
-
-        return new String(charBuffer);
-    }
-    
-    static public String toHalfWidthString(String orgString) {
+    public static String toHalfWidthString(final String orgString) {
         if (TextUtils.isEmpty(orgString)) {
             return null;
         }
-        StringBuilder builder = new StringBuilder();
-        int length = orgString.length();
-        for (int i = 0; i < length; i++) {
+        final StringBuilder builder = new StringBuilder();
+        final int length = orgString.length();
+        for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
             // All Japanese character is able to be expressed by char.
             // Do not need to use String#codepPointAt().
-            char ch = orgString.charAt(i);
-            CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
+            final char ch = orgString.charAt(i);
+            final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
             if (halfWidthText != null) {
                 builder.append(halfWidthText);
             } else {
@@ -428,368 +481,50 @@
         }
         return builder.toString();
     }
-    
-    private VCardUtils() {
-    }
-}
-
-/**
- * TextUtils especially for Japanese.
- * TODO: make this in android.text in the future
- */
-class JapaneseUtils {
-    static private final Map<Character, String> sHalfWidthMap =
-        new HashMap<Character, String>();
-
-    static {
-        // There's no logical mapping rule in Unicode. Sigh.
-        sHalfWidthMap.put('\u3001', "\uFF64");
-        sHalfWidthMap.put('\u3002', "\uFF61");
-        sHalfWidthMap.put('\u300C', "\uFF62");
-        sHalfWidthMap.put('\u300D', "\uFF63");
-        sHalfWidthMap.put('\u301C', "~");
-        sHalfWidthMap.put('\u3041', "\uFF67");
-        sHalfWidthMap.put('\u3042', "\uFF71");
-        sHalfWidthMap.put('\u3043', "\uFF68");
-        sHalfWidthMap.put('\u3044', "\uFF72");
-        sHalfWidthMap.put('\u3045', "\uFF69");
-        sHalfWidthMap.put('\u3046', "\uFF73");
-        sHalfWidthMap.put('\u3047', "\uFF6A");
-        sHalfWidthMap.put('\u3048', "\uFF74");
-        sHalfWidthMap.put('\u3049', "\uFF6B");
-        sHalfWidthMap.put('\u304A', "\uFF75");
-        sHalfWidthMap.put('\u304B', "\uFF76");
-        sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u304D', "\uFF77");
-        sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u304F', "\uFF78");
-        sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u3051', "\uFF79");
-        sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u3053', "\uFF7A");
-        sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u3055', "\uFF7B");
-        sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u3057', "\uFF7C");
-        sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u3059', "\uFF7D");
-        sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u305B', "\uFF7E");
-        sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u305D', "\uFF7F");
-        sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u305F', "\uFF80");
-        sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u3061', "\uFF81");
-        sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u3063', "\uFF6F");
-        sHalfWidthMap.put('\u3064', "\uFF82");
-        sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u3066', "\uFF83");
-        sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u3068', "\uFF84");
-        sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u306A', "\uFF85");
-        sHalfWidthMap.put('\u306B', "\uFF86");
-        sHalfWidthMap.put('\u306C', "\uFF87");
-        sHalfWidthMap.put('\u306D', "\uFF88");
-        sHalfWidthMap.put('\u306E', "\uFF89");
-        sHalfWidthMap.put('\u306F', "\uFF8A");
-        sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u3072', "\uFF8B");
-        sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u3075', "\uFF8C");
-        sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u3078', "\uFF8D");
-        sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u307B', "\uFF8E");
-        sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u307E', "\uFF8F");
-        sHalfWidthMap.put('\u307F', "\uFF90");
-        sHalfWidthMap.put('\u3080', "\uFF91");
-        sHalfWidthMap.put('\u3081', "\uFF92");
-        sHalfWidthMap.put('\u3082', "\uFF93");
-        sHalfWidthMap.put('\u3083', "\uFF6C");
-        sHalfWidthMap.put('\u3084', "\uFF94");
-        sHalfWidthMap.put('\u3085', "\uFF6D");
-        sHalfWidthMap.put('\u3086', "\uFF95");
-        sHalfWidthMap.put('\u3087', "\uFF6E");
-        sHalfWidthMap.put('\u3088', "\uFF96");
-        sHalfWidthMap.put('\u3089', "\uFF97");
-        sHalfWidthMap.put('\u308A', "\uFF98");
-        sHalfWidthMap.put('\u308B', "\uFF99");
-        sHalfWidthMap.put('\u308C', "\uFF9A");
-        sHalfWidthMap.put('\u308D', "\uFF9B");
-        sHalfWidthMap.put('\u308E', "\uFF9C");
-        sHalfWidthMap.put('\u308F', "\uFF9C");
-        sHalfWidthMap.put('\u3090', "\uFF72");
-        sHalfWidthMap.put('\u3091', "\uFF74");
-        sHalfWidthMap.put('\u3092', "\uFF66");
-        sHalfWidthMap.put('\u3093', "\uFF9D");
-        sHalfWidthMap.put('\u309B', "\uFF9E");
-        sHalfWidthMap.put('\u309C', "\uFF9F");
-        sHalfWidthMap.put('\u30A1', "\uFF67");
-        sHalfWidthMap.put('\u30A2', "\uFF71");
-        sHalfWidthMap.put('\u30A3', "\uFF68");
-        sHalfWidthMap.put('\u30A4', "\uFF72");
-        sHalfWidthMap.put('\u30A5', "\uFF69");
-        sHalfWidthMap.put('\u30A6', "\uFF73");
-        sHalfWidthMap.put('\u30A7', "\uFF6A");
-        sHalfWidthMap.put('\u30A8', "\uFF74");
-        sHalfWidthMap.put('\u30A9', "\uFF6B");
-        sHalfWidthMap.put('\u30AA', "\uFF75");
-        sHalfWidthMap.put('\u30AB', "\uFF76");
-        sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u30AD', "\uFF77");
-        sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u30AF', "\uFF78");
-        sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u30B1', "\uFF79");
-        sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u30B3', "\uFF7A");
-        sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u30B5', "\uFF7B");
-        sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u30B7', "\uFF7C");
-        sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u30B9', "\uFF7D");
-        sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u30BB', "\uFF7E");
-        sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u30BD', "\uFF7F");
-        sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u30BF', "\uFF80");
-        sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u30C1', "\uFF81");
-        sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u30C3', "\uFF6F");
-        sHalfWidthMap.put('\u30C4', "\uFF82");
-        sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u30C6', "\uFF83");
-        sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u30C8', "\uFF84");
-        sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u30CA', "\uFF85");
-        sHalfWidthMap.put('\u30CB', "\uFF86");
-        sHalfWidthMap.put('\u30CC', "\uFF87");
-        sHalfWidthMap.put('\u30CD', "\uFF88");
-        sHalfWidthMap.put('\u30CE', "\uFF89");
-        sHalfWidthMap.put('\u30CF', "\uFF8A");
-        sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u30D2', "\uFF8B");
-        sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u30D5', "\uFF8C");
-        sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u30D8', "\uFF8D");
-        sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u30DB', "\uFF8E");
-        sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u30DE', "\uFF8F");
-        sHalfWidthMap.put('\u30DF', "\uFF90");
-        sHalfWidthMap.put('\u30E0', "\uFF91");
-        sHalfWidthMap.put('\u30E1', "\uFF92");
-        sHalfWidthMap.put('\u30E2', "\uFF93");
-        sHalfWidthMap.put('\u30E3', "\uFF6C");
-        sHalfWidthMap.put('\u30E4', "\uFF94");
-        sHalfWidthMap.put('\u30E5', "\uFF6D");
-        sHalfWidthMap.put('\u30E6', "\uFF95");
-        sHalfWidthMap.put('\u30E7', "\uFF6E");
-        sHalfWidthMap.put('\u30E8', "\uFF96");
-        sHalfWidthMap.put('\u30E9', "\uFF97");
-        sHalfWidthMap.put('\u30EA', "\uFF98");
-        sHalfWidthMap.put('\u30EB', "\uFF99");
-        sHalfWidthMap.put('\u30EC', "\uFF9A");
-        sHalfWidthMap.put('\u30ED', "\uFF9B");
-        sHalfWidthMap.put('\u30EE', "\uFF9C");
-        sHalfWidthMap.put('\u30EF', "\uFF9C");
-        sHalfWidthMap.put('\u30F0', "\uFF72");
-        sHalfWidthMap.put('\u30F1', "\uFF74");
-        sHalfWidthMap.put('\u30F2', "\uFF66");
-        sHalfWidthMap.put('\u30F3', "\uFF9D");
-        sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
-        sHalfWidthMap.put('\u30F5', "\uFF76");
-        sHalfWidthMap.put('\u30F6', "\uFF79");
-        sHalfWidthMap.put('\u30FB', "\uFF65");
-        sHalfWidthMap.put('\u30FC', "\uFF70");
-        sHalfWidthMap.put('\uFF01', "!");
-        sHalfWidthMap.put('\uFF02', "\"");
-        sHalfWidthMap.put('\uFF03', "#");
-        sHalfWidthMap.put('\uFF04', "$");
-        sHalfWidthMap.put('\uFF05', "%");
-        sHalfWidthMap.put('\uFF06', "&");
-        sHalfWidthMap.put('\uFF07', "'");
-        sHalfWidthMap.put('\uFF08', "(");
-        sHalfWidthMap.put('\uFF09', ")");
-        sHalfWidthMap.put('\uFF0A', "*");
-        sHalfWidthMap.put('\uFF0B', "+");
-        sHalfWidthMap.put('\uFF0C', ",");
-        sHalfWidthMap.put('\uFF0D', "-");
-        sHalfWidthMap.put('\uFF0E', ".");
-        sHalfWidthMap.put('\uFF0F', "/");
-        sHalfWidthMap.put('\uFF10', "0");
-        sHalfWidthMap.put('\uFF11', "1");
-        sHalfWidthMap.put('\uFF12', "2");
-        sHalfWidthMap.put('\uFF13', "3");
-        sHalfWidthMap.put('\uFF14', "4");
-        sHalfWidthMap.put('\uFF15', "5");
-        sHalfWidthMap.put('\uFF16', "6");
-        sHalfWidthMap.put('\uFF17', "7");
-        sHalfWidthMap.put('\uFF18', "8");
-        sHalfWidthMap.put('\uFF19', "9");
-        sHalfWidthMap.put('\uFF1A', ":");
-        sHalfWidthMap.put('\uFF1B', ";");
-        sHalfWidthMap.put('\uFF1C', "<");
-        sHalfWidthMap.put('\uFF1D', "=");
-        sHalfWidthMap.put('\uFF1E', ">");
-        sHalfWidthMap.put('\uFF1F', "?");
-        sHalfWidthMap.put('\uFF20', "@");
-        sHalfWidthMap.put('\uFF21', "A");
-        sHalfWidthMap.put('\uFF22', "B");
-        sHalfWidthMap.put('\uFF23', "C");
-        sHalfWidthMap.put('\uFF24', "D");
-        sHalfWidthMap.put('\uFF25', "E");
-        sHalfWidthMap.put('\uFF26', "F");
-        sHalfWidthMap.put('\uFF27', "G");
-        sHalfWidthMap.put('\uFF28', "H");
-        sHalfWidthMap.put('\uFF29', "I");
-        sHalfWidthMap.put('\uFF2A', "J");
-        sHalfWidthMap.put('\uFF2B', "K");
-        sHalfWidthMap.put('\uFF2C', "L");
-        sHalfWidthMap.put('\uFF2D', "M");
-        sHalfWidthMap.put('\uFF2E', "N");
-        sHalfWidthMap.put('\uFF2F', "O");
-        sHalfWidthMap.put('\uFF30', "P");
-        sHalfWidthMap.put('\uFF31', "Q");
-        sHalfWidthMap.put('\uFF32', "R");
-        sHalfWidthMap.put('\uFF33', "S");
-        sHalfWidthMap.put('\uFF34', "T");
-        sHalfWidthMap.put('\uFF35', "U");
-        sHalfWidthMap.put('\uFF36', "V");
-        sHalfWidthMap.put('\uFF37', "W");
-        sHalfWidthMap.put('\uFF38', "X");
-        sHalfWidthMap.put('\uFF39', "Y");
-        sHalfWidthMap.put('\uFF3A', "Z");
-        sHalfWidthMap.put('\uFF3B', "[");
-        sHalfWidthMap.put('\uFF3C', "\\");
-        sHalfWidthMap.put('\uFF3D', "]");
-        sHalfWidthMap.put('\uFF3E', "^");
-        sHalfWidthMap.put('\uFF3F', "_");
-        sHalfWidthMap.put('\uFF41', "a");
-        sHalfWidthMap.put('\uFF42', "b");
-        sHalfWidthMap.put('\uFF43', "c");
-        sHalfWidthMap.put('\uFF44', "d");
-        sHalfWidthMap.put('\uFF45', "e");
-        sHalfWidthMap.put('\uFF46', "f");
-        sHalfWidthMap.put('\uFF47', "g");
-        sHalfWidthMap.put('\uFF48', "h");
-        sHalfWidthMap.put('\uFF49', "i");
-        sHalfWidthMap.put('\uFF4A', "j");
-        sHalfWidthMap.put('\uFF4B', "k");
-        sHalfWidthMap.put('\uFF4C', "l");
-        sHalfWidthMap.put('\uFF4D', "m");
-        sHalfWidthMap.put('\uFF4E', "n");
-        sHalfWidthMap.put('\uFF4F', "o");
-        sHalfWidthMap.put('\uFF50', "p");
-        sHalfWidthMap.put('\uFF51', "q");
-        sHalfWidthMap.put('\uFF52', "r");
-        sHalfWidthMap.put('\uFF53', "s");
-        sHalfWidthMap.put('\uFF54', "t");
-        sHalfWidthMap.put('\uFF55', "u");
-        sHalfWidthMap.put('\uFF56', "v");
-        sHalfWidthMap.put('\uFF57', "w");
-        sHalfWidthMap.put('\uFF58', "x");
-        sHalfWidthMap.put('\uFF59', "y");
-        sHalfWidthMap.put('\uFF5A', "z");
-        sHalfWidthMap.put('\uFF5B', "{");
-        sHalfWidthMap.put('\uFF5C', "|");
-        sHalfWidthMap.put('\uFF5D', "}");
-        sHalfWidthMap.put('\uFF5E', "~");
-        sHalfWidthMap.put('\uFF61', "\uFF61");
-        sHalfWidthMap.put('\uFF62', "\uFF62");
-        sHalfWidthMap.put('\uFF63', "\uFF63");
-        sHalfWidthMap.put('\uFF64', "\uFF64");
-        sHalfWidthMap.put('\uFF65', "\uFF65");
-        sHalfWidthMap.put('\uFF66', "\uFF66");
-        sHalfWidthMap.put('\uFF67', "\uFF67");
-        sHalfWidthMap.put('\uFF68', "\uFF68");
-        sHalfWidthMap.put('\uFF69', "\uFF69");
-        sHalfWidthMap.put('\uFF6A', "\uFF6A");
-        sHalfWidthMap.put('\uFF6B', "\uFF6B");
-        sHalfWidthMap.put('\uFF6C', "\uFF6C");
-        sHalfWidthMap.put('\uFF6D', "\uFF6D");
-        sHalfWidthMap.put('\uFF6E', "\uFF6E");
-        sHalfWidthMap.put('\uFF6F', "\uFF6F");
-        sHalfWidthMap.put('\uFF70', "\uFF70");
-        sHalfWidthMap.put('\uFF71', "\uFF71");
-        sHalfWidthMap.put('\uFF72', "\uFF72");
-        sHalfWidthMap.put('\uFF73', "\uFF73");
-        sHalfWidthMap.put('\uFF74', "\uFF74");
-        sHalfWidthMap.put('\uFF75', "\uFF75");
-        sHalfWidthMap.put('\uFF76', "\uFF76");
-        sHalfWidthMap.put('\uFF77', "\uFF77");
-        sHalfWidthMap.put('\uFF78', "\uFF78");
-        sHalfWidthMap.put('\uFF79', "\uFF79");
-        sHalfWidthMap.put('\uFF7A', "\uFF7A");
-        sHalfWidthMap.put('\uFF7B', "\uFF7B");
-        sHalfWidthMap.put('\uFF7C', "\uFF7C");
-        sHalfWidthMap.put('\uFF7D', "\uFF7D");
-        sHalfWidthMap.put('\uFF7E', "\uFF7E");
-        sHalfWidthMap.put('\uFF7F', "\uFF7F");
-        sHalfWidthMap.put('\uFF80', "\uFF80");
-        sHalfWidthMap.put('\uFF81', "\uFF81");
-        sHalfWidthMap.put('\uFF82', "\uFF82");
-        sHalfWidthMap.put('\uFF83', "\uFF83");
-        sHalfWidthMap.put('\uFF84', "\uFF84");
-        sHalfWidthMap.put('\uFF85', "\uFF85");
-        sHalfWidthMap.put('\uFF86', "\uFF86");
-        sHalfWidthMap.put('\uFF87', "\uFF87");
-        sHalfWidthMap.put('\uFF88', "\uFF88");
-        sHalfWidthMap.put('\uFF89', "\uFF89");
-        sHalfWidthMap.put('\uFF8A', "\uFF8A");
-        sHalfWidthMap.put('\uFF8B', "\uFF8B");
-        sHalfWidthMap.put('\uFF8C', "\uFF8C");
-        sHalfWidthMap.put('\uFF8D', "\uFF8D");
-        sHalfWidthMap.put('\uFF8E', "\uFF8E");
-        sHalfWidthMap.put('\uFF8F', "\uFF8F");
-        sHalfWidthMap.put('\uFF90', "\uFF90");
-        sHalfWidthMap.put('\uFF91', "\uFF91");
-        sHalfWidthMap.put('\uFF92', "\uFF92");
-        sHalfWidthMap.put('\uFF93', "\uFF93");
-        sHalfWidthMap.put('\uFF94', "\uFF94");
-        sHalfWidthMap.put('\uFF95', "\uFF95");
-        sHalfWidthMap.put('\uFF96', "\uFF96");
-        sHalfWidthMap.put('\uFF97', "\uFF97");
-        sHalfWidthMap.put('\uFF98', "\uFF98");
-        sHalfWidthMap.put('\uFF99', "\uFF99");
-        sHalfWidthMap.put('\uFF9A', "\uFF9A");
-        sHalfWidthMap.put('\uFF9B', "\uFF9B");
-        sHalfWidthMap.put('\uFF9C', "\uFF9C");
-        sHalfWidthMap.put('\uFF9D', "\uFF9D");
-        sHalfWidthMap.put('\uFF9E', "\uFF9E");
-        sHalfWidthMap.put('\uFF9F', "\uFF9F");
-        sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
-    }
 
     /**
-     * Return half-width version of that character if possible. Return null if not possible
-     * @param ch input character
-     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+     * Guesses the format of input image. Currently just the first few bytes are used.
+     * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
+     * the guess failed.
+     * @param input Image as byte array.
+     * @return The image type or null when the type cannot be determined.
      */
-    public static CharSequence tryGetHalfWidthText(char ch) {
-        if (sHalfWidthMap.containsKey(ch)) {
-            return sHalfWidthMap.get(ch);
+    public static String guessImageType(final byte[] input) {
+        if (input == null) {
+            return null;
+        }
+        if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
+            return "GIF";
+        } else if (input.length >= 4 && input[0] == (byte) 0x89
+                && input[1] == 'P' && input[2] == 'N' && input[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...
+            return "PNG";
+        } else if (input.length >= 2 && input[0] == (byte) 0xff
+                && input[1] == (byte) 0xd8) {
+            return "JPEG";
         } else {
             return null;
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * @return True when all the given values are null or empty Strings.
+     */
+    public static boolean areAllEmpty(final String...values) {
+        if (values == null) {
+            return true;
+        }
+
+        for (final String value : values) {
+            if (!TextUtils.isEmpty(value)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private VCardUtils() {
+    }
+}
diff --git a/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
new file mode 100644
index 0000000..e72c7df
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.pim.vcard.exception;
+
+public class VCardAgentNotSupportedException extends VCardNotSupportedException {
+    public VCardAgentNotSupportedException() {
+        super();
+    }
+
+    public VCardAgentNotSupportedException(String message) {
+        super(message);
+    }
+
+}
\ No newline at end of file
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/Gmail.java b/core/java/android/provider/Gmail.java
index 073ae6c..8bb7adf 100644
--- a/core/java/android/provider/Gmail.java
+++ b/core/java/android/provider/Gmail.java
@@ -37,9 +37,10 @@
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
 import android.text.style.CharacterStyle;
-import android.text.util.Regex;
 import android.util.Log;
 
+import com.android.common.Patterns;
+
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.ArrayList;
@@ -192,7 +193,7 @@
      */
     public static String getEmailFromAddressString(String addressString) {
         String result = addressString;
-        Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(addressString);
+        Matcher match = Patterns.EMAIL_ADDRESS.matcher(addressString);
         if (match.find()) {
             result = addressString.substring(match.start(), match.end());
         }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..a796fe9 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -238,6 +238,29 @@
         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;
+
+        private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
+            Bitmap bitmap = null;
+            Uri thumbUri = null;
+            try {
+                long thumbId = c.getLong(0);
+                String filePath = c.getString(1);
+                thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
+                ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
+                bitmap = BitmapFactory.decodeFileDescriptor(
+                        pfdInput.getFileDescriptor(), null, options);
+                pfdInput.close();
+            } catch (FileNotFoundException ex) {
+                Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+            } catch (IOException ex) {
+                Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+            } catch (OutOfMemoryError ex) {
+                Log.e(TAG, "failed to allocate memory for thumbnail "
+                        + thumbUri + "; " + ex);
+            }
+            return bitmap;
+        }
 
         /**
          * This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +269,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,18 +297,20 @@
          * @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;
             // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
-            // some optimization for MICRO_KIND: if the magic is non-zero, we don't bother
+            // If the magic is non-zero, we simply return thumbnail if it does exist.
             // querying MediaProvider and simply return thumbnail.
-            if (kind == MICRO_KIND) {
-                MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
-                if (thumbFile.getMagic(origId) != 0) {
+            MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
+            long magic = thumbFile.getMagic(origId);
+            if (magic != 0) {
+                if (kind == MICRO_KIND) {
                     byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
                     if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
                         bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -291,20 +319,34 @@
                         }
                     }
                     return bitmap;
+                } else if (kind == MINI_KIND) {
+                    String column = isVideo ? "video_id=" : "image_id=";
+                    Cursor c = null;
+                    try {
+                        c = cr.query(baseUri, PROJECTION, column + origId, null, null);
+                        if (c != null && c.moveToFirst()) {
+                            bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
+                            if (bitmap != null) {
+                                return bitmap;
+                            }
+                        }
+                    } finally {
+                        if (c != null) c.close();
+                    }
                 }
             }
 
             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;
 
                 // Assuming thumbnail has been generated, at least original image exists.
                 if (kind == MICRO_KIND) {
-                    MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
                     byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
                     if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
                         bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -314,24 +356,7 @@
                     }
                 } else if (kind == MINI_KIND) {
                     if (c.moveToFirst()) {
-                        ParcelFileDescriptor pfdInput;
-                        Uri thumbUri = null;
-                        try {
-                            long thumbId = c.getLong(0);
-                            filePath = c.getString(1);
-                            thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
-                            pfdInput = cr.openFileDescriptor(thumbUri, "r");
-                            bitmap = BitmapFactory.decodeFileDescriptor(
-                                    pfdInput.getFileDescriptor(), null, options);
-                            pfdInput.close();
-                        } catch (FileNotFoundException ex) {
-                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
-                        } catch (IOException ex) {
-                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
-                        } catch (OutOfMemoryError ex) {
-                            Log.e(TAG, "failed to allocate memory for thumbnail "
-                                    + thumbUri + "; " + ex);
-                        }
+                        bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
                     }
                 } else {
                     throw new IllegalArgumentException("Unsupported kind: " + kind);
@@ -354,7 +379,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 +694,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 +711,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 +1656,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 +1666,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 7433a79..1c52cf3 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();
 
@@ -2620,6 +2651,11 @@
         public static final String CHECKIN_INTERVAL = "checkin_interval";
 
         /**
+         * The interval (in seconds) between event log aggregation runs.
+         */
+        public static final String AGGREGATION_INTERVAL_SECONDS = "aggregation_interval_seconds";
+
+        /**
          * Boolean indicating if the market app should force market only checkins on
          * install/uninstall. Any non-0 value is considered true.
          */
@@ -2809,6 +2845,11 @@
                 "gmail_wait_time_retry_uphill_op";
 
         /**
+         * Controls if Gmail should delay sending operations that have previously failed.
+         */
+        public static final String GMAIL_DELAY_BAD_OP = "gmail_delay_bad_op";
+
+        /**
          * Controls if the protocol buffer version of the protocol will use a multipart request for
          * attachment uploads. Value must be an integer where non-zero means true. Defaults to 0.
          */
@@ -3110,6 +3151,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.
          */
@@ -3248,6 +3296,12 @@
             "vending_carrier_cred_buf_ms";
 
         /**
+         * Whether to turn on performance logging in the Market client.
+         */
+        public static final String VENDING_LOG_PERFORMANCE =
+            "vending_log_perf";
+
+        /**
          * URL that points to the legal terms of service to display in Settings.
          * <p>
          * This should be a https URL. For a pretty user-friendly URL, use
@@ -3623,7 +3677,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 +3691,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:";
+
+        /**
          * The length of time in milli-seconds that automatic small adjustments to
          * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d8c5a53..6380a8d 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -28,10 +28,11 @@
 import android.net.Uri;
 import android.telephony.SmsMessage;
 import android.text.TextUtils;
-import android.text.util.Regex;
 import android.util.Config;
 import android.util.Log;
 
+import com.android.common.Patterns;
+
 import java.util.HashSet;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -152,6 +153,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 +250,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 +273,7 @@
                 return false;
             }
 
-            ContentValues values = new ContentValues(2);
+            ContentValues values = new ContentValues(3);
 
             values.put(TYPE, folder);
             if (markAsUnread) {
@@ -274,6 +281,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);
@@ -1283,7 +1291,7 @@
             }
 
             String s = extractAddrSpec(address);
-            Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(s);
+            Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
             return match.matches();
         }
 
@@ -1298,7 +1306,7 @@
                 return false;
             }
 
-            Matcher match = Regex.PHONE_PATTERN.matcher(number);
+            Matcher match = Patterns.PHONE.matcher(number);
             return match.matches();
         }
 
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 809e230..3bb8a18 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/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index ce25c47..7f87365 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -25,6 +25,8 @@
 import android.webkit.WebView;
 import android.widget.TextView;
 
+import com.android.common.Patterns;
+
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.ArrayList;
@@ -133,7 +135,7 @@
      */
     public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
         public final String transformUrl(final Matcher match, String url) {
-            return Regex.digitsAndPlusOnly(match);
+            return Patterns.digitsAndPlusOnly(match);
         }
     };
 
@@ -207,19 +209,19 @@
         ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
 
         if ((mask & WEB_URLS) != 0) {
-            gatherLinks(links, text, Regex.WEB_URL_PATTERN,
+            gatherLinks(links, text, Patterns.WEB_URL,
                 new String[] { "http://", "https://", "rtsp://" },
                 sUrlMatchFilter, null);
         }
 
         if ((mask & EMAIL_ADDRESSES) != 0) {
-            gatherLinks(links, text, Regex.EMAIL_ADDRESS_PATTERN,
+            gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
                 new String[] { "mailto:" },
                 null, null);
         }
 
         if ((mask & PHONE_NUMBERS) != 0) {
-            gatherLinks(links, text, Regex.PHONE_PATTERN,
+            gatherLinks(links, text, Patterns.PHONE,
                 new String[] { "tel:" },
                 sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
         }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index bef3e58..ed2139d 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..e6e26fa 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.
@@ -480,6 +541,7 @@
                                               String method,
                                               HashMap headers,
                                               byte[] postData,
+                                              long postDataIdentifier,
                                               int cacheMode,
                                               boolean synchronous) {
         PerfChecker checker = new PerfChecker();
@@ -551,8 +613,9 @@
         }
 
         // Create a LoadListener
-        LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url,
-                loaderHandle, synchronous, isMainFramePage);
+        LoadListener loadListener = LoadListener.getLoadListener(mContext,
+                this, url, loaderHandle, synchronous, isMainFramePage,
+                postDataIdentifier);
 
         mCallbackProxy.onLoadResource(url);
 
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/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 75028de..c167414 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -74,6 +74,11 @@
     // Flag to clear the cache when the CacheManager is initialized
     private static boolean mClearCacheOnInit = false;
 
+    /**
+     * This class represents a resource retrieved from the HTTP cache.
+     * Instances of this class can be obtained by invoking the
+     * CacheManager.getCacheFile() method.
+     */
     public static class CacheResult {
         // these fields are saved to the database
         int httpStatusCode;
@@ -283,16 +288,24 @@
     // only called from WebCore thread
     public static CacheResult getCacheFile(String url,
             Map<String, String> headers) {
+        return getCacheFile(url, 0, headers);
+    }
+
+    // only called from WebCore thread
+    static CacheResult getCacheFile(String url, long postIdentifier,
+            Map<String, String> headers) {
         if (mDisabled) {
             return null;
         }
 
-        CacheResult result = mDataBase.getCache(url);
+        String databaseKey = getDatabaseKey(url, postIdentifier);
+
+        CacheResult result = mDataBase.getCache(databaseKey);
         if (result != null) {
             if (result.contentLength == 0) {
                 if (!checkCacheRedirect(result.httpStatusCode)) {
                     // this should not happen. If it does, remove it.
-                    mDataBase.removeCache(url);
+                    mDataBase.removeCache(databaseKey);
                     return null;
                 }
             } else {
@@ -304,7 +317,7 @@
                 } catch (FileNotFoundException e) {
                     // the files in the cache directory can be removed by the
                     // system. If it is gone, clean up the database
-                    mDataBase.removeCache(url);
+                    mDataBase.removeCache(databaseKey);
                     return null;
                 }
             }
@@ -352,14 +365,24 @@
     // only called from WebCore thread
     public static CacheResult createCacheFile(String url, int statusCode,
             Headers headers, String mimeType, boolean forceCache) {
+        return createCacheFile(url, statusCode, headers, mimeType, 0,
+                forceCache);
+    }
+
+    // only called from WebCore thread
+    static CacheResult createCacheFile(String url, int statusCode,
+            Headers headers, String mimeType, long postIdentifier,
+            boolean forceCache) {
         if (!forceCache && mDisabled) {
             return null;
         }
 
+        String databaseKey = getDatabaseKey(url, postIdentifier);
+
         // according to the rfc 2616, the 303 response MUST NOT be cached.
         if (statusCode == 303) {
             // remove the saved cache if there is any
-            mDataBase.removeCache(url);
+            mDataBase.removeCache(databaseKey);
             return null;
         }
 
@@ -367,7 +390,7 @@
         // header.
         if (checkCacheRedirect(statusCode) && !headers.getSetCookie().isEmpty()) {
             // remove the saved cache if there is any
-            mDataBase.removeCache(url);
+            mDataBase.removeCache(databaseKey);
             return null;
         }
 
@@ -375,9 +398,9 @@
         if (ret == null) {
             // this should only happen if the headers has "no-store" in the
             // cache-control. remove the saved cache if there is any
-            mDataBase.removeCache(url);
+            mDataBase.removeCache(databaseKey);
         } else {
-            setupFiles(url, ret);
+            setupFiles(databaseKey, ret);
             try {
                 ret.outStream = new FileOutputStream(ret.outFile);
             } catch (FileNotFoundException e) {
@@ -408,6 +431,12 @@
      */
     // only called from WebCore thread
     public static void saveCacheFile(String url, CacheResult cacheRet) {
+        saveCacheFile(url, 0, cacheRet);
+    }
+
+    // only called from WebCore thread
+    static void saveCacheFile(String url, long postIdentifier,
+            CacheResult cacheRet) {
         try {
             cacheRet.outStream.close();
         } catch (IOException e) {
@@ -434,7 +463,7 @@
             return;
         }
 
-        mDataBase.addCache(url, cacheRet);
+        mDataBase.addCache(getDatabaseKey(url, postIdentifier), cacheRet);
 
         if (DebugFlags.CACHE_MANAGER) {
             Log.v(LOGTAG, "saveCacheFile for url " + url);
@@ -513,6 +542,11 @@
         }
     }
 
+    private static String getDatabaseKey(String url, long postIdentifier) {
+        if (postIdentifier == 0) return url;
+        return postIdentifier + url;
+    }
+
     @SuppressWarnings("deprecation")
     private static void setupFiles(String url, CacheResult cacheRet) {
         if (true) {
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index f760b61..4a8e758 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.
      */
@@ -238,8 +249,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;
                 
@@ -660,6 +673,12 @@
                     mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
                 }
                 break;
+
+            case OPEN_FILE_CHOOSER:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+                }
+                break;
         }
     }
 
@@ -758,11 +777,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
@@ -1000,10 +1014,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;
@@ -1335,4 +1349,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/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index c1eeb3b..c3dac6e 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -242,7 +242,7 @@
             // to load POST content in a history navigation.
             case WebSettings.LOAD_CACHE_ONLY: {
                 CacheResult result = CacheManager.getCacheFile(mListener.url(),
-                        null);
+                        mListener.postIdentifier(), null);
                 if (result != null) {
                     startCacheLoad(result);
                 } else {
@@ -270,7 +270,7 @@
                 // Get the cache file name for the current URL, passing null for
                 // the validation headers causes no validation to occur
                 CacheResult result = CacheManager.getCacheFile(mListener.url(),
-                        null);
+                        mListener.postIdentifier(), null);
                 if (result != null) {
                     startCacheLoad(result);
                     return true;
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 64a9d9b..d12d828 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -26,8 +26,22 @@
 
 
 /**
- * Implements the Java side of GeolocationPermissions. Simply marshalls calls
- * from the UI thread to the WebKit thread.
+ * This class is used to get Geolocation permissions from, and set them on the
+ * WebView. For example, it could be used to allow a user to manage Geolocation
+ * permissions from a browser's UI.
+ *
+ * Permissions are managed on a per-origin basis, as required by the
+ * Geolocation spec - http://dev.w3.org/geo/api/spec-source.html. An origin
+ * specifies the scheme, host and port of particular frame. An origin is
+ * represented here as a string, using the output of
+ * WebCore::SecurityOrigin::toString.
+ *
+ * This class is the Java counterpart of the WebKit C++ GeolocationPermissions
+ * class. It simply marshalls calls from the UI thread to the WebKit thread.
+ *
+ * Within WebKit, Geolocation permissions may be applied either temporarily
+ * (for the duration of the page) or permanently. This class deals only with
+ * permanent permissions.
  */
 public final class GeolocationPermissions {
     /**
@@ -92,8 +106,8 @@
                     switch (msg.what) {
                         case RETURN_ORIGINS: {
                             Map values = (Map) msg.obj;
-                            Set origins = (Set) values.get(ORIGINS);
-                            ValueCallback<Set> callback = (ValueCallback<Set>) values.get(CALLBACK);
+                            Set<String> origins = (Set<String>) values.get(ORIGINS);
+                            ValueCallback<Set<String> > callback = (ValueCallback<Set<String> >) values.get(CALLBACK);
                             callback.onReceiveValue(origins);
                         } break;
                         case RETURN_ALLOWED: {
@@ -122,10 +136,9 @@
                         case GET_ORIGINS: {
                             getOriginsImpl();
                             ValueCallback callback = (ValueCallback) msg.obj;
-                            Set origins = new HashSet(mOrigins);
                             Map values = new HashMap<String, Object>();
                             values.put(CALLBACK, callback);
-                            values.put(ORIGINS, origins);
+                            values.put(ORIGINS, mOrigins);
                             postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
                             } break;
                         case GET_ALLOWED: {
@@ -185,15 +198,17 @@
      * Gets the set of origins for which Geolocation permissions are stored.
      * Note that we represent the origins as strings. These are created using
      * WebCore::SecurityOrigin::toString(). As long as all 'HTML 5 modules'
-     * (Database, Geolocation etc) do so, it's safe to match up origins for the
-     * purposes of displaying UI.
+     * (Database, Geolocation etc) do so, it's safe to match up origins based
+     * on this string.
+     *
+     * Callback is a ValueCallback object whose onReceiveValue method will be
+     * called asynchronously with the set of origins.
      */
-    public void getOrigins(ValueCallback<Set> callback) {
+    public void getOrigins(ValueCallback<Set<String> > callback) {
         if (callback != null) {
             if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
                 getOriginsImpl();
-                Set origins = new HashSet(mOrigins);
-                callback.onReceiveValue(origins);
+                callback.onReceiveValue(mOrigins);
             } else {
                 postMessage(Message.obtain(null, GET_ORIGINS, callback));
             }
@@ -210,6 +225,9 @@
 
     /**
      * Gets the permission state for the specified origin.
+     *
+     * Callback is a ValueCallback object whose onReceiveValue method will be
+     * called asynchronously with the permission state for the origin.
      */
     public void getAllowed(String origin, ValueCallback<Boolean> callback) {
         if (callback == null) {
@@ -231,7 +249,7 @@
     }
 
     /**
-     * Helper method to get the permission state.
+     * Helper method to get the permission state for the specified origin.
      */
     private void getAllowedImpl(String origin) {
         // Called on the WebKit thread.
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..938df95 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;
@@ -104,6 +104,7 @@
     private SslError mSslError;
     private RequestHandle mRequestHandle;
     private RequestHandle mSslErrorRequestHandle;
+    private long     mPostIdentifier;
 
     // Request data. It is only valid when we are doing a load from the
     // cache. It is needed if the cache returns a redirect
@@ -123,13 +124,13 @@
     // Public functions
     // =========================================================================
 
-    public static LoadListener getLoadListener(
-            Context context, BrowserFrame frame, String url,
-            int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+    public static LoadListener getLoadListener(Context context,
+            BrowserFrame frame, String url, int nativeLoader,
+            boolean synchronous, boolean isMainPageLoader, long postIdentifier) {
 
         sNativeLoaderCount += 1;
-        return new LoadListener(
-            context, frame, url, nativeLoader, synchronous, isMainPageLoader);
+        return new LoadListener(context, frame, url, nativeLoader, synchronous,
+                isMainPageLoader, postIdentifier);
     }
 
     public static int getNativeLoaderCount() {
@@ -137,7 +138,8 @@
     }
 
     LoadListener(Context context, BrowserFrame frame, String url,
-            int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+            int nativeLoader, boolean synchronous, boolean isMainPageLoader,
+            long postIdentifier) {
         if (DebugFlags.LOAD_LISTENER) {
             Log.v(LOGTAG, "LoadListener constructor url=" + url);
         }
@@ -150,6 +152,7 @@
             mMessageQueue = new Vector<Message>();
         }
         mIsMainPageLoader = isMainPageLoader;
+        mPostIdentifier = postIdentifier;
     }
 
     /**
@@ -408,9 +411,14 @@
                 mStatusCode == HTTP_MOVED_PERMANENTLY ||
                 mStatusCode == HTTP_TEMPORARY_REDIRECT) && 
                 mNativeLoader != 0) {
-            if (!mFromCache && mRequestHandle != null) {
+            // for POST request, only cache the result if there is an identifier
+            // associated with it. postUrl() or form submission should set the
+            // identifier while XHR POST doesn't.
+            if (!mFromCache && mRequestHandle != null
+                    && (!mRequestHandle.getMethod().equals("POST")
+                            || mPostIdentifier != 0)) {
                 mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
-                        headers, mMimeType, false);
+                        headers, mMimeType, mPostIdentifier, false);
             }
             if (mCacheResult != null) {
                 mCacheResult.encoding = mEncoding;
@@ -522,17 +530,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();
@@ -636,7 +645,7 @@
      */
     boolean checkCache(Map<String, String> headers) {
         // Get the cache file name for the current URL
-        CacheResult result = CacheManager.getCacheFile(url(),
+        CacheResult result = CacheManager.getCacheFile(url(), mPostIdentifier,
                 headers);
 
         // Go ahead and set the cache loader to null in case the result is
@@ -861,6 +870,10 @@
         }
     }
 
+    long postIdentifier() {
+        return mPostIdentifier;
+    }
+
     void attachRequestHandle(RequestHandle requestHandle) {
         if (DebugFlags.LOAD_LISTENER) {
             Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
@@ -907,8 +920,9 @@
      * be used. This is just for forward/back navigation to a POST
      * URL.
      */
-    static boolean willLoadFromCache(String url) {
-        boolean inCache = CacheManager.getCacheFile(url, null) != null;
+    static boolean willLoadFromCache(String url, long identifier) {
+        boolean inCache =
+                CacheManager.getCacheFile(url, identifier, null) != null;
         if (DebugFlags.LOAD_LISTENER) {
             Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " + 
                     inCache);
@@ -1009,28 +1023,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 +1067,7 @@
                 }
                 nativeAddData(c.mArray, c.mLength);
             }
-            mDataBuilder.releaseChunk(c);
+            c.release();
             checker.responseAlert("res nativeAddData");
         }
     }
@@ -1059,7 +1079,7 @@
     void tearDown() {
         if (mCacheResult != null) {
             if (getErrorID() == OK) {
-                CacheManager.saveCacheFile(mUrl, mCacheResult);
+                CacheManager.saveCacheFile(mUrl, mPostIdentifier, mCacheResult);
             }
 
             // we need to reset mCacheResult to be null
@@ -1187,7 +1207,8 @@
             // Cache the redirect response
             if (mCacheResult != null) {
                 if (getErrorID() == OK) {
-                    CacheManager.saveCacheFile(mUrl, mCacheResult);
+                    CacheManager.saveCacheFile(mUrl, mPostIdentifier,
+                            mCacheResult);
                 }
                 mCacheResult = null;
             }
@@ -1210,8 +1231,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/PluginUtil.java b/core/java/android/webkit/PluginUtil.java
index 8fdbd67..33ccf9d 100644
--- a/core/java/android/webkit/PluginUtil.java
+++ b/core/java/android/webkit/PluginUtil.java
@@ -19,9 +19,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.util.Log;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
 class PluginUtil {
 
     private static final String LOGTAG = "PluginUtil";
@@ -35,12 +32,7 @@
     static PluginStub getPluginStub(Context context, String packageName, 
             String className) {
         try {
-            Context pluginContext = context.createPackageContext(packageName,
-                    Context.CONTEXT_INCLUDE_CODE |
-                    Context.CONTEXT_IGNORE_SECURITY);
-            ClassLoader pluginCL = pluginContext.getClassLoader();
-
-            Class<?> stubClass = pluginCL.loadClass(className);
+            Class<?> stubClass = getPluginClass(context, packageName, className);
             Object stubObject = stubClass.newInstance();
 
             if (stubObject instanceof PluginStub) {
@@ -56,4 +48,14 @@
         }
         return null;
     }
+    
+    /* package */
+    static Class<?> getPluginClass(Context context, String packageName,
+            String className) throws NameNotFoundException, ClassNotFoundException {
+        Context pluginContext = context.createPackageContext(packageName,
+                Context.CONTEXT_INCLUDE_CODE |
+                Context.CONTEXT_IGNORE_SECURITY);
+        ClassLoader pluginCL = pluginContext.getClassLoader();
+        return pluginCL.loadClass(className);
+    }
 }
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 92676aa..dd494e1 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;
 
@@ -265,20 +266,25 @@
     public void addMessageToConsole(String message, int lineNumber, String sourceID) {}
 
     /**
-     * Ask the host application for an icon to represent a <video> element.
-     * This icon will be used if the Web page did not specify a poster attribute.
+     * When not playing, video elements are represented by a 'poster' image. The
+     * image to use can be specified by the poster attribute of the video tag in
+     * HTML. If the attribute is absent, then a default poster will be used. This
+     * method allows the ChromeClient to provide that default image.
      *
-     * @return Bitmap The icon or null if no such icon is available.
+     * @return Bitmap The image to use as a default poster, or null if no such image is
+     * available.
      */
     public Bitmap getDefaultVideoPoster() {
         return null;
     }
 
     /**
-     * Ask the host application for a custom progress view to show while
-     * a <video> is loading.
+     * When the user starts to playback a video element, it may take time for enough
+     * data to be buffered before the first frames can be rendered. While this buffering
+     * is taking place, the ChromeClient can use this function to provide a View to be
+     * displayed. For example, the ChromeClient could show a spinner animation.
      *
-     * @return View The progress view.
+     * @return View The View to be displayed whilst the video is loading.
      */
     public View getVideoLoadingProgressView() {
         return null;
@@ -289,4 +295,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 6f3262a..8e40b23 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1002,7 +1002,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 691fa77..1899a6e 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;
@@ -491,6 +504,16 @@
         "REQUEST_KEYBOARD" //                = 27;
     };
 
+    // If the site doesn't use the viewport meta tag to specify the viewport,
+    // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
+    static final int DEFAULT_VIEWPORT_WIDTH = 800;
+
+    // normally we try to fit the content to the minimum preferred width
+    // calculated by the Webkit. To avoid the bad behavior when some site's
+    // minimum preferred width keeps growing when changing the viewport width or
+    // the minimum preferred width is huge, an upper limit is needed.
+    static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
+
     // default scale limit. Depending on the display density
     private static float DEFAULT_MAX_ZOOM_SCALE;
     private static float DEFAULT_MIN_ZOOM_SCALE;
@@ -510,7 +533,7 @@
 
     // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
     // engadget always have wider mContentWidth no matter what viewport size is.
-    int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH;
+    int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
     float mLastScale;
 
     // default scale. Depending on the display density.
@@ -535,11 +558,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
@@ -2350,6 +2372,7 @@
         }
         int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
         invalidate();
+        mLastFind = find;
         return result;
     }
 
@@ -2357,6 +2380,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
@@ -2412,12 +2438,14 @@
      * Clear the highlighting surrounding text matches created by findAll.
      */
     public void clearMatches() {
+        if (mNativeClass == 0)
+            return;
         if (mFindIsUp) {
             recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
                     false);
             mFindIsUp = false;
         }
-        nativeSetFindIsDown();
+        nativeSetFindIsUp();
         // Now that the dialog has been removed, ensure that we scroll to a
         // location that is not beyond the end of the page.
         pinScrollTo(mScrollX, mScrollY, false, 0);
@@ -2531,6 +2559,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
@@ -2565,8 +2628,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;
@@ -2607,12 +2680,12 @@
 
         if (mHeightCanMeasure) {
             if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
-                    && updateLayout) {
+                    || updateLayout) {
                 requestLayout();
             }
         } else if (mWidthCanMeasure) {
             if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
-                    && updateLayout) {
+                    || updateLayout) {
                 requestLayout();
             }
         } else {
@@ -2632,6 +2705,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.
@@ -2812,10 +2895,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()) {
@@ -2838,6 +2918,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) {
@@ -2847,8 +2942,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);
@@ -2865,19 +2974,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()) {
@@ -2929,11 +3032,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) {
@@ -2953,6 +3057,10 @@
         if (mFindIsUp && !animateScroll) {
             nativeDrawMatches(canvas);
         }
+        if (mFocusSizeChanged) {
+            mFocusSizeChanged = false;
+            didUpdateTextViewBounds(true);
+        }
     }
 
     // draw history
@@ -3048,6 +3156,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,
@@ -3103,6 +3221,7 @@
                     && nativeTextGeneration() == mTextGeneration) {
                 mWebTextView.setTextAndKeepSelection(text);
             } else {
+                // FIXME: Determine whether this is necessary.
                 Selection.setSelection(spannable, start, end);
             }
         } else {
@@ -3135,34 +3254,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();
         }
     }
@@ -3229,23 +3326,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;
@@ -3257,6 +3353,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);
@@ -3306,10 +3405,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(),
@@ -3318,13 +3414,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);
             }
         }
 
@@ -3389,7 +3489,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
@@ -3399,21 +3505,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;
         }
 
@@ -3429,14 +3537,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() {
@@ -3454,45 +3577,40 @@
             mExtendSelection = false;
         }
         mShiftIsPressed = false;
+        invalidate(); // remove selection region and pointer
         if (mTouchMode == TOUCH_SELECT_MODE) {
             mTouchMode = TOUCH_INIT_MODE;
         }
         return copiedSomething;
     }
 
-    // Set this as a hierarchy change listener so we can know when this view
-    // is removed and still have access to our parent.
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        ViewParent parent = getParent();
-        if (parent instanceof ViewGroup) {
-            ViewGroup p = (ViewGroup) parent;
-            p.setOnHierarchyChangeListener(this);
-        }
+        if (hasWindowFocus()) onWindowFocusChanged(true);
     }
 
     @Override
     protected void onDetachedFromWindow() {
+        clearTextEntry();
         super.onDetachedFromWindow();
-        ViewParent parent = getParent();
-        if (parent instanceof ViewGroup) {
-            ViewGroup p = (ViewGroup) parent;
-            p.setOnHierarchyChangeListener(null);
-        }
-
         // Clean up the zoom controller
         mZoomButtonsController.setVisible(false);
     }
 
-    // Implementation for OnHierarchyChangeListener
+    /**
+     * @deprecated WebView no longer needs to implement
+     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
+     */
+    @Deprecated
     public void onChildViewAdded(View parent, View child) {}
 
-    public void onChildViewRemoved(View p, View child) {
-        if (child == this) {
-            clearTextEntry();
-        }
-    }
+    /**
+     * @deprecated WebView no longer needs to implement
+     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
+     */
+    @Deprecated
+    public void onChildViewRemoved(View p, View child) {}
 
     /**
      * @deprecated WebView should not have implemented
@@ -3597,6 +3715,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);
@@ -3606,6 +3742,14 @@
             mZoomCenterY = getViewHeight() * .5f;
         }
 
+        // adjust the max viewport width depending on the view dimensions. This
+        // is to ensure the scaling is not going insane. So do not shrink it if
+        // the view size is temporarily smaller, e.g. when soft keyboard is up.
+        int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE);
+        if (newMaxViewportWidth > sMaxViewportWidth) {
+            sMaxViewportWidth = newMaxViewportWidth;
+        }
+
         // update mMinZoomScale if the minimum zoom scale is not fixed
         if (!mMinZoomScaleFixed) {
             // when change from narrow screen to wide screen, the new viewWidth
@@ -3712,8 +3856,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: {
@@ -3735,6 +3881,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) {
@@ -3839,12 +3986,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);
@@ -3856,56 +4012,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;
@@ -3924,13 +4071,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;
             }
@@ -3983,6 +4134,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
@@ -4022,6 +4176,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;
@@ -4048,6 +4205,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;
@@ -4106,6 +4264,7 @@
                     commitCopy();
                 } else {
                     mExtendSelection = true;
+                    invalidate(); // draw the i-beam instead of the arrow
                 }
                 return true; // discard press if copy in progress
             }
@@ -4153,8 +4312,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
@@ -4236,8 +4395,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;
         }
@@ -4251,8 +4413,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);
@@ -4327,7 +4489,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;
@@ -4520,7 +4682,9 @@
         switchOutDrawHistory();
         float scale = mActualScale * 0.8f;
         if (scale < (mMinZoomScale + 0.1f)
-                && mWebViewCore.getSettings().getUseWideViewPort()) {
+                && mWebViewCore.getSettings().getUseWideViewPort()
+                && mZoomOverviewWidth > Math.ceil(getViewWidth()
+                        * mInvActualScale)) {
             // when zoom out to min scale, switch to overview mode
             doDoubleTap();
             return true;
@@ -4607,21 +4771,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);
         }
     }
 
@@ -4667,9 +4831,15 @@
         }
         settings.setDoubleTapToastCount(0);
         if (mInZoomOverview) {
-            // Force the titlebar fully reveal in overview mode
-            if (mScrollY < getTitleHeight()) mScrollY = 0;
-            zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth);
+            float newScale = (float) getViewWidth() / mZoomOverviewWidth;
+            if (Math.abs(mActualScale - newScale) < 0.01f) {
+                // reset mInZoomOverview to false if scale doesn't change
+                mInZoomOverview = false;
+            } else {
+                // Force the titlebar fully reveal in overview mode
+                if (mScrollY < getTitleHeight()) mScrollY = 0;
+                zoomWithPreview(newScale);
+            }
         } else {
             // mLastTouchX and mLastTouchY are the point in the current viewport
             int contentX = viewToContentX((int) mLastTouchX + mScrollX);
@@ -4691,15 +4861,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;
@@ -4848,14 +5009,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;
@@ -4886,9 +5039,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) {
@@ -5037,8 +5191,11 @@
                         mPictureListener.onNewPicture(WebView.this, capturePicture());
                     }
                     if (useWideViewport) {
-                        mZoomOverviewWidth = Math.max(draw.mMinPrefWidth,
-                                draw.mViewPoint.x);
+                        // limit mZoomOverviewWidth to sMaxViewportWidth so that
+                        // if the page doesn't behave well, the WebView won't go
+                        // insane.
+                        mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
+                                .max(draw.mMinPrefWidth, draw.mViewPoint.x));
                     }
                     if (!mMinZoomScaleFixed) {
                         mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5052,6 +5209,9 @@
                                     / mZoomOverviewWidth, false);
                         }
                     }
+                    if (draw.mFocusSizeChanged && inEditingMode()) {
+                        mFocusSizeChanged = true;
+                    }
                     break;
                 }
                 case WEBCORE_INITIALIZED_MSG_ID:
@@ -5092,9 +5252,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
@@ -5178,9 +5336,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;
@@ -5204,8 +5389,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() {
@@ -5226,6 +5419,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
@@ -5260,12 +5501,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;
 
@@ -5279,8 +5519,7 @@
             }
         }
 
-        private InvokeListBox(String[] array, boolean[] enabled, int
-                selection) {
+        private InvokeListBox(String[] array, int[] enabled, int selection) {
             mSelection = selection;
             mMultiple = false;
 
@@ -5411,10 +5650,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));
@@ -5425,10 +5665,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));
     }
@@ -5512,7 +5753,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
@@ -5587,6 +5828,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.
      */
@@ -5604,7 +5856,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();
@@ -5617,13 +5869,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();
@@ -5633,6 +5887,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();
@@ -5650,13 +5905,12 @@
     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,
             boolean pressed, boolean invalidate);
     private native void     nativeSelectBestAt(Rect rect);
-    private native void     nativeSetFindIsDown();
+    private native void     nativeSetFindIsUp();
     private native void     nativeSetFollowedLink(boolean followed);
     private native void     nativeSetHeightCanMeasure(boolean measure);
     // Returns a value corresponding to CachedFrame::ImeAction
@@ -5672,7 +5926,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..769f0b1 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
 import android.graphics.Canvas;
 import android.graphics.DrawFilter;
 import android.graphics.Paint;
@@ -26,11 +28,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;
@@ -120,10 +124,6 @@
     private int mWebkitScrollX = 0;
     private int mWebkitScrollY = 0;
 
-    // If the site doesn't use viewport meta tag to specify the viewport, use
-    // DEFAULT_VIEWPORT_WIDTH as default viewport width
-    static final int DEFAULT_VIEWPORT_WIDTH = 800;
-
     // The thread name used to identify the WebCore thread and for use in
     // debugging other classes that require operation within the WebCore thread.
     /* package */ static final String THREAD_NAME = "WebViewCoreThread";
@@ -273,6 +273,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 +455,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.
@@ -471,6 +506,7 @@
             int framePtr, int x, int y);
 
     private native String nativeRetrieveHref(int framePtr, int nodePtr);
+    private native String nativeRetrieveAnchorText(int framePtr, int nodePtr);
 
     private native void nativeTouchUp(int touchGeneration,
             int framePtr, int nodePtr, int x, int y);
@@ -522,8 +558,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 +712,6 @@
         int mY;
     }
 
-    static class PluginStateData {
-        int mFrame;
-        int mNode;
-        int mState;
-    }
-
     static class GeolocationPermissionsData {
         String mOrigin;
         boolean mAllow;
@@ -720,7 +748,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 +799,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 +1058,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 " +
@@ -1139,8 +1161,10 @@
 
                         case REQUEST_CURSOR_HREF: {
                             Message hrefMsg = (Message) msg.obj;
-                            String res = nativeRetrieveHref(msg.arg1, msg.arg2);
-                            hrefMsg.getData().putString("url", res);
+                            hrefMsg.getData().putString("url",
+                                    nativeRetrieveHref(msg.arg1, msg.arg2));
+                            hrefMsg.getData().putString("title",
+                                    nativeRetrieveAnchorText(msg.arg1, msg.arg2));
                             hrefMsg.sendToTarget();
                             break;
                         }
@@ -1497,7 +1521,7 @@
             if (mViewportWidth == -1) {
                 if (mSettings.getLayoutAlgorithm() ==
                         WebSettings.LayoutAlgorithm.NORMAL) {
-                    width = DEFAULT_VIEWPORT_WIDTH;
+                    width = WebView.DEFAULT_VIEWPORT_WIDTH;
                 } else {
                     /*
                      * if a page's minimum preferred width is wider than the
@@ -1511,8 +1535,9 @@
                      * In the worse case, the native width will be adjusted when
                      * next zoom or screen orientation change happens.
                      */
-                    width = Math.max(w, Math.max(DEFAULT_VIEWPORT_WIDTH,
-                            nativeGetContentMinPrefWidth()));
+                    width = Math.min(WebView.sMaxViewportWidth, Math.max(w,
+                            Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
+                                    nativeGetContentMinPrefWidth())));
                 }
             } else {
                 width = Math.max(w, mViewportWidth);
@@ -1593,6 +1618,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,10 +1633,11 @@
         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(
-                        mViewportWidth == -1 ? DEFAULT_VIEWPORT_WIDTH
+                        mViewportWidth == -1 ? WebView.DEFAULT_VIEWPORT_WIDTH
                                 : (mViewportWidth == 0 ? mCurrentViewWidth
                                         : mViewportWidth),
                         nativeGetContentMinPrefWidth());
@@ -1643,6 +1670,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 +1681,7 @@
         if (animatingZoom) {
             df = mZoomFilter;
         } else if (animatingScroll) {
-            df = null;
+            df = mScrollFilter;
         }
         canvas.setDrawFilter(df);
         boolean tookTooLong = nativeDrawContent(canvas, color);
@@ -2013,9 +2043,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 +2123,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 +2143,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 +2151,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);
@@ -2124,6 +2168,32 @@
         }
     }
 
+    // called by JNI
+    private Class<?> getPluginClass(String libName, String clsName) {
+        
+        if (mWebView == null) {
+            return null;
+        }
+        
+        String pkgName = PluginManager.getInstance(null).getPluginsAPKName(libName);
+        if (pkgName == null) {
+            Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
+            return null;
+        }
+        
+        Class<?> pluginClass = null;
+        try {
+            pluginClass = PluginUtil.getPluginClass(mWebView.getContext(), pkgName, clsName);
+        } catch (NameNotFoundException e) {
+            Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")");
+        } catch (ClassNotFoundException e) {
+            Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
+                    ") in the apk (" + pkgName + ")");
+        }
+
+        return pluginClass;
+    }
+    
     // called by JNI. PluginWidget function to launch an activity and overlays
     // the activity with the View provided by the plugin class.
     private void startFullScreenPluginActivity(String libName, String clsName, int npp) {
@@ -2173,6 +2243,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 271989a..e353501 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3563,6 +3563,7 @@
             // into the scrap heap
             int viewType = lp.viewType;
             if (!shouldRecycleViewType(viewType)) {
+                removeDetachedView(scrap, false);
                 return;
             }
 
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 75d0f31..ce985e3 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -1027,7 +1027,6 @@
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
-        performValidation();
         if (!hasWindowFocus && !mDropDownAlwaysVisible) {
             dismissDropDown();
         }
@@ -1036,7 +1035,10 @@
     @Override
     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
-        performValidation();
+        // Perform validation if the view is losing focus.
+        if (!focused) {
+            performValidation();
+        }
         if (!focused && !mDropDownAlwaysVisible) {
             dismissDropDown();
         }
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/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
new file mode 100644
index 0000000..d067926
--- /dev/null
+++ b/core/java/com/android/internal/os/IDropBoxManagerService.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.DropBoxManager;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the
+ * DropBoxManagerService that actually implements the drop box functionality.
+ *
+ * @see DropBoxManager
+ * @hide
+ */
+interface IDropBoxManagerService {
+    /**
+     * @see DropBoxManager#addText
+     * @see DropBoxManager#addData
+     * @see DropBoxManager#addFile
+     */
+    void add(in DropBoxManager.Entry entry);
+
+    /** @see DropBoxManager#getNextEntry */
+    boolean isTagEnabled(String tag);
+
+    /** @see DropBoxManager#getNextEntry */
+    DropBoxManager.Entry getNextEntry(String tag, long millis);
+}
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 2a1f23a..1f754bc 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -424,6 +424,9 @@
                     // faster.
                     if ("text/plain".equals(type) || "application/smil".equals(type)) {
                         String text = c.getString(PART_COLUMN_TEXT);
+                        if (text == null) {
+                            text = "";
+                        }
                         byte [] blob = new EncodedStringValue(text).getTextString();
                         baos.write(blob, 0, blob.length);
                     } else {
@@ -858,7 +861,7 @@
         } else {
             values.put(Mms.SUBJECT, "");
         }
-        
+
         long messageSize = sendReq.getMessageSize();
         if (messageSize > 0) {
             values.put(Mms.MESSAGE_SIZE, messageSize);
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c3cf3c..22716b8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -515,7 +515,7 @@
 
     <!-- Allows an application to modify the Google service map. -->
     <permission android:name="android.permission.WRITE_GSERVICES"
-        android:protectionLevel="signature"
+        android:protectionLevel="signatureOrSystem"
         android:label="@string/permlab_writeGservices"
         android:description="@string/permdesc_writeGservices" />
 
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 e93e8ff..6b5b168 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -353,7 +353,7 @@
     <string name="permdesc_useCredentials" msgid="7416570544619546974">"Umožňuje aplikaci požadovat ověřovací tokeny."</string>
     <string name="permlab_accessNetworkState" msgid="6865575199464405769">"zobrazení stavu sítě"</string>
     <string name="permdesc_accessNetworkState" msgid="558721128707712766">"Umožňuje aplikaci zobrazit stav všech sítí."</string>
-    <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"plný přístup k internetu"</string>
+    <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"plný přístup k Internetu"</string>
     <string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Umožňuje aplikaci vytvořit síťové sokety."</string>
     <string name="permlab_writeApnSettings" msgid="7823599210086622545">"zápis nastavení názvu přístupového bodu (APN)"</string>
     <string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Umožňuje aplikaci změnit nastavení APN, jako je například proxy či port APN."</string>
@@ -387,7 +387,7 @@
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Umožní aplikaci číst soukromá slova, jména a fráze, která uživatel mohl uložit do svého slovníku."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"zápis do slovníku definovaného uživatelem"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Umožní aplikaci zapisovat nová slova do uživatelského slovníku."</string>
-    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"změna/smazání obsahu karty SD"</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"změna/smazání obsah karty SD"</string>
     <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Umožní aplikaci zápis na kartu SD."</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Domů"</item>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Přihlásit se"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Neplatné uživatelské jméno nebo heslo."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Probíhá kontrola..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Odemknout"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Zapnout zvuk"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Vypnout zvuk"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Vymazat"</string>
@@ -780,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>
@@ -795,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 11ef0a9..33c21e3 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -33,7 +33,7 @@
     <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten blev aktiveret for:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten er deaktiveret."</string>
     <string name="serviceRegistered" msgid="6275019082598102493">"Registreringen er afsluttet."</string>
-    <string name="serviceErased" msgid="1288584695297200972">"Sletningen er fuldført."</string>
+    <string name="serviceErased" msgid="1288584695297200972">"Sletning afsluttet."</string>
     <string name="passwordIncorrect" msgid="7612208839450128715">"Forkert adgangskode."</string>
     <string name="mmiComplete" msgid="8232527495411698359">"MMI-nummer afsluttet."</string>
     <string name="badPin" msgid="5085454289896032547">"Den gamle PIN-kode, du indtastede, er ikke korrekt."</string>
@@ -49,11 +49,11 @@
     <string name="BaMmi" msgid="455193067926770581">"Opkaldsspærring"</string>
     <string name="PwdMmi" msgid="7043715687905254199">"Ændring af adgangskode"</string>
     <string name="PinMmi" msgid="3113117780361190304">"ændring af PIN-kode"</string>
-    <string name="CnipMmi" msgid="3110534680557857162">"Opkaldsnummeret er til stede"</string>
-    <string name="CnirMmi" msgid="3062102121430548731">"Opkaldsnummeret er begrænset"</string>
+    <string name="CnipMmi" msgid="3110534680557857162">"Opkaldsnummer til stede"</string>
+    <string name="CnirMmi" msgid="3062102121430548731">"Opkaldsnummer begrænset"</string>
     <string name="ThreeWCMmi" msgid="9051047170321190368">"Trevejsopkald"</string>
     <string name="RuacMmi" msgid="7827887459138308886">"Afvisning af uønskede, irriterende opkald"</string>
-    <string name="CndMmi" msgid="3116446237081575808">"Levering af opkaldsnummer"</string>
+    <string name="CndMmi" msgid="3116446237081575808">"Opkaldsnummer levering"</string>
     <string name="DndMmi" msgid="1265478932418334331">"Forstyr ikke"</string>
     <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"Standarder for opkalds-id til begrænset. Næste opkald: Begrænset"</string>
     <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"Standarder for opkalds-id til begrænset. Næste opkald: Ikke begrænset"</string>
@@ -62,14 +62,14 @@
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"Tjenesten leveres ikke!"</string>
     <string name="CLIRPermanent" msgid="5460892159398802465">"Indstillingen for opkalds-id kan ikke ændres."</string>
     <string name="RestrictedChangedTitle" msgid="5592189398956187498">"Begrænset adgang ændret"</string>
-    <string name="RestrictedOnData" msgid="8653794784690065540">"Datatjenesten er blokeret."</string>
-    <string name="RestrictedOnEmergency" msgid="6581163779072833665">"Nødtjenesten er blokeret."</string>
-    <string name="RestrictedOnNormal" msgid="2045364908281990708">"Stemme-/sms-tjenesten er blokeret."</string>
-    <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle stemme-/sms-tjenester er blokerede."</string>
+    <string name="RestrictedOnData" msgid="8653794784690065540">"Datatjeneste er blokeret."</string>
+    <string name="RestrictedOnEmergency" msgid="6581163779072833665">"Nødtjeneste er blokeret."</string>
+    <string name="RestrictedOnNormal" msgid="2045364908281990708">"Stemme-/SMS-tjeneste er blokeret."</string>
+    <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle stemme-/SMS-tjenester er blokerede."</string>
     <string name="serviceClassVoice" msgid="1258393812335258019">"Stemme"</string>
     <string name="serviceClassData" msgid="872456782077937893">"Data"</string>
     <string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
-    <string name="serviceClassSMS" msgid="2015460373701527489">"sms"</string>
+    <string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
     <string name="serviceClassDataAsync" msgid="4523454783498551468">"Asynkroniser"</string>
     <string name="serviceClassDataSync" msgid="7530000519646054776">"Synkroniser"</string>
     <string name="serviceClassPacket" msgid="6991006557993423453">"Pakke"</string>
@@ -77,8 +77,8 @@
     <string name="roamingText0" msgid="7170335472198694945">"Roamingindikator til"</string>
     <string name="roamingText1" msgid="5314861519752538922">"Roamingindikator fra"</string>
     <string name="roamingText2" msgid="8969929049081268115">"Roamingindikator blinker"</string>
-    <string name="roamingText3" msgid="5148255027043943317">"Ikke i kvarteret"</string>
-    <string name="roamingText4" msgid="8808456682550796530">"Ikke i bygningen"</string>
+    <string name="roamingText3" msgid="5148255027043943317">"Ude af kvarteret"</string>
+    <string name="roamingText4" msgid="8808456682550796530">"Ude af bygningen"</string>
     <string name="roamingText5" msgid="7604063252850354350">"Roaming – Foretrukket system"</string>
     <string name="roamingText6" msgid="2059440825782871513">"Roaming – Tilgængeligt system"</string>
     <string name="roamingText7" msgid="7112078724097233605">"Roaming – Alliance Partner"</string>
@@ -88,12 +88,12 @@
     <string name="roamingText11" msgid="4154476854426920970">"Roamingbanner til"</string>
     <string name="roamingText12" msgid="1189071119992726320">"Roamingbanner fra"</string>
     <string name="roamingTextSearching" msgid="8360141885972279963">"Søger efter tjeneste"</string>
-    <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
+    <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke videresendt"</string>
     <string name="cfTemplateForwarded" msgid="1302922117498590521">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="9206251736527085256">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
-    <string name="cfTemplateRegistered" msgid="5073237827620166285">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
-    <string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
-    <string name="fcComplete" msgid="3118848230966886575">"Funktionskoden er komplet."</string>
+    <string name="cfTemplateRegistered" msgid="5073237827620166285">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke videresendt"</string>
+    <string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke videresendt"</string>
+    <string name="fcComplete" msgid="3118848230966886575">"Funktionskode komplet."</string>
     <string name="fcError" msgid="3327560126588500777">"Forbindelsesproblemer eller ugyldig funktionskode."</string>
     <string name="httpErrorOk" msgid="1191919378083472204">"OK"</string>
     <string name="httpError" msgid="2567300624552921790">"Websiden indeholder en fejl."</string>
@@ -124,29 +124,29 @@
     <string name="screen_lock" msgid="799094655496098153">"Skærmlås"</string>
     <string name="power_off" msgid="4266614107412865048">"Sluk"</string>
     <string name="shutdown_progress" msgid="2281079257329981203">"Lukker ned ..."</string>
-    <string name="shutdown_confirm" msgid="649792175242821353">"lydstyrke for opkald"</string>
+    <string name="shutdown_confirm" msgid="649792175242821353">"Din telefon lukkes ned."</string>
     <string name="no_recent_tasks" msgid="279702952298056674">"Der er ingen nye programmer."</string>
-    <string name="global_actions" msgid="2406416831541615258">"Indstillinger for telefon"</string>
+    <string name="global_actions" msgid="2406416831541615258">"Telefonvalgmuligheder"</string>
     <string name="global_action_lock" msgid="2844945191792119712">"Skærmlås"</string>
     <string name="global_action_power_off" msgid="4471879440839879722">"Sluk"</string>
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Lydløs"</string>
-    <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Lyden er slået FRA"</string>
+    <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Lyden er FRA"</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Lyden er TIL"</string>
     <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Flytilstand"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flytilstand er TIL"</string>
-    <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flytilstand er slået FRA"</string>
+    <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flytilstand er FRA"</string>
     <string name="safeMode" msgid="2788228061547930246">"Sikker tilstand"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android-system"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"Tjenester, der koster dig penge"</string>
-    <string name="permgroupdesc_costMoney" msgid="8193824940620517189">"Tillader, at et program kan gøre ting, som kan koste penge."</string>
+    <string name="permgroupdesc_costMoney" msgid="8193824940620517189">"Tillader et program at gøre ting, som kan koste penge."</string>
     <string name="permgrouplab_messages" msgid="7521249148445456662">"Dine beskeder"</string>
-    <string name="permgroupdesc_messages" msgid="7045736972019211994">"Læs og skriv dine sms-, e-mail- og andre beskeder."</string>
+    <string name="permgroupdesc_messages" msgid="7045736972019211994">"Læs og skriv dine SMS-, e-mail- og andre beskeder."</string>
     <string name="permgrouplab_personalInfo" msgid="3519163141070533474">"Dine personlige oplysninger"</string>
     <string name="permgroupdesc_personalInfo" msgid="5488050357388806068">"Få direkte adgang til dine kontakter og din kalender, der er gemt på telefonen."</string>
     <string name="permgrouplab_location" msgid="635149742436692049">"Din placering"</string>
     <string name="permgroupdesc_location" msgid="2430258821648348660">"Overvåg din fysiske placering"</string>
     <string name="permgrouplab_network" msgid="5808983377727109831">"Netværkskommunikation"</string>
-    <string name="permgroupdesc_network" msgid="5035763698958415998">"Tillader, at programmerne får adgang til forskellige netværksfunktioner."</string>
+    <string name="permgroupdesc_network" msgid="5035763698958415998">"Tillader programmer at få adgang til forskellige netværksfunktioner."</string>
     <string name="permgrouplab_accounts" msgid="3359646291125325519">"Dine konti"</string>
     <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Få adgang til de tilgængelige konti."</string>
     <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Hardwarekontroller"</string>
@@ -160,123 +160,123 @@
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Få adgang til SD-kortet."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"deaktiver eller rediger statuslinje"</string>
-    <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillader, at et program deaktiverer statuslinjen eller tilføjer eller fjerner systemikoner."</string>
+    <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillader et program at deaktivere statuslinjen eller tilføje eller fjerne systemikoner."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"udvid/skjul statuslinje"</string>
-    <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillader, at et program udvider eller skjuler statuslinjen."</string>
+    <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillader et program at udvide eller skjule statuslinjen."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"opfang udgående opkald"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"Tillader, at et program behandler udgående opkald og ændrer det nummer, der ringes til. Ondsindede programmer kan overvåge, omdirigere eller forhindre udgående opkald."</string>
-    <string name="permlab_receiveSms" msgid="2697628268086208535">"modtag sms"</string>
-    <string name="permdesc_receiveSms" msgid="6298292335965966117">"Tillader, at et program modtager og behandler sms-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
-    <string name="permlab_receiveMms" msgid="8894700916188083287">"modtag mms"</string>
-    <string name="permdesc_receiveMms" msgid="4563346832000174373">"Tillader, at et program modtager og behandler mms-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
-    <string name="permlab_sendSms" msgid="5600830612147671529">"send sms-beskeder"</string>
-    <string name="permdesc_sendSms" msgid="1946540351763502120">"Tillader, at et program at sender sms-beskeder. Ondsindede programmer kan eventuelt koste dig penge ved at sende beskeder uden din bekræftelse."</string>
-    <string name="permlab_readSms" msgid="4085333708122372256">"læs sms eller mms"</string>
-    <string name="permdesc_readSms" msgid="3002170087197294591">"Tillader, at et program læser sms-beskeder, der er gemt på din telefon eller dit SIM-kort. Ondsindede programmer kan eventuelt læse dine fortrolige beskeder."</string>
-    <string name="permlab_writeSms" msgid="6881122575154940744">"rediger sms eller mms"</string>
-    <string name="permdesc_writeSms" msgid="6299398896177548095">"Tillader, at et program skriver i sms-beskeder, der er gemt på din telefon eller dit SIM-kort. Ondsindede programmer kan eventuelt slette dine beskeder."</string>
+    <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"Tillader et program at behandle udgående opkald og ændre nummeret, der skal ringes til. Ondsindede programmer kan overvåge, omdirigere eller forhindre udgående opkald."</string>
+    <string name="permlab_receiveSms" msgid="2697628268086208535">"modtag SMS"</string>
+    <string name="permdesc_receiveSms" msgid="6298292335965966117">"Tillader et program at modtage og behandle SMS-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
+    <string name="permlab_receiveMms" msgid="8894700916188083287">"modtag MMS"</string>
+    <string name="permdesc_receiveMms" msgid="4563346832000174373">"Tillader et program at modtage og behandle MMS-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
+    <string name="permlab_sendSms" msgid="5600830612147671529">"send SMS-beskeder"</string>
+    <string name="permdesc_sendSms" msgid="1946540351763502120">"Tillader et program at sende SMS-beskeder. Ondsindede programmer kan eventuelt koste dig penge ved at sende beskeder uden din bekræftelse."</string>
+    <string name="permlab_readSms" msgid="4085333708122372256">"læs SMS eller MMS"</string>
+    <string name="permdesc_readSms" msgid="3002170087197294591">"Tillader et program at læse SMS-beskeder, der er gemt på din telefon eller dit SIM-kort. Ondsindede programmer kan eventuelt læse dine fortrolige beskeder."</string>
+    <string name="permlab_writeSms" msgid="6881122575154940744">"rediger SMS eller MMS"</string>
+    <string name="permdesc_writeSms" msgid="6299398896177548095">"Tillader et program at skrive til SMS-beskeder, der er gemt på din telefon eller dit SIM-kort. Ondsindede programmer kan eventuelt slette dine beskeder."</string>
     <string name="permlab_receiveWapPush" msgid="8258226427716551388">"modtag WAP"</string>
-    <string name="permdesc_receiveWapPush" msgid="5979623826128082171">"Tillader, at et program modtager og behandler WAP-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
+    <string name="permdesc_receiveWapPush" msgid="5979623826128082171">"Tillader et program at modtage og behandle WAP-beskeder. Ondsindede programmer kan overvåge dine beskeder eller slette dem uden at vise dem til dig."</string>
     <string name="permlab_getTasks" msgid="5005277531132573353">"hent kørende programmer"</string>
-    <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillader, at et program henter oplysninger om nuværende og for nyligt kørende opgaver. Tillader, at eventuelt ondsindede programmer finder private oplysninger om andre programmer."</string>
+    <string name="permdesc_getTasks" msgid="7048711358713443341">"Tillader et program at hente oplysninger om nuværende og for nyligt kørende opgaver. Tillader eventuelt ondsindede programmer at finde private oplysninger om andre programmer."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"omorganiser kørende programmer"</string>
-    <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillader, at et program flytter opgaver til forgrunden og baggrunden. Ondsindede programmer kan tvinge dem selv til forgrunden uden din kontrol."</string>
+    <string name="permdesc_reorderTasks" msgid="126252774270522835">"Tillader et program at flytte opgaver til forgrunden og baggrunden. Ondsindede programmer kan tvinge dem selv til forgrunden uden din kontrol."</string>
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"aktiver programfejlretning"</string>
-    <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillader, at et program slår fejlretning af andet program til. Ondsindede programmer kan bruge dette til at standse andre programmer."</string>
-    <string name="permlab_changeConfiguration" msgid="8214475779521218295">"skift indstillinger for brugergrænsefladen"</string>
-    <string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillader, at et program ændrer den nuværende konfiguration, f.eks. den lokale eller overordnede skrifttypestørrelse."</string>
+    <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillader et program at slå fejlretning af andet program til. Ondsindede programmer kan bruge dette til at standse andre programmer."</string>
+    <string name="permlab_changeConfiguration" msgid="8214475779521218295">"skift dine UI-indstillinger"</string>
+    <string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillader et program at ændre den nuværende konfiguration, som f.eks. den lokale eller overordnede skrifttypestørrelse."</string>
     <string name="permlab_restartPackages" msgid="2386396847203622628">"genstart andre programmer"</string>
-    <string name="permdesc_restartPackages" msgid="1076364837492936814">"Tillader, at et program tvangsgenstarter andre programmer."</string>
+    <string name="permdesc_restartPackages" msgid="1076364837492936814">"Tillader et program at tvangsgenstarte andre programmer."</string>
     <string name="permlab_forceBack" msgid="1804196839880393631">"tving programmet til at lukke"</string>
-    <string name="permdesc_forceBack" msgid="6534109744159919013">"Tillader, at et program tvinger alle programmer, der er i forgrunden, til at lukke og køre i baggrunden. Bør aldrig være nødvendigt til normale programmer."</string>
+    <string name="permdesc_forceBack" msgid="6534109744159919013">"Tillader et program at tvinge alle programmer, der er i forgrunden, til at lukke og gå tilbage. Bør aldrig være nødvendigt til normale programmer."</string>
     <string name="permlab_dump" msgid="1681799862438954752">"hent intern systemtilstand"</string>
-    <string name="permdesc_dump" msgid="2198776174276275220">"Tillader, at et program henter systemets interne tilstand. Ondsindede programmer kan hente en række private og sikre oplysninger, som de normalt aldrig bør have brug for."</string>
+    <string name="permdesc_dump" msgid="2198776174276275220">"Tillader et program at hente systemets interne tilstand. Ondsindede programmer kan hente en række private og sikre oplysninger, som de normalt aldrig bør have brug for."</string>
     <string name="permlab_shutdown" msgid="7185747824038909016">"delvis lukning"</string>
     <string name="permdesc_shutdown" msgid="7046500838746291775">"Sætter aktivitetsadministratoren i lukningstilstand. Lukker ikke helt ned."</string>
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"undgå programskift"</string>
     <string name="permdesc_stopAppSwitches" msgid="3857886086919033794">"Forhindrer brugeren i at skifte til et andet program."</string>
     <string name="permlab_runSetActivityWatcher" msgid="7811586187574696296">"overvåg og kontroller start af alle programmer"</string>
-    <string name="permdesc_runSetActivityWatcher" msgid="3228701938345388092">"Tillader, at et program overvåger og kontrollerer, hvordan systemet starter aktiviteter. Ondsindede programmer kan fuldstændig kompromittere systemet. Denne tilladelse er kun nødvendig til udvikling, aldrig til normal telefonbrug."</string>
+    <string name="permdesc_runSetActivityWatcher" msgid="3228701938345388092">"Tillader et program at overvåge og kontrollere, hvordan systemet starter aktiviteter. Ondsindede programmer kan fuldstændig kompromittere systemet. Denne tilladelse er kun nødvendig til udvikling, aldrig til normal telefonbrug."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"send udsendelse om fjernet pakke"</string>
-    <string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"Tillader, at et program udsender en meddelelse om, at en programpakke er fjernet. Ondsindede programmer kan bruge dette til at afslutte et andet kørende program."</string>
-    <string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"send sms-modtaget udsendelse"</string>
-    <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"Tillader, at et program udsender en meddelelse om, at der er modtaget en sms-besked. Ondsindede programmer kan eventuelt bruge dette til at forfalske indgående sms-beskeder."</string>
+    <string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"Tillader et program at udsende en meddelelse om, at en programpakke er fjernet. Ondsindede programmer kan bruge dette til at afslutte et andet kørende program."</string>
+    <string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"send SMS-modtaget udsendelse"</string>
+    <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"Tillader et program at udsende en meddelelse om, at der er modtaget en SMS-besked. Ondsindede programmer kan eventuelt bruge dette til at forfalske indgående SMS-beskeder."</string>
     <string name="permlab_broadcastWapPush" msgid="3145347413028582371">"send WAP-PUSH-modtaget udsendelse"</string>
-    <string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"Tillader, at et program udsender en meddelelse om, at der er modtaget en WAP PUSH-besked. Ondsindede programmer kan eventuelt bruge dette til at forfalske modtagelse af mms-besked eller i det skjulte erstatte indholdet på alle websider med ondsindede varianter."</string>
+    <string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"Tillader et program at udsende en meddelelse om, at der er modtaget en WAP PUSH-besked. Ondsindede programmer kan eventuelt bruge dette til at forfalske modtagelse af MMS-besked eller i det skjulte erstatte indholdet på alle websider med ondsindede varianter."</string>
     <string name="permlab_setProcessLimit" msgid="2451873664363662666">"begræns antallet af kørende processer"</string>
-    <string name="permdesc_setProcessLimit" msgid="7824786028557379539">"Tillader, at et program kontrollerer det maksimale antal processer, der kan køre. Er aldrig nødvendigt til normale programmer."</string>
+    <string name="permdesc_setProcessLimit" msgid="7824786028557379539">"Tillader et program at kontrollere det maksimale antal processer, der kan køre. Er aldrig nødvendigt til normale programmer."</string>
     <string name="permlab_setAlwaysFinish" msgid="5342837862439543783">"få alle baggrundsprogrammer til at lukke"</string>
-    <string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"Tillader, at et program kontrollerer, om aktiviteter altid afsluttes, så snart de går i baggrunden. Aldrig nødvendigt til normale programmer."</string>
+    <string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"Tillader et program at kontrollere, om aktiviteter altid afsluttes, så snart de går i baggrunden. Aldrig nødvendigt til normale programmer."</string>
     <string name="permlab_batteryStats" msgid="7863923071360031652">"rediger batteristatistikker"</string>
     <string name="permdesc_batteryStats" msgid="5847319823772230560">"Tillader ændring af indsamlede batteristatistikker. Ikke til brug for normale programmer."</string>
     <string name="permlab_backup" msgid="470013022865453920">"kontroller sikkerhedskopiering af system, og gendan"</string>
-    <string name="permdesc_backup" msgid="4837493065154256525">"Tillader, at et program kontrollerer systemets sikkerhedskopierings- og gendannelsesfunktion. Ikke til brug til normale programmer."</string>
-    <string name="permlab_backup_data" msgid="4057625941707926463">"sikkerhedskopier og gendan programmets data"</string>
-    <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillader, at et program deltager i systemets sikkerhedskopierings- og gendannelsesfunktion."</string>
+    <string name="permdesc_backup" msgid="4837493065154256525">"Tillader et program at kontrollere systemets sikkerhedskopierings- og gendannelsesmekanisme. Ikke til brug til normale programmer."</string>
+    <string name="permlab_backup_data" msgid="4057625941707926463">"Sikkerhedskopier og gendan programmets data"</string>
+    <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillader et program at deltage i systemets sikkerhedskopierings- og gendannelsesmekanisme."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"vis uautoriserede vinduer"</string>
     <string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillader oprettelse af vinduer, der er beregnet til at blive brugt af den interne systembrugergrænseflade. Ikke til brug for normale programmer."</string>
     <string name="permlab_systemAlertWindow" msgid="3372321942941168324">"vis underretninger på systemniveau"</string>
-    <string name="permdesc_systemAlertWindow" msgid="5109622689323490558">"Tillader, at et program viser vinduer med systemunderretninger. Ondsindede programmer kan overtage hele telefonens skærm."</string>
+    <string name="permdesc_systemAlertWindow" msgid="5109622689323490558">"Tillader et program at vise systemunderretningsvinduer. Ondsindede programmer kan overtage hele telefonens skærm."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"rediger global animationshastighed"</string>
-    <string name="permdesc_setAnimationScale" msgid="7181522138912391988">"Tillader, at et program altid kan ændre den globale animationshastighed (hurtigere eller langsommere animationer)."</string>
+    <string name="permdesc_setAnimationScale" msgid="7181522138912391988">"Tillader et program at ændre den globale animationshastighed (hurtigere eller langsommere animationer) når som helst."</string>
     <string name="permlab_manageAppTokens" msgid="17124341698093865">"administrer programtokens"</string>
-    <string name="permdesc_manageAppTokens" msgid="977127907524195988">"Tillader, at programmet opretter og administrerer sine egen tokens og gå uden om sin normale Z-rækkefølge. Bør aldrig være nødvendigt til normale programmer."</string>
+    <string name="permdesc_manageAppTokens" msgid="977127907524195988">"Tillader programmet at oprette og administrere deres egen tokens og gå uden om deres normale Z-rækkefølge. Bør aldrig være nødvendigt til normale programmer."</string>
     <string name="permlab_injectEvents" msgid="1378746584023586600">"tryk på taster og kontrolknapper"</string>
-    <string name="permdesc_injectEvents" msgid="3946098050410874715">"Tillader, at et program leverer sine egne inputbegivenheder (tastetryk osv.) til andre programmer. Ondsindede programmer kan bruge dette til at overtage telefonen."</string>
+    <string name="permdesc_injectEvents" msgid="3946098050410874715">"Tillader et program at levere sine egne inputbegivenheder (tastetryk osv.) til andre programmer. Ondsindede programmer kan bruge dette til at overtage telefonen."</string>
     <string name="permlab_readInputState" msgid="469428900041249234">"registrerer, hvad du indtaster, og hvilke handlinger du foretager dig"</string>
-    <string name="permdesc_readInputState" msgid="5132879321450325445">"Tillader, at et program registrerer taster, du trykker på, selv når du interagerer med andre programmer (f.eks. ved indtastning af adgangskode). Bør aldrig være nødvendigt til normale programmer."</string>
+    <string name="permdesc_readInputState" msgid="5132879321450325445">"Tillader et program at holde øje med de taster, du trykker på, selv når du interagerer med andre programmer (som f.eks. indtastning af adgangskode). Bør aldrig være nødvendigt til normale programmer."</string>
     <string name="permlab_bindInputMethod" msgid="3360064620230515776">"forpligt til en inputmetode"</string>
-    <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Tillader, at brugeren forpligter sig til en inputmetodes grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
+    <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Tillader brugeren at forpligte sig på en inputmetodes grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
     <string name="permlab_bindWallpaper" msgid="8716400279937856462">"forpligt til et tapet"</string>
-    <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Tillader, at brugeren forpligter sig til et tapets grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
+    <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Tillader brugeren at forpligte sig på et tapets grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
     <string name="permlab_setOrientation" msgid="3365947717163866844">"skift skærmretning"</string>
-    <string name="permdesc_setOrientation" msgid="6335814461615851863">"Tillader, at et program ændrer rotationen af skærmen når som helst. Bør aldrig være nødvendigt til normale programmer."</string>
+    <string name="permdesc_setOrientation" msgid="6335814461615851863">"Tillader et program at ændre rotationen af skærmen når som helst. Bør aldrig være nødvendigt til normale programmer."</string>
     <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"send Linux-signaler til programmer"</string>
-    <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"Tillader, at programmet kan anmode om, at det leverede signal sendes til alle vedholdende processer."</string>
+    <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"Tillader program at anmode om, at det leverede signal sendes til alle vedholdende processer."</string>
     <string name="permlab_persistentActivity" msgid="8659652042401085862">"lad altid programmet køre"</string>
-    <string name="permdesc_persistentActivity" msgid="5037199778265006008">"Tillader, at et program gør dele af sig selv vedholdende, så systemet ikke kan bruge det til andre programmer."</string>
+    <string name="permdesc_persistentActivity" msgid="5037199778265006008">"Tillader et program at gøre dele af sig selv vedholdende, så systemet ikke kan bruge det til andre programmer."</string>
     <string name="permlab_deletePackages" msgid="3343439331576348805">"slet programmer"</string>
-    <string name="permdesc_deletePackages" msgid="3634943677518723314">"Tillader, at et program sletter Android-pakker. Ondsindede programmer kan bruge dette til at slette vigtige programmer."</string>
+    <string name="permdesc_deletePackages" msgid="3634943677518723314">"Tillader et program at slette Android-pakker. Ondsindede programmer kan bruge dette til at slette vigtige programmer."</string>
     <string name="permlab_clearAppUserData" msgid="2192134353540277878">"slet andre programmers data"</string>
-    <string name="permdesc_clearAppUserData" msgid="7546345080434325456">"Tillader, at et program rydder brugerdata."</string>
+    <string name="permdesc_clearAppUserData" msgid="7546345080434325456">"Lader et program rydde brugerdata."</string>
     <string name="permlab_deleteCacheFiles" msgid="1518556602634276725">"slet andre programmers cacher"</string>
-    <string name="permdesc_deleteCacheFiles" msgid="2283074077168165971">"Tillader, at et program sletter cachefiler."</string>
-    <string name="permlab_getPackageSize" msgid="4799785352306641460">"vis programmets lagerplads"</string>
-    <string name="permdesc_getPackageSize" msgid="5557253039670753437">"Tillader, at et program henter sin kode, data og cachestørrelser"</string>
+    <string name="permdesc_deleteCacheFiles" msgid="2283074077168165971">"Tillader et program at slette cachefiler."</string>
+    <string name="permlab_getPackageSize" msgid="4799785352306641460">"måler programmets lagerplads"</string>
+    <string name="permdesc_getPackageSize" msgid="5557253039670753437">"Tillader et program at hente dets kode, data og cachestørrelser"</string>
     <string name="permlab_installPackages" msgid="335800214119051089">"installer programmer direkte"</string>
-    <string name="permdesc_installPackages" msgid="526669220850066132">"Tillader, at et program installerer nye eller opdaterede Android-pakker. Ondsindede programmer kan bruge dette til at tilføje nye programmer med vilkårlige, effektive tilladelser."</string>
+    <string name="permdesc_installPackages" msgid="526669220850066132">"Tillader et program at installere nye eller opdaterede Android-pakker. Ondsindede programmer kan bruge dette til at tilføje nye programmer med vilkårlige, effektive tilladelser."</string>
     <string name="permlab_clearAppCache" msgid="4747698311163766540">"slet alle cachedata for programmet"</string>
-    <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillader, at et program frigør plads på telefonen ved at slette filer i programmets cachemappe. Adgang er normalt meget begrænset til systemprocesser."</string>
+    <string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillader et program at frigøre plads på telefonen ved at slette filer i programmets cachemappe. Adgang er normalt meget begrænset til systemprocesser."</string>
     <string name="permlab_readLogs" msgid="4811921703882532070">"læs systemlogfiler"</string>
-    <string name="permdesc_readLogs" msgid="2257937955580475902">"Tillader, at et program læser fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, registreres, men logfilerne bør ikke indeholde personlige eller private oplysninger."</string>
+    <string name="permdesc_readLogs" msgid="2257937955580475902">"Tillader et program at læse fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, opdages, men logfilerne skulle ikke indeholde personlige eller private oplysninger."</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"læs/skriv til ressourcer ejet af diag"</string>
-    <string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillader, at et program læser og skriver til alle ressourcer, der ejes af diag-gruppen, som f.eks. flier i /dev. Dette kan muligvis påvirke systemets stabilitet og sikkerhed. Dette bør KUN bruges til hardwarespecifikke diagnosticeringer foretaget af producent eller udbyder."</string>
+    <string name="permdesc_diagnostic" msgid="3121238373951637049">"Tillader et program at læse og skrive til alle ressourcer, der ejes af diag-gruppen, som f.eks. flier i /dev. Dette kan muligvis påvirke systemets stabilitet og sikkerhed. Dette bør KUN bruges til hardwarespecifikke diagnosticeringer foretaget af producent eller udbyder."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"aktiver eller deaktiver programkomponenter"</string>
-    <string name="permdesc_changeComponentState" msgid="4569107043246700630">"Tillader, at et program ændrer, om en komponent fra et andet program er aktiveret eller ej. Ondsindede programmer kan bruge dette til at deaktivere vigtige telefonfunktioner. Denne tilladelse skal anvendes med forsigtighed, da det kan forårsage ubrugelige, inkonsekvente eller ustabile programkomponenter."</string>
-    <string name="permlab_setPreferredApplications" msgid="3393305202145172005">"angiv foretrukne programmer"</string>
-    <string name="permdesc_setPreferredApplications" msgid="760008293501937546">"Tillader, at et program ændrer dine foretrukne programmer. Dette kan medføre, at ondsindede programmer ændrer kørende programmer i det skjulte og narrer dine eksisterende programmer til at indsamle personlige oplysninger fra dig."</string>
+    <string name="permdesc_changeComponentState" msgid="4569107043246700630">"Tillader et program at ændre, om en komponent fra et andet program er aktiveret eller ej. Ondsindede programmer kan bruge dette til at deaktivere vigtige telefonfunktioner. Denne tilladelse skal anvendes med forsigtighed, da det kan forårsage ubrugelige, inkonsekvente eller ustabile programkomponenter."</string>
+    <string name="permlab_setPreferredApplications" msgid="3393305202145172005">"indstil foretrukne programmer"</string>
+    <string name="permdesc_setPreferredApplications" msgid="760008293501937546">"Tillader et program at ændre dine foretrukne programmer. Dette kan tillade, at ondsindede programmer ændrer kørende programmer i det skjulte og narrer dine eksisterende programmer til at indsamle personlige data fra dig."</string>
     <string name="permlab_writeSettings" msgid="1365523497395143704">"rediger globale systemindstillinger"</string>
-    <string name="permdesc_writeSettings" msgid="838789419871034696">"Tillader, at et program ændrer systemets indstillingsdata. Ondsindede programmer kan skade systemets konfiguration."</string>
+    <string name="permdesc_writeSettings" msgid="838789419871034696">"Tillader et program at ændre systemets indstillingsdata. Ondsindede programmer kan skade systemets konfiguration."</string>
     <string name="permlab_writeSecureSettings" msgid="204676251876718288">"rediger sikre systemindstillinger"</string>
-    <string name="permdesc_writeSecureSettings" msgid="5497873143539034724">"Tillader, at et program ændrer systemernes sikre indstillingsdata. Ikke til brug til almindelige programmer."</string>
+    <string name="permdesc_writeSecureSettings" msgid="5497873143539034724">"Tillader et program at ændre systemernes sikre indstillingsdata. Ikke til brug til almindelige programmer."</string>
     <string name="permlab_writeGservices" msgid="2149426664226152185">"rediger kortet over Google-tjenester"</string>
-    <string name="permdesc_writeGservices" msgid="6602362746516676175">"Tillader, at et program ændrer kortet over Google-tjenester. Ikke til brug til normale programmer."</string>
+    <string name="permdesc_writeGservices" msgid="6602362746516676175">"Tillader et program at ændre kortet over Google-tjenester. Ikke til brug til normale programmer."</string>
     <string name="permlab_receiveBootCompleted" msgid="7776779842866993377">"start automatisk ved opstart"</string>
-    <string name="permdesc_receiveBootCompleted" msgid="698336728415008796">"Tillader, at et program starter selv, når systemet er færdig med at starte. Dette kan gøre startem af telefonen langsommere og generelt gøre telefonen langsommere ved altid at lade programmet køre."</string>
+    <string name="permdesc_receiveBootCompleted" msgid="698336728415008796">"Tillader et program at starte selv, når systemet er færdig med at starte. Dette kan gøre start af telefonen langsommere og generelt gøre telefonen langsommere ved altid at lade programmet køre."</string>
     <string name="permlab_broadcastSticky" msgid="7919126372606881614">"send klæbende udsendelse"</string>
-    <string name="permdesc_broadcastSticky" msgid="1920045289234052219">"Tillader, at et program sender klæbende udsendelser, der bliver tilbage, efter udsendelsen er slut. Ondsindede programmer kan gøre telefonen langsom eller ustabil ved at få den til at bruge for meget hukommelse."</string>
+    <string name="permdesc_broadcastSticky" msgid="1920045289234052219">"Tillader et program at sende klæbende udsendelser, der bliver tilbage, efter udsendelsen er slut. Ondsindede programmer kan gøre telefonen langsom eller ustabil ved at få den til at bruge for meget hukommelse."</string>
     <string name="permlab_readContacts" msgid="6219652189510218240">"læs kontaktdata"</string>
-    <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillader, at et program læser alle kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine data til andre mennesker."</string>
+    <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillader et program at læse alle kontaktdata (adresse), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine data til andre mennesker."</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"skriv kontaktdata"</string>
-    <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillader, at et program ændrer kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kontaktdata."</string>
+    <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillader et program at ændre telefonens kontaktdata (adresse), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kontaktdata."</string>
     <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriv ejerdata"</string>
-    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader, at et program ændrer rtelefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string>
+    <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader et program at ændre telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string>
     <string name="permlab_readOwnerData" msgid="6668525984731523563">"læs ejerdata"</string>
-    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader, at et program læser telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string>
+    <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader et program at læse telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string>
     <string name="permlab_readCalendar" msgid="3728905909383989370">"læs kalenderdata"</string>
-    <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader, at et program læser alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string>
+    <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader et program at læse alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string>
     <string name="permlab_writeCalendar" msgid="377926474603567214">"skriv kalenderdata"</string>
-    <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Tillader, at et program ændrer de kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kalenderdata."</string>
+    <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Tillader et program at ændre telefonens kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kalenderdata."</string>
     <string name="permlab_accessMockLocation" msgid="8688334974036823330">"imiterede placeringskilder til test"</string>
     <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Opret imiterede placeringskilder til testning. Ondsindede programmer kan bruge dette til at tilsidesætte den returnerede placering og/eller status fra rigtige placeringskilder som f.eks. GPS eller netværksudbydere."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få adgang til ekstra kommandoer for placeringsudbyder"</string>
@@ -288,33 +288,33 @@
     <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"grov (netværksbaseret) placering"</string>
     <string name="permdesc_accessCoarseLocation" msgid="8235655958070862293">"Få adgang til grove placeringskilder som f.eks. den mobile netværksdatabase for at finde en omtrentlig placering for telefonen, hvor det er muligt. Ondsindede programmer kan bruge dette til at finde ud af, hvor du omtrent er."</string>
     <string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"få adgang til SurfaceFlinger"</string>
-    <string name="permdesc_accessSurfaceFlinger" msgid="6805241830020733025">"Tillader, at et program bruger SurfaceFlinger-funktioner på lavt niveau."</string>
+    <string name="permdesc_accessSurfaceFlinger" msgid="6805241830020733025">"Tillader et program at bruge SurfaceFlinger-funktioner på lavt niveau."</string>
     <string name="permlab_readFrameBuffer" msgid="6690504248178498136">"læs rammebuffer"</string>
-    <string name="permdesc_readFrameBuffer" msgid="7530020370469942528">"Tillader, at et program læser indholdet fra rammebufferen."</string>
+    <string name="permdesc_readFrameBuffer" msgid="7530020370469942528">"Tillader et program at læse indholdet fra rammebufferen."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"skift dine lydindstillinger"</string>
-    <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader, at et program ændrer globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string>
+    <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader et program at ændre globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"optag lyd"</string>
-    <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader, at et program får adgang til lydregistreringsstien."</string>
+    <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader et program at få adgang til lydregistreringsstien."</string>
     <string name="permlab_camera" msgid="8059288807274039014">"tag billeder"</string>
-    <string name="permdesc_camera" msgid="9013476258810982546">"Tillader, at programmet tager billeder med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string>
+    <string name="permdesc_camera" msgid="9013476258810982546">"Tillader programmet at tage billeder med kameraet. Dette tillader programmet til hver en tid at indsamle de billeder, kameraet ser."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"deaktiver telefonen permanent"</string>
-    <string name="permdesc_brick" msgid="5569526552607599221">"Tillader, at programmet deaktiverer hele telefonen permanent. Dette er meget farligt."</string>
+    <string name="permdesc_brick" msgid="5569526552607599221">"Tillader programmet at deaktivere hele telefonen permanent. Dette er meget farligt."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"tving telefon til at genstarte"</string>
-    <string name="permdesc_reboot" msgid="7914933292815491782">"Tillader, at programmet tvinger telefonen til at genstarte."</string>
+    <string name="permdesc_reboot" msgid="7914933292815491782">"Tillader programmet at tvinge telefonen til at genstarte."</string>
     <string name="permlab_mount_unmount_filesystems" msgid="1761023272170956541">"monter eller demonter filsystemer"</string>
-    <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Tillader, at programmet monterer eller demonterer filsystemer til flytbar lagring."</string>
+    <string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Tillader programmet at montere eller demontere filsystemer til flytbar lagring."</string>
     <string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formater ekstern lagring"</string>
-    <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Tillader, at et program formaterer flytbart lager."</string>
+    <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Tillader et program at formatere flytbar lagring."</string>
     <string name="permlab_vibrate" msgid="7768356019980849603">"kontroller vibrator"</string>
     <string name="permdesc_vibrate" msgid="2886677177257789187">"Lader programmet kontrollere vibratoren."</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string>
-    <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader, at programmet kontrollerer lommelygten."</string>
+    <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader programmet at kontrollere lommelygten."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"test hardware"</string>
-    <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillader, at et program kontrollerer forskellige perifere enheder for at teste hardwaren."</string>
+    <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillader et program at kontrollere forskellige perifere enheder med det formål at teste hardwaren."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ring direkte op til telefonnumre"</string>
-    <string name="permdesc_callPhone" msgid="3369867353692722456">"Tillader, at programmet ringer til telefonnumre uden din indgriben. Ondsindede programmer kan forårsage uventede opkald på din telefonregning. Vær opmærksom på, at programmet ikke kan ringe til nødnumre."</string>
+    <string name="permdesc_callPhone" msgid="3369867353692722456">"Tillader programmet at ringe til telefonnumre uden din indgriben. Ondsindede programmer kan forårsage uventede opkald på din telefonregning. Vær opmærksom på, at det ikke tillader programmet at ringe til nødnumre."</string>
     <string name="permlab_callPrivileged" msgid="4198349211108497879">"ring direkte op til alle telefonnumre"</string>
-    <string name="permdesc_callPrivileged" msgid="244405067160028452">"Tillader, at programmet ringer til alle telefonnumre inklusive nødnumre uden din indgriben. Ondsindede programmer kan eventuelt foretage unødvendige og ulovlige opkald til nødtjenester."</string>
+    <string name="permdesc_callPrivileged" msgid="244405067160028452">"Tillader programmet at ringe til alle telefonnumre inklusive nødnumre uden din indgriben. Ondsindede programmer kan eventuelt foretage unødvendige og ulovlige opkald til nødtjenester."</string>
     <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"start CDMA-telefonopsætning direkte"</string>
     <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Tillader, at programmet starter CDMA-levering. Ondsindede programmer kan starte unødvendig CDMA-levering"</string>
     <string name="permlab_locationUpdates" msgid="7785408253364335740">"kontroller meddelelser om placeringsopdatering"</string>
@@ -322,75 +322,75 @@
     <string name="permlab_checkinProperties" msgid="7855259461268734914">"egenskaber for adgangskontrol"</string>
     <string name="permdesc_checkinProperties" msgid="7150307006141883832">"Tillader læse/skrive-adgang til egenskaber, der er uploadet af kontroltjenesten. Ikke til brug til normale programmer."</string>
     <string name="permlab_bindGadget" msgid="776905339015863471">"vælg widgets"</string>
-    <string name="permdesc_bindGadget" msgid="2098697834497452046">"Tillader, at programmet fortæller systemet, hvilke widgets der kan bruges af hvilke programmer. Med denne tilladelse kan programmer give adgang til personlige oplysninger til andre programmer. Ikke til brug til normale programmer."</string>
+    <string name="permdesc_bindGadget" msgid="2098697834497452046">"Tillader programmet at fortælle systemet, hvilke widgets der kan bruges af hvilke programmer. Med denne tilladelse kan programmer give adgang til personlige data til andre programmer. Ikke til brug til normale programmer."</string>
     <string name="permlab_modifyPhoneState" msgid="8423923777659292228">"rediger telefontilstand"</string>
-    <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"Tillader, at programmet kontrollerer enhedens telefonfunktioner. Et program med denne tilladelse kan skifte netværk, slå telefonens radio til og fra og lignende uden nogensinde at informere dig."</string>
+    <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"Tillader programmet at kontrollere enhedens telefonfunktioner. Et program med denne tilladelse kan skifte netværk, slå telefonens radio til og fra og lignende uden nogensinde at underrette dig."</string>
     <string name="permlab_readPhoneState" msgid="2326172951448691631">"læs telefontilstand og identitet"</string>
-    <string name="permdesc_readPhoneState" msgid="188877305147626781">"Tillader, at programmet får adgang til enhedens telefonfunktioner. Et program kan med denne tilladelse registrere denne telefons telefon- og serienummer, om et opkald er aktivt, nummeret som det opkald er forbundet til osv."</string>
+    <string name="permdesc_readPhoneState" msgid="188877305147626781">"Tillader programmet at få adgang til enhedens telefonfunktioner. Et program med denne tilladelse kan afgøre denne telefons telefon- og serienummer, om et opkald er aktivt, nummeret som opkaldet er forbundet til osv."</string>
     <string name="permlab_wakeLock" msgid="573480187941496130">"afhold telefonen fra at gå i dvale"</string>
-    <string name="permdesc_wakeLock" msgid="7584036471227467099">"Tillader, at et program forhindrer telefonen i at gå i dvale."</string>
-    <string name="permlab_devicePower" msgid="4928622470980943206">"tænd eller sluk for telefonen"</string>
-    <string name="permdesc_devicePower" msgid="4577331933252444818">"Tillader, at programmet slukker og tænder telefonen."</string>
+    <string name="permdesc_wakeLock" msgid="7584036471227467099">"Tillader et program at forhindre telefonen i at gå i dvale."</string>
+    <string name="permlab_devicePower" msgid="4928622470980943206">"Tænd eller sluk for telefonen"</string>
+    <string name="permdesc_devicePower" msgid="4577331933252444818">"Tillader programmet at slå telefonen til eller fra."</string>
     <string name="permlab_factoryTest" msgid="3715225492696416187">"kør i fabriksindstillet testtilstand"</string>
     <string name="permdesc_factoryTest" msgid="8136644990319244802">"Kør som en producenttest på lavt niveau. Giver fuld adgang til telefonens hardware. Kun tilgængeligt når en telefon kører i producenttesttilstand."</string>
     <string name="permlab_setWallpaper" msgid="6627192333373465143">"angiv tapet"</string>
-    <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Tillader, at programmet opsætter systemets tapet."</string>
+    <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Tillader programmet at opsætte systemets tapet."</string>
     <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"opsæt tip til tapetstørrelse"</string>
-    <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Tillader, at programmet opsætter størrelsestip for systemets tapet."</string>
+    <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Tillader programmet at opsætte størrelsestip for systemets tapet."</string>
     <string name="permlab_masterClear" msgid="2315750423139697397">"nulstil system til fabriksstandarder"</string>
-    <string name="permdesc_masterClear" msgid="5033465107545174514">"Tillader, at et program nulstiller systemet fuldstændig til fabriksindstillingerne, sletter alle data, konfigurationer og installerede programmer."</string>
-    <string name="permlab_setTimeZone" msgid="2945079801013077340">"angiv tidszone"</string>
-    <string name="permdesc_setTimeZone" msgid="1902540227418179364">"Tillader, at et program ændrer telefonens tidszone."</string>
+    <string name="permdesc_masterClear" msgid="5033465107545174514">"Tillader et program fuldstændig at nulstille systemet til fabriksindstillingerne, slette alle data, konfigurationen og installerede programmer."</string>
+    <string name="permlab_setTimeZone" msgid="2945079801013077340">"indstil tidszone"</string>
+    <string name="permdesc_setTimeZone" msgid="1902540227418179364">"Tillader et program at ændre telefonens tidszone."</string>
     <string name="permlab_accountManagerService" msgid="4829262349691386986">"fungerer som kontoadministrationstjeneste"</string>
-    <string name="permdesc_accountManagerService" msgid="6056903274106394752">"Tillader, at et program foretager opkald til kontogodkendere"</string>
-    <string name="permlab_getAccounts" msgid="4549918644233460103">"registrer kendte konti"</string>
-    <string name="permdesc_getAccounts" msgid="6839262446413155394">"Tillader, at et program henter listen over konti, der er kendt af telefonen."</string>
+    <string name="permdesc_accountManagerService" msgid="6056903274106394752">"Tillader et program at foretage opkald til kontogodkendere"</string>
+    <string name="permlab_getAccounts" msgid="4549918644233460103">"opdag kendte konti"</string>
+    <string name="permdesc_getAccounts" msgid="6839262446413155394">"Tillader et program at hente listen over konti, der er kendt af telefonen."</string>
     <string name="permlab_authenticateAccounts" msgid="3940505577982882450">"fungerer som en kontogodkender"</string>
-    <string name="permdesc_authenticateAccounts" msgid="4006839406474208874">"Tillader, at et program bruger kontoadministratorens kontogodkendelsesegenskaber, bl.a. oprettelse af konti samt hentning og indstilling af deres adgangskoder."</string>
-    <string name="permlab_manageAccounts" msgid="4440380488312204365">"administrer kontolisten"</string>
-    <string name="permdesc_manageAccounts" msgid="8804114016661104517">"Tillader, at et program foretager handlinger, f.eks. at tilføje og fjerne konti samt slette sin adgangskode."</string>
+    <string name="permdesc_authenticateAccounts" msgid="4006839406474208874">"Tillader et program at bruge kontoadministratorens kontogodkendelsesegenskaber, bl.a. oprettelse af konti samt hentning og indstilling af deres adgangskoder."</string>
+    <string name="permlab_manageAccounts" msgid="4440380488312204365">"administrer kontilisten"</string>
+    <string name="permdesc_manageAccounts" msgid="8804114016661104517">"Tillader et program at foretage handlinger, som f.eks. at tilføje og fjerne konti samt slette deres adgangskode."</string>
     <string name="permlab_useCredentials" msgid="6401886092818819856">"brug en kontos godkendelsesoplysninger"</string>
-    <string name="permdesc_useCredentials" msgid="7416570544619546974">"Tillader, at et program anmoder om godkendelsestokens"</string>
+    <string name="permdesc_useCredentials" msgid="7416570544619546974">"Tillader et program at anmode om godkendelsestokens"</string>
     <string name="permlab_accessNetworkState" msgid="6865575199464405769">"vis netværkstilstand"</string>
-    <string name="permdesc_accessNetworkState" msgid="558721128707712766">"Tillader, at et program viser tilstanden for alle netværk."</string>
+    <string name="permdesc_accessNetworkState" msgid="558721128707712766">"Tillader et program at vise tilstanden for alle netværk."</string>
     <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"Fuld internetadgang"</string>
-    <string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Tillader, at et program opretter netværks-sockets."</string>
+    <string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Tillader et program at oprette netværks-sockets."</string>
     <string name="permlab_writeApnSettings" msgid="7823599210086622545">"skriv indstillinger for adgangspunktnavn"</string>
-    <string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Tillader, at et program ændrer APN-indstillingerne, f.eks. enhver APNs Proxy og Port."</string>
+    <string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Tillader et program at ændre APN-indstillingerne, som f.eks. enhver APNs Proxy og Port."</string>
     <string name="permlab_changeNetworkState" msgid="958884291454327309">"skift netværksforbindelse"</string>
-    <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Tillader, at et program ændrer netværksforbindelsens tilstand."</string>
+    <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Tillader et program at ændre netværksforbindelsens tilstand."</string>
     <string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"skift brugerindstilling for baggrundsdata"</string>
-    <string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Tillader, at et program ændrer brugerindstillingerne for baggrundsdata."</string>
+    <string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Tillader et program at ændre brugerindstillingerne for baggrundsdata."</string>
     <string name="permlab_accessWifiState" msgid="8100926650211034400">"vis Wi-Fi-tilstand"</string>
-    <string name="permdesc_accessWifiState" msgid="485796529139236346">"Tillader, at et program viser oplysninger om Wi-Fi-tilstanden."</string>
+    <string name="permdesc_accessWifiState" msgid="485796529139236346">"Tillader et program at vise oplysninger om Wi-Fi-tilstanden."</string>
     <string name="permlab_changeWifiState" msgid="7280632711057112137">"skift Wi-Fi-tilstand"</string>
-    <string name="permdesc_changeWifiState" msgid="2950383153656873267">"Tillader, at et program opretter og afbryder forbindelsen fra Wi-Fi-adgangspunkter og foretager ændringer i konfigurerede Wi-Fi-netværk."</string>
+    <string name="permdesc_changeWifiState" msgid="2950383153656873267">"Tillader et program at oprette og afbryde forbindelse fra Wi-Fi-adgangspunkter og foretage ændringer til konfigurerede Wi-Fi-netværk."</string>
     <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"tillad Wi-Fi-multicastmodtagelse"</string>
-    <string name="permdesc_changeWifiMulticastState" msgid="8199464507656067553">"Tillader, at et program modtager pakker, der ikke er direkte adresseret til din enhed. Dette kan være nyttigt, hvis du finder tjenester, der tilbydes i nærheden. Det bruger mere strøm end multicasttilstanden."</string>
+    <string name="permdesc_changeWifiMulticastState" msgid="8199464507656067553">"Tillader et program at modtage pakker, der ikke er direkte adresseret til din enhed. Dette kan være nyttigt, hvis du finder tjenester, der er tilbudt i nærheden. Det bruger mere strøm end multicasttilstanden."</string>
     <string name="permlab_bluetoothAdmin" msgid="1092209628459341292">"bluetooth-administration"</string>
-    <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Tillader, at et program konfigurerer den lokale Bluetooth-telefon samt opdager og parrer med fjerne enheder."</string>
+    <string name="permdesc_bluetoothAdmin" msgid="7256289774667054555">"Tillader et program at konfigurere den lokale Bluetooth-telefon samt at opdage og parre med fjerne enheder."</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"opret Bluetooth-forbindelser"</string>
-    <string name="permdesc_bluetooth" msgid="762515380679392945">"Tillader, at et program viser konfigurationen af den lokale Bluetooth-telefon samt opretter og accepterer forbindelse med parrede enheder."</string>
+    <string name="permdesc_bluetooth" msgid="762515380679392945">"Tillader et program at vise konfigurationen af den lokale Bluetooth-telefon samt at oprette og acceptere forbindelse med parrede enheder."</string>
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"deaktiver tastaturlås"</string>
-    <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Tillader, at et program deaktiverer tastaturlåsen og al associeret adgangskodesikkerhed. Et legitimt eksempel på dette er, at telefonen deaktiverer tastaturlåsen, når der modtages et indgående telefonopkald, og genaktiverer tastaturlåsen, når opkaldet er afsluttet."</string>
-    <string name="permlab_readSyncSettings" msgid="6201810008230503052">"læs indstillinger for synkronisering"</string>
-    <string name="permdesc_readSyncSettings" msgid="5315925706353341823">"Tillader, at et program læser synkroniseringsindstillingerne, f.eks. om kontakter skal synkroniseres."</string>
-    <string name="permlab_writeSyncSettings" msgid="6297138566442486462">"skriv indstillinger for synkronisering"</string>
-    <string name="permdesc_writeSyncSettings" msgid="2498201614431360044">"Tillader, at et program ændrer indstillingerne for synkronisering, f.eks. kontakter skal synkroniseres."</string>
+    <string name="permdesc_disableKeyguard" msgid="3189763479326302017">"Tillader et program at deaktivere tastaturlåsen og al associeret adgangskodesikkerhed. Et legitimt eksempel på dette er, at telefonen deaktiverer tastaturlåsen, når der modtages et indgående telefonopkald, og genaktiverer tastaturlåsen, når opkaldet er afsluttet."</string>
+    <string name="permlab_readSyncSettings" msgid="6201810008230503052">"læs synkroniseringsindstillinger"</string>
+    <string name="permdesc_readSyncSettings" msgid="5315925706353341823">"Tillader et program at læse synkroniseringsindstillingerne, som f.eks. om synkronisering er aktiveret for kontakter."</string>
+    <string name="permlab_writeSyncSettings" msgid="6297138566442486462">"skriv synkroniseringsindstillinger"</string>
+    <string name="permdesc_writeSyncSettings" msgid="2498201614431360044">"Tillader et program at ændre synkroniseringsindstillingerne, som f.eks. om synkronisering er aktiveret for kontakter."</string>
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"læs synkroniseringsstatistikker"</string>
-    <string name="permdesc_readSyncStats" msgid="7511448343374465000">"Tillader, at et program læser synkroniseringsstatistikker, f.eks. oversigten over forrige synkroniseringer."</string>
+    <string name="permdesc_readSyncStats" msgid="7511448343374465000">"Tillader et program at læse synkroniseringsstatistikkerne, som f.eks. oversigt over forekomne synkroniseringer."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"læs abonnerede feeds"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="3622200625634207660">"Tillader, at et program får detaljer om de aktuelt synkroniserede feeds."</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="3622200625634207660">"Lader et program få detaljer om de aktuelt synkroniserede feeds."</string>
     <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"skriv abonnerede feeds"</string>
-    <string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"Tillader, at et program ændrer dine aktuelle synkroniserede feeds. Dette kan muligvis lade et ondsindet program ændre dine synkroniserede feeds."</string>
+    <string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"Tillader et program at ændre dine aktuelle synkroniserede feeds. Dette kan muligvis lade et ondsindet program ændre dine synkroniserede feeds."</string>
     <string name="permlab_readDictionary" msgid="432535716804748781">"læs brugerdefineret ordbog"</string>
-    <string name="permdesc_readDictionary" msgid="1082972603576360690">"Tillader, at et program læser alle private ord, navne og sætninger, som brugeren eventuelt har gemt i brugerordbogen."</string>
-    <string name="permlab_writeDictionary" msgid="6703109511836343341">"skriv til den brugerdefinerede ordbog"</string>
-    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillader, at et program skriver nye ord i brugerordbogen."</string>
-    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"ret/slet indholdet på SD-kortet"</string>
-    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillader, at et program skriver til SD-kortet."</string>
+    <string name="permdesc_readDictionary" msgid="1082972603576360690">"Tillader et program at læse alle private ord, navne og sætninger, som brugeren eventuelt har gemt i brugerordbogen."</string>
+    <string name="permlab_writeDictionary" msgid="6703109511836343341">"skriv til den brugerdefinerede mappe"</string>
+    <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillader et program at skrive nye ord i brugermappen."</string>
+    <string name="permlab_sdcardWrite" msgid="8079403759001777291">"ret/slet indholdet af SD-kort"</string>
+    <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillader et program at skrive til SD-kortet."</string>
   <string-array name="phoneTypes">
-    <item msgid="8901098336658710359">"Hjem"</item>
+    <item msgid="8901098336658710359">"Start"</item>
     <item msgid="869923650527136615">"Mobil"</item>
     <item msgid="7897544654242874543">"Arbejde"</item>
     <item msgid="1103601433382158155">"Arbejdsfax"</item>
@@ -400,19 +400,19 @@
     <item msgid="9192514806975898961">"Tilpasset"</item>
   </string-array>
   <string-array name="emailAddressTypes">
-    <item msgid="8073994352956129127">"Hjem"</item>
+    <item msgid="8073994352956129127">"Start"</item>
     <item msgid="7084237356602625604">"Arbejde"</item>
     <item msgid="1112044410659011023">"Andre"</item>
     <item msgid="2374913952870110618">"Tilpasset"</item>
   </string-array>
   <string-array name="postalAddressTypes">
-    <item msgid="6880257626740047286">"Hjem"</item>
+    <item msgid="6880257626740047286">"Start"</item>
     <item msgid="5629153956045109251">"Arbejde"</item>
     <item msgid="4966604264500343469">"Andre"</item>
     <item msgid="4932682847595299369">"Tilpasset"</item>
   </string-array>
   <string-array name="imAddressTypes">
-    <item msgid="1738585194601476694">"Hjem"</item>
+    <item msgid="1738585194601476694">"Start"</item>
     <item msgid="1359644565647383708">"Arbejde"</item>
     <item msgid="7868549401053615677">"Anden"</item>
     <item msgid="3145118944639869809">"Tilpasset"</item>
@@ -433,7 +433,7 @@
     <item msgid="1648797903785279353">"Jabber"</item>
   </string-array>
     <string name="phoneTypeCustom" msgid="1644738059053355820">"Tilpasset"</string>
-    <string name="phoneTypeHome" msgid="2570923463033985887">"Hjem"</string>
+    <string name="phoneTypeHome" msgid="2570923463033985887">"Start"</string>
     <string name="phoneTypeMobile" msgid="6501463557754751037">"Mobil"</string>
     <string name="phoneTypeWork" msgid="8863939667059911633">"Arbejde"</string>
     <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Arbejdsfax"</string>
@@ -445,28 +445,28 @@
     <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Virksomhed (hovednummer)"</string>
     <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string>
     <string name="phoneTypeMain" msgid="6766137010628326916">"Hoved"</string>
-    <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Andre faxbeskeder"</string>
+    <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Andre faxmeddelelser"</string>
     <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string>
     <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string>
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Arbejdsmobiltelefon"</string>
     <string name="phoneTypeWorkPager" msgid="649938731231157056">"Personsøger"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string>
-    <string name="phoneTypeMms" msgid="7254492275502768992">"mms"</string>
+    <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
     <string name="eventTypeBirthday" msgid="2813379844211390740">"Fødselsdato"</string>
     <string name="eventTypeAnniversary" msgid="3876779744518284000">"Årsdag"</string>
     <string name="eventTypeOther" msgid="5834288791948564594">"Begivenhed"</string>
     <string name="emailTypeCustom" msgid="8525960257804213846">"Tilpasset"</string>
-    <string name="emailTypeHome" msgid="449227236140433919">"Hjem"</string>
+    <string name="emailTypeHome" msgid="449227236140433919">"Start"</string>
     <string name="emailTypeWork" msgid="3548058059601149973">"Arbejde"</string>
     <string name="emailTypeOther" msgid="2923008695272639549">"Andre"</string>
     <string name="emailTypeMobile" msgid="119919005321166205">"Mobil"</string>
     <string name="postalTypeCustom" msgid="8903206903060479902">"Tilpasset"</string>
-    <string name="postalTypeHome" msgid="8165756977184483097">"Hjem"</string>
+    <string name="postalTypeHome" msgid="8165756977184483097">"Start"</string>
     <string name="postalTypeWork" msgid="5268172772387694495">"Arbejde"</string>
     <string name="postalTypeOther" msgid="2726111966623584341">"Andre"</string>
     <string name="imTypeCustom" msgid="2074028755527826046">"Tilpasset"</string>
-    <string name="imTypeHome" msgid="6241181032954263892">"Hjem"</string>
+    <string name="imTypeHome" msgid="6241181032954263892">"Start"</string>
     <string name="imTypeWork" msgid="1371489290242433090">"Arbejde"</string>
     <string name="imTypeOther" msgid="5377007495735915478">"Andre"</string>
     <string name="imProtocolCustom" msgid="6919453836618749992">"Tilpasset"</string>
@@ -492,7 +492,7 @@
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skærmen er låst."</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tryk på Menu for at låse op eller foretage et nødopkald."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryk på Menu for at låse op."</string>
-    <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Tegn oplåsningsmønster"</string>
+    <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Tegn mønster til at låse op"</string>
     <string name="lockscreen_emergency_call" msgid="5347633784401285225">"Nødopkald"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Rigtigt!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager! Prøv igen"</string>
@@ -504,35 +504,32 @@
     <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Der er ikke noget SIM-kort i telefonen."</string>
     <string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"Indsæt et SIM-kort."</string>
     <string name="lockscreen_network_locked_message" msgid="143389224986028501">"Netværket er låst"</string>
-    <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM-kortet er låst med PUK-koden."</string>
+    <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM-kort er låst med PUK-koden."</string>
     <string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"Se brugervejledningen, eller kontakt kundeservice."</string>
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet er låst."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser SIM-kortet op ..."</string>
-    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
-    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. Efter <xliff:g id="NUMBER_1">%d</xliff:g> forsøg mere vil du blive bedt om at låse din telefon op ved hjælp af dit Google-login"\n\n" Prøv igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har tegnet dit mønster til at låse op forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har tegnet dit mønster til at låse op forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. Efter <xliff:g id="NUMBER_1">%d</xliff:g> forsøg mere vil du blive bedt om at låse din telefon op ved hjælp af dit Google-login"\n\n" Prøv igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Prøv igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
-    <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Har du glemt mønstret?"</string>
+    <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Har du glemt mønster?"</string>
     <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Oplåsning af konto"</string>
-    <string name="lockscreen_glogin_too_many_attempts" msgid="2446246026221678244">"For mange forsøg på at tegne mønstret korrekt!"</string>
+    <string name="lockscreen_glogin_too_many_attempts" msgid="2446246026221678244">"For mange mønsterforsøg!"</string>
     <string name="lockscreen_glogin_instructions" msgid="1816635201812207709">"For at låse op skal du logge ind med din Google-konto"</string>
     <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Brugernavn (e-mail)"</string>
     <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Adgangskode"</string>
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Log ind"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Ugyldigt brugernavn eller ugyldig adgangskode."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Kontrollerer ..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Lås op"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Lyd slået til"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Lyd slået fra"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ryd"</string>
     <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen meddelelser"</string>
-    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"I gang"</string>
+    <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Løbende"</string>
     <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelelser"</string>
     <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="battery_status_charging" msgid="756617993998772213">"Oplader ..."</string>
     <string name="battery_low_title" msgid="7923774589611311406">"Forbind oplader"</string>
-    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at være fladt:"</string>
+    <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at blive tomt:"</string>
     <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre tilbage."</string>
     <string name="battery_low_why" msgid="7279169609518386372">"Batteriforbrug"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string>
@@ -545,9 +542,9 @@
     <string name="save_password_label" msgid="6860261758665825069">"Bekræft"</string>
     <string name="double_tap_toast" msgid="1068216937244567247">"Tip: Dobbeltklik for at zoome ind eller ud."</string>
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"læs browserens oversigt og bogmærker"</string>
-    <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader, at programmet læser alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string>
+    <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader programmet at læse alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriv browserens oversigt og bogmærker"</string>
-    <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillader, at et program ændrer browseroversigten eller bogmærker, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre din browsers data."</string>
+    <string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillader et program at ændre browseroversigten eller bogmærker, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre din browsers data."</string>
     <string name="save_password_message" msgid="767344687139195790">"Ønsker du, at browseren skal huske denne adgangskode?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Ikke nu"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
@@ -641,12 +638,12 @@
     <string name="weeks" msgid="6509623834583944518">"uger"</string>
     <string name="year" msgid="4001118221013892076">"år"</string>
     <string name="years" msgid="6881577717993213522">"år"</string>
-    <string name="every_weekday" msgid="8777593878457748503">"Hverdage (man.-fre.)"</string>
+    <string name="every_weekday" msgid="8777593878457748503">"Hver ugedag (man.-fre.)"</string>
     <string name="daily" msgid="5738949095624133403">"Dagligt"</string>
     <string name="weekly" msgid="983428358394268344">"Ugentlig hver <xliff:g id="DAY">%s</xliff:g>"</string>
     <string name="monthly" msgid="2667202947170988834">"Månedligt"</string>
     <string name="yearly" msgid="1519577999407493836">"Årligt"</string>
-    <string name="VideoView_error_title" msgid="3359437293118172396">"Videoen kan ikke afspilles"</string>
+    <string name="VideoView_error_title" msgid="3359437293118172396">"Video kan ikke afspilles"</string>
     <string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Beklager! Denne video er ikke gyldig til streaming på denne enhed."</string>
     <string name="VideoView_error_text_unknown" msgid="710301040038083944">"Beklager! Denne video kan ikke afspilles."</string>
     <string name="VideoView_error_button" msgid="2822238215100679592">"OK"</string>
@@ -658,8 +655,8 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Vælg alle"</string>
-    <string name="selectText" msgid="3889149123626888637">"Marker tekst"</string>
-    <string name="stopSelectingText" msgid="4157931463872320996">"Hold op med at markere tekst"</string>
+    <string name="selectText" msgid="3889149123626888637">"Vælg tekst"</string>
+    <string name="stopSelectingText" msgid="4157931463872320996">"Stands med at vælge tekst"</string>
     <string name="cut" msgid="3092569408438626261">"Klip"</string>
     <string name="cutAll" msgid="2436383270024931639">"Klip alle"</string>
     <string name="copy" msgid="2681946229533511987">"Kopier"</string>
@@ -678,7 +675,7 @@
     <string name="dialog_alert_title" msgid="2049658708609043103">"Bemærk"</string>
     <string name="capital_on" msgid="1544682755514494298">"TIL"</string>
     <string name="capital_off" msgid="6815870386972805832">"FRA"</string>
-    <string name="whichApplication" msgid="4533185947064773386">"Fuldfør handling ved hjælp af"</string>
+    <string name="whichApplication" msgid="4533185947064773386">"Afslut handling ved hjælp af"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"Brug som standard til denne handling."</string>
     <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Ryd standard i Startindstillinger &gt; Programmer &gt; Administrer programmer."</string>
     <string name="chooseActivity" msgid="1009246475582238425">"Vælg en handling"</string>
@@ -696,14 +693,14 @@
     <string name="wait" msgid="7147118217226317732">"Vent"</string>
     <string name="debug" msgid="9103374629678531849">"Fejlretning"</string>
     <string name="sendText" msgid="5132506121645618310">"Vælg en handling for teksten"</string>
-    <string name="volume_ringtone" msgid="6885421406845734650">"Lydstyrke for opkald"</string>
-    <string name="volume_music" msgid="5421651157138628171">"Lydstyrke for medier"</string>
-    <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"Afspilning via Bluetooth"</string>
-    <string name="volume_music_hint_silent_ringtone_selected" msgid="6158339745293431194">"Lydløs ringetone er valgt"</string>
-    <string name="volume_call" msgid="3941680041282788711">"Lydstyrke for opkald"</string>
-    <string name="volume_bluetooth_call" msgid="2002891926351151534">"Lydstyrke for Bluetooth under opkald"</string>
-    <string name="volume_alarm" msgid="1985191616042689100">"Lydstyrke for alarm"</string>
-    <string name="volume_notification" msgid="2422265656744276715">"Lydstyrke for meddelelser"</string>
+    <string name="volume_ringtone" msgid="6885421406845734650">"Opkaldslydstyrke"</string>
+    <string name="volume_music" msgid="5421651157138628171">"Medielydstyrke"</string>
+    <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"Spiller gennem Bluetooth"</string>
+    <string name="volume_music_hint_silent_ringtone_selected" msgid="6158339745293431194">"Lydløs ringetone valgt"</string>
+    <string name="volume_call" msgid="3941680041282788711">"Opkaldslydstyrke"</string>
+    <string name="volume_bluetooth_call" msgid="2002891926351151534">"Bluetooth-lydstyrke under opkald"</string>
+    <string name="volume_alarm" msgid="1985191616042689100">"Alarmlydstyrke"</string>
+    <string name="volume_notification" msgid="2422265656744276715">"Meddelelseslydstyrke"</string>
     <string name="volume_unknown" msgid="1400219669770445902">"Lydstyrke"</string>
     <string name="ringtone_default" msgid="3789758980357696936">"Standardringetone"</string>
     <string name="ringtone_default_with_actual" msgid="8129563480895990372">"Standardringetone (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
@@ -720,46 +717,46 @@
   </plurals>
     <string name="select_character" msgid="3365550120617701745">"Indsæt tegn"</string>
     <string name="sms_control_default_app_name" msgid="7630529934366549163">"Ukendt program"</string>
-    <string name="sms_control_title" msgid="7296612781128917719">"Sender sms-beskeder"</string>
-    <string name="sms_control_message" msgid="1289331457999236205">"Der sendes et stort antal sms-beskeder. Vælg \"OK\" for at fortsætte eller \"Annuller\" for at stoppe afsendelsen."</string>
+    <string name="sms_control_title" msgid="7296612781128917719">"Sender SMS-beskeder"</string>
+    <string name="sms_control_message" msgid="1289331457999236205">"Der sendes et stort antal SMS-beskeder. Vælg \"OK\" for at fortsætte eller \"Annuller\" for at stoppe med at sende."</string>
     <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
     <string name="sms_control_no" msgid="1715320703137199869">"Annuller"</string>
-    <string name="date_time_set" msgid="5777075614321087758">"Angiv"</string>
+    <string name="date_time_set" msgid="5777075614321087758">"Indstil"</string>
     <string name="default_permission_group" msgid="2690160991405646128">"Standard"</string>
     <string name="no_permissions" msgid="7283357728219338112">"Der kræves ingen tilladelser"</string>
     <string name="perms_hide" msgid="7283915391320676226"><b>"Skjul"</b></string>
     <string name="perms_show_all" msgid="2671791163933091180"><b>"Vis alle"</b></string>
     <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Indlæser ..."</string>
-    <string name="usb_storage_title" msgid="5901459041398751495">"USB er tilsluttet"</string>
+    <string name="usb_storage_title" msgid="5901459041398751495">"USB forbundet"</string>
     <string name="usb_storage_message" msgid="2759542180575016871">"Du har forbundet din telefon til din computer via USB. Vælg \"Monter\", hvis du ønsker at kopiere filer mellem din computer og din telefons SD-kort."</string>
     <string name="usb_storage_button_mount" msgid="8063426289195405456">"Monter"</string>
     <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Indsæt ikke"</string>
     <string name="usb_storage_error_message" msgid="2534784751603345363">"Der opstod et problem med at bruge dit SD-kort til USB-lagring."</string>
-    <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB er tilsluttet"</string>
+    <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB forbundet"</string>
     <string name="usb_storage_notification_message" msgid="7380082404288219341">"Vælg for at kopiere filer til/fra din computer."</string>
     <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Slå USB-lagringen fra"</string>
     <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Vælg for at slå USB-lagring fra."</string>
     <string name="usb_storage_stop_title" msgid="6014127947456185321">"Slå USB-lagring fra"</string>
-    <string name="usb_storage_stop_message" msgid="2390958966725232848">"Inden du slår USB-lagringen fra, skal du sørge for, at USB-værten er demonteret. Vælg \"Slå fra\" for at slå USB-lagringen fra."</string>
+    <string name="usb_storage_stop_message" msgid="2390958966725232848">"Inden du slår USB-lagringen fra, skal du sørge for, at du demonterer USB-værten. Vælg \"Slå fra\" for at slå USB-lagringen fra."</string>
     <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Slå fra"</string>
     <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Annuller"</string>
     <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Der opstod et problem med at slå USB-lagringen fra. Sørg for, at du har demonteret USB-værten, og prøv så igen."</string>
     <string name="extmedia_format_title" msgid="8663247929551095854">"Formater SD-kort"</string>
     <string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formater"</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-fejlretning er tilsluttet"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-fejlretning forbundet"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"Vælg for at deaktivere USB-fejlretning."</string>
     <string name="select_input_method" msgid="6865512749462072765">"Vælg indtastningsmetode"</string>
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string>
-    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Forbereder SD-kortet"</string>
+    <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"Forbereder SD-kort"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Kontrollerer for fejl."</string>
-    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Tomt SD-kort"</string>
+    <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"Blankt SD-kort"</string>
     <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD-kortet er tomt eller har et ikke understøttet filsystem."</string>
     <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"Beskadiget SD-kort"</string>
-    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-kortet er beskadiget. Du bliver muligvis nødt til at formatere det igen."</string>
-    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-kortet blev fjernet uventet"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD-kort beskadiget. Du bliver muligvis nødt til at omformatere det."</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD-kort blev uventet fjernet"</string>
     <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"Demonter SD-kortet inden fjernelse for at undgå tab af data."</string>
     <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD-kortet kan fjernes sikkert"</string>
     <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"Du kan nu fjerne SD-kortet."</string>
@@ -769,19 +766,23 @@
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"opdater brugerstatistikker for komponenter"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillader ændring af indsamlede brugerstatistikker for komponenter. Ikke til brug til normale programmer."</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tryk to gange for zoomkontrol"</string>
-    <string name="gadget_host_error_inflating" msgid="2613287218853846830">"Der opstod en fejl under forøgelsen af widgetten"</string>
+    <string name="gadget_host_error_inflating" msgid="2613287218853846830">"Der opstod en fejl under forøgelsen af widgeten"</string>
     <string name="ime_action_go" msgid="8320845651737369027">"Gå"</string>
     <string name="ime_action_search" msgid="658110271822807811">"Søg"</string>
     <string name="ime_action_send" msgid="2316166556349314424">"Send"</string>
     <string name="ime_action_next" msgid="3138843904009813834">"Næste"</string>
-    <string name="ime_action_done" msgid="8971516117910934605">"Udfør"</string>
+    <string name="ime_action_done" msgid="8971516117910934605">"Færdig"</string>
     <string name="ime_action_default" msgid="2840921885558045721">"Udfør"</string>
     <string name="dial_number_using" msgid="5789176425167573586">"Ring til nummer"\n"ved hjælp af <xliff:g id="NUMBER">%s</xliff:g>"</string>
     <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>
@@ -795,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 d59d3a7..167faa0 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -177,7 +177,7 @@
     <string name="permdesc_writeSms" msgid="6299398896177548095">"Ermöglicht einer Anwendung, auf Ihrem Telefon oder Ihrer SIM-Karte gespeicherte Kurznachrichten zu bearbeiten. Schädliche Anwendungen löschen möglicherweise Ihre Nachrichten."</string>
     <string name="permlab_receiveWapPush" msgid="8258226427716551388">"WAP-Nachrichten empfangen"</string>
     <string name="permdesc_receiveWapPush" msgid="5979623826128082171">"Ermöglicht der Anwendung, WAP-Mitteilungen zu empfangen und zu verarbeiten. Schädliche Anwendungen können Ihre Nachrichten möglicherweise überwachen oder löschen, bevor sie angezeigt werden."</string>
-    <string name="permlab_getTasks" msgid="5005277531132573353">"laufende Anwendungen abrufen"</string>
+    <string name="permlab_getTasks" msgid="5005277531132573353">"Laufende Anwendungen abrufen"</string>
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Ermöglicht der Anwendung, Informationen zu aktuellen und kürzlich ausführten Aufgaben abzurufen. Schädliche Anwendungen können so eventuell geheime Informationen zu anderen Anwendungen entdecken."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"Laufende Anwendungen neu ordnen"</string>
     <string name="permdesc_reorderTasks" msgid="126252774270522835">"Ermöglicht einer Anwendung, Aufgaben in den Vorder- und Hintergrund zu verschieben. Schädliche Anwendungen können so ohne Ihr Zutun eine Anzeige im Vordergrund erzwingen."</string>
@@ -253,9 +253,9 @@
     <string name="permdesc_diagnostic" msgid="3121238373951637049">"Ermöglicht einer Anwendung, alle Elemente in der Diagnosegruppe zu lesen und zu bearbeiten, etwa Dateien in \"/dev\". Dies könnte eine potenzielle Gefährdung für die Stabilität und Sicherheit des Systems darstellen und sollte NUR für Hardware-spezifische Diagnosen des Herstellers oder Netzbetreibers verwendet werden."</string>
     <string name="permlab_changeComponentState" msgid="79425198834329406">"Anwendungskomponenten aktivieren oder deaktivieren"</string>
     <string name="permdesc_changeComponentState" msgid="4569107043246700630">"Ermöglicht einer Anwendung, die Komponente einer anderen Anwendung nach Belieben zu aktivieren oder zu deaktivieren. Schädliche Anwendungen können so wichtige Funktionen des Telefons deaktivieren. Bei der Erteilung von Berechtigungen ist daher Vorsicht geboten, da die Anwendungskomponenten unbrauchbar, inkonsistent und unstabil werden können."</string>
-    <string name="permlab_setPreferredApplications" msgid="3393305202145172005">"bevorzugte Einstellungen festlegen"</string>
+    <string name="permlab_setPreferredApplications" msgid="3393305202145172005">"Bevorzugte Einstellungen festlegen"</string>
     <string name="permdesc_setPreferredApplications" msgid="760008293501937546">"Ermöglicht einer Anwendung, Ihre bevorzugten Einstellungen zu ändern. Schädliche Anwendungen können so laufende Anwendungen ohne Ihr Wissen ändern, damit die vorhandenen Anwendungen private Daten von Ihnen sammeln."</string>
-    <string name="permlab_writeSettings" msgid="1365523497395143704">"allgemeine Systemeinstellungen ändern"</string>
+    <string name="permlab_writeSettings" msgid="1365523497395143704">"Allgemeine Systemeinstellungen ändern"</string>
     <string name="permdesc_writeSettings" msgid="838789419871034696">"Ermöglicht einer Anwendung, die Einstellungsdaten des Systems zu ändern. Schädliche Anwendungen können so die Systemkonfiguration beschädigen."</string>
     <string name="permlab_writeSecureSettings" msgid="204676251876718288">"Sicherheitseinstellungen für das System ändern"</string>
     <string name="permdesc_writeSecureSettings" msgid="5497873143539034724">"Der Anwendung wird das Ändern der Sicherheitseinstellungsdaten des Systems ermöglicht. Nicht für normale Anwendungen vorgesehen."</string>
@@ -353,7 +353,7 @@
     <string name="permdesc_useCredentials" msgid="7416570544619546974">"Ermöglicht einer Anwendung, Authentifizierungs-Token anzufordern."</string>
     <string name="permlab_accessNetworkState" msgid="6865575199464405769">"Netzwerkstatus anzeigen"</string>
     <string name="permdesc_accessNetworkState" msgid="558721128707712766">"Ermöglicht einer Anwendung, den Status aller Netzwerke anzuzeigen."</string>
-    <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"uneingeschränkter Internetzugriff"</string>
+    <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"Uneingeschränkter Internetzugriff"</string>
     <string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Ermöglicht einer Anwendung, Netzwerk-Sockets einzurichten."</string>
     <string name="permlab_writeApnSettings" msgid="7823599210086622545">"Einstellungen für Zugriffspunktname schreiben"</string>
     <string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Ermöglicht einer Anwendung, die APN-Einstellungen wie Proxy und Port eines Zugriffspunkts zu ändern."</string>
@@ -379,11 +379,11 @@
     <string name="permdesc_writeSyncSettings" msgid="2498201614431360044">"Ermöglicht einer Anwendung, die Synchronisierungseinstellungen zu ändern, etwa ob die Synchronisierung für Kontakte aktiviert ist oder nicht."</string>
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"Synchronisierungsstatistiken lesen"</string>
     <string name="permdesc_readSyncStats" msgid="7511448343374465000">"Ermöglicht einer Anwendung, die Synchronisierungsstatistiken zu lesen, etwa den Verlauf der bereits durchgeführten Synchronisierungen."</string>
-    <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"abonnierte Feeds lesen"</string>
+    <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"Abonnierte Feeds lesen"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="3622200625634207660">"Ermöglicht einer Anwendung, Details zu den zurzeit synchronisierten Feeds abzurufen."</string>
-    <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"abonnierte Feeds schreiben"</string>
+    <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"Abonnierte Feeds schreiben"</string>
     <string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"Ermöglicht einer Anwendung, Änderungen an den kürzlich synchronisierten Feeds vorzunehmen. Schädliche Anwendungen könnten so Ihre synchronisierten Feeds ändern."</string>
-    <string name="permlab_readDictionary" msgid="432535716804748781">"nutzerdefiniertes Wörterbuch lesen"</string>
+    <string name="permlab_readDictionary" msgid="432535716804748781">"Nutzerdefiniertes Wörterbuch lesen"</string>
     <string name="permdesc_readDictionary" msgid="1082972603576360690">"Erlaubt einer Anwendung, alle privaten Wörter, Namen und Ausdrücke zu lesen, die ein Nutzer in seinem Wörterbuch gespeichert hat."</string>
     <string name="permlab_writeDictionary" msgid="6703109511836343341">"in nutzerdefiniertes Wörterbuch schreiben"</string>
     <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Erlaubt einer Anwendung, neue Wörter in das Wörterbuch des Nutzers zu schreiben."</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Anmelden"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Ungültiger  Nutzername oder ungültiges Passwort."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Überprüfung..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Entsperren"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Ton ein"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Ton aus"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Löschen"</string>
@@ -780,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>
@@ -795,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 c423033..5b02a65 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Σύνδεση"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Μη έγκυρο όνομα χρήστη ή κωδικός πρόσβασης."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Έλεγχος..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Ξεκλείδωμα"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Ενεργοποίηση ήχου"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Απενεργοποίηση ήχου"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Εκκαθάριση"</string>
@@ -780,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>
@@ -795,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 6893979..e0408b3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Inicia sesión"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nombre de usuario o contraseña incorrecta."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Comprobando..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Sonido encendido"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Sonido apagado"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
@@ -780,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>
@@ -795,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 a5890a0..fa6160a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -101,7 +101,7 @@
     <string name="httpErrorUnsupportedAuthScheme" msgid="2781440683514730227">"No se admite el esquema de autenticación del sitio."</string>
     <string name="httpErrorAuth" msgid="7293960746955020542">"La autenticación no se ha realizado correctamente."</string>
     <string name="httpErrorProxyAuth" msgid="1788207010559081331">"La autenticación mediante el servidor proxy no se ha realizado correctamente."</string>
-    <string name="httpErrorConnect" msgid="7623096283505770433">"La conexión con el servidor no se ha realizado correctamente."</string>
+    <string name="httpErrorConnect" msgid="7623096283505770433">"La conexión al servidor no se ha realizado correctamente."</string>
     <string name="httpErrorIO" msgid="5047872902739125260">"El servidor no ha podido establecer la comunicación. Vuelve a intentarlo más tarde."</string>
     <string name="httpErrorTimeout" msgid="4743403703762883954">"Se ha agotado el tiempo de espera de conexión al servidor."</string>
     <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"La página contiene demasiados redireccionamientos de servidor."</string>
@@ -156,7 +156,7 @@
     <string name="permgrouplab_systemTools" msgid="4652191644082714048">"Herramientas del sistema"</string>
     <string name="permgroupdesc_systemTools" msgid="8162102602190734305">"Acceso de nivel inferior y control del sistema"</string>
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"Herramientas de desarrollo"</string>
-    <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funciones necesarias solo para desarrolladores de aplicaciones"</string>
+    <string name="permgroupdesc_developmentTools" msgid="9056431193893809814">"Funciones necesarias sólo para desarrolladores de aplicaciones"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Almacenamiento"</string>
     <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"inhabilitar o modificar la barra de estado"</string>
@@ -180,7 +180,7 @@
     <string name="permlab_getTasks" msgid="5005277531132573353">"recuperar aplicaciones en ejecución"</string>
     <string name="permdesc_getTasks" msgid="7048711358713443341">"Permite que la aplicación recupere información sobre tareas que se están ejecutando en este momento o que se han ejecutado recientemente. Puede permitir que las aplicaciones malintencionadas vean información privada sobre otras aplicaciones."</string>
     <string name="permlab_reorderTasks" msgid="5669588525059921549">"reorganizar aplicaciones en ejecución"</string>
-    <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite que una aplicación mueva tareas a segundo plano y a primer plano. Las aplicaciones malintencionadas pueden aparecer en primer plano sin tu control."</string>
+    <string name="permdesc_reorderTasks" msgid="126252774270522835">"Permite que una aplicación mueva tareas a segundo plano y a primer plano. Las aplicaciones malintencionadas pueden aparecer en primer plano sin su control."</string>
     <string name="permlab_setDebugApp" msgid="4339730312925176742">"habilitar depuración de aplicación"</string>
     <string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite que una aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para desactivar otras aplicaciones."</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar la configuración de la interfaz de usuario"</string>
@@ -196,7 +196,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"evitar cambios de aplicación"</string>
     <string name="permdesc_stopAppSwitches" msgid="3857886086919033794">"Evita que el usuario cambie a otra aplicación."</string>
     <string name="permlab_runSetActivityWatcher" msgid="7811586187574696296">"supervisar y controlar la ejecución de todas las aplicaciones"</string>
-    <string name="permdesc_runSetActivityWatcher" msgid="3228701938345388092">"Permite que una aplicación supervise y controle la ejecución de las actividades por parte del sistema. Las aplicaciones malintencionadas pueden vulnerar la seguridad del sistema. Este permiso solo es necesario para tareas de desarrollo, nunca para el uso habitual del teléfono."</string>
+    <string name="permdesc_runSetActivityWatcher" msgid="3228701938345388092">"Permite que una aplicación supervise y controle la ejecución de las actividades por parte del sistema. Las aplicaciones malintencionadas pueden vulnerar la seguridad del sistema. Este permiso sólo es necesario para tareas de desarrollo, nunca para el uso habitual del teléfono."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar emisión eliminada de paquete"</string>
     <string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"Permite que una aplicación emita una notificación de que se ha eliminado un paquete de aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para interrumpir la ejecución de cualquier otra aplicación."</string>
     <string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"enviar una emisión recibida mediante SMS"</string>
@@ -284,7 +284,7 @@
     <string name="permlab_installLocationProvider" msgid="6578101199825193873">"permiso para instalar un proveedor de ubicación"</string>
     <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"Crear fuentes de origen simuladas para realizar pruebas. Las aplicaciones malintencionadas pueden utilizar este permiso para sobrescribir la ubicación o el estado devueltos por orígenes de ubicación reales, tales como los proveedores de red o GPS, o para controlar y notificar tu ubicación a una fuente externa."</string>
     <string name="permlab_accessFineLocation" msgid="8116127007541369477">"precisar la ubicación (GPS)"</string>
-    <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"Permite precisar las fuentes de ubicación como, por ejemplo, el sistema de posicionamiento global, en el teléfono, en los casos en que estén disponibles. Las aplicaciones malintencionadas pueden utilizar este permiso para determinar dónde se encuentra el usuario y pueden consumir batería adicional."</string>
+    <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"Permite precisar las fuentes de ubicación como, por ejemplo, el sistema de posicionamiento global, en el teléfono, en los casos en que estén disponibles. Las aplicaciones malintencionadas pueden utilizar este permiso para determinar dónde se encuentra en usuario y pueden consumir batería adicional."</string>
     <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"ubicación común (basada en red)"</string>
     <string name="permdesc_accessCoarseLocation" msgid="8235655958070862293">"Acceder a fuentes de ubicación comunes como, por ejemplo, la base de datos de red de un teléfono móvil, para determinar una ubicación telefónica aproximada, en los casos en que esté disponible. Las aplicaciones malintencionadas pueden utilizar este permiso para determinar dónde te encuentras aproximadamente."</string>
     <string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"acceder a SurfaceFlinger"</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Acceder"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nombre de usuario o contraseña no válido"</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Comprobando..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Activar sonido"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Desactivar sonido"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
@@ -536,7 +533,7 @@
     <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> o menos disponible"</string>
     <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string>
-    <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST solo es compatible con los paquetes instalados en /system/app."</string>
+    <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST sólo es compatible con los paquetes instalados en /system/app."</string>
     <string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string>
     <string name="factorytest_reboot" msgid="6320168203050791643">"Reiniciar"</string>
     <string name="js_dialog_title" msgid="8143918455087008109">"La página \"<xliff:g id="TITLE">%s</xliff:g>\" dice:"</string>
@@ -780,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>
@@ -795,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 7128528..75f7406 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Se connecter"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nom d\'utilisateur ou mot de passe incorrect."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Vérification..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Débloquer"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Son activé"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Son désactivé"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Effacer"</string>
@@ -780,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>
@@ -795,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 d9b6ca4..f89b9ad 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Accedi"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Password o nome utente non valido."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Controllo in corso..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Sblocca"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Audio attivato"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Audio disattivato"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Cancella"</string>
@@ -780,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>
@@ -795,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 d8bffe2..e26ece2 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"ログイン"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"ユーザー名またはパスワードが正しくありません。"</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"確認中..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"ロック解除"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"サウンドON"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"サウンドOFF"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"通知を消去"</string>
@@ -726,7 +723,7 @@
     <string name="sms_control_no" msgid="1715320703137199869">"キャンセル"</string>
     <string name="date_time_set" msgid="5777075614321087758">"設定"</string>
     <string name="default_permission_group" msgid="2690160991405646128">"端末既定"</string>
-    <string name="no_permissions" msgid="7283357728219338112">"権限の許可は必要ありません"</string>
+    <string name="no_permissions" msgid="7283357728219338112">"権限付与の必要はありません"</string>
     <string name="perms_hide" msgid="7283915391320676226"><b>"隠す"</b></string>
     <string name="perms_show_all" msgid="2671791163933091180"><b>"すべて表示"</b></string>
     <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"読み込み中..."</string>
@@ -780,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>
@@ -795,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 63b2461..fc156b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"로그인"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"사용자 이름 또는 비밀번호가 잘못되었습니다."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"확인 중..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"잠금해제"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"사운드 켜기"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"사운드 끄기"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"지우기"</string>
@@ -780,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>
@@ -795,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 1546ddb..c0aae98 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Logg på"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Ugyldig brukernavn eller passord."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Kontrollerer ..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Lås opp"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Lyd på"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Lyd av"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Fjern"</string>
@@ -780,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>
@@ -795,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 37f6e0a..9b95c09 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -147,8 +147,10 @@
     <string name="permgroupdesc_location" msgid="2430258821648348660">"Uw fysieke locatie bijhouden"</string>
     <string name="permgrouplab_network" msgid="5808983377727109831">"Netwerkcommunicatie"</string>
     <string name="permgroupdesc_network" msgid="5035763698958415998">"Toepassingen toestaan verschillende netwerkfuncties te openen."</string>
-    <string name="permgrouplab_accounts" msgid="3359646291125325519">"Uw accounts"</string>
-    <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Toegang tot de beschikbare accounts."</string>
+    <!-- no translation found for permgrouplab_accounts (3359646291125325519) -->
+    <skip />
+    <!-- no translation found for permgroupdesc_accounts (4948732641827091312) -->
+    <skip />
     <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Bedieningselementen hardware"</string>
     <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Rechtstreekse toegang tot hardware op de handset."</string>
     <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Telefoonoproepen"</string>
@@ -453,9 +455,12 @@
     <string name="phoneTypeWorkPager" msgid="649938731231157056">"Pager werk"</string>
     <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistent"</string>
     <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
-    <string name="eventTypeBirthday" msgid="2813379844211390740">"Verjaardag"</string>
-    <string name="eventTypeAnniversary" msgid="3876779744518284000">"Jubileum"</string>
-    <string name="eventTypeOther" msgid="5834288791948564594">"Afspraak"</string>
+    <!-- no translation found for eventTypeBirthday (2813379844211390740) -->
+    <skip />
+    <!-- no translation found for eventTypeAnniversary (3876779744518284000) -->
+    <skip />
+    <!-- no translation found for eventTypeOther (5834288791948564594) -->
+    <skip />
     <string name="emailTypeCustom" msgid="8525960257804213846">"Aangepast"</string>
     <string name="emailTypeHome" msgid="449227236140433919">"Thuis"</string>
     <string name="emailTypeWork" msgid="3548058059601149973">"Werk"</string>
@@ -520,9 +525,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Aanmelden"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Gebruikersnaam of wachtwoord ongeldig."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Controleren..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Ontgrendelen"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Geluid aan"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Geluid uit"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string>
@@ -780,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>
@@ -791,8 +797,14 @@
     <string name="accessibility_binding_label" msgid="4148120742096474641">"Toegankelijkheid"</string>
     <string name="wallpaper_binding_label" msgid="1240087844304687662">"Achtergrond"</string>
     <string name="chooser_wallpaper" msgid="7873476199295190279">"Achtergrond wijzigen"</string>
-    <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point Tunneling Protocol (PPTP)"</string>
-    <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol (L2TP)"</string>
-    <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Vooraf gedeelde sleutel op basis van L2TP/IPSec VPN"</string>
-    <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificaat op basis van L2TP/IPSec VPN"</string>
+    <!-- no translation found for pptp_vpn_description (2688045385181439401) -->
+    <skip />
+    <!-- no translation found for l2tp_vpn_description (3750692169378923304) -->
+    <skip />
+    <!-- no translation found for l2tp_ipsec_psk_vpn_description (3945043564008303239) -->
+    <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 633088d..6ee52ed 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Zaloguj"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Błędna nazwa użytkownika lub hasło."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Trwa sprawdzanie..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Odblokuj"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Włącz dźwięk"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Wyłącz dźwięk"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wyczyść"</string>
@@ -596,7 +593,7 @@
   </plurals>
   <plurals name="abbrev_num_seconds_ago">
     <item quantity="one" msgid="1849036840200069118">"sekundę temu"</item>
-    <item quantity="other" msgid="3699169366650930415">"<xliff:g id="COUNT">%d</xliff:g> s temu"</item>
+    <item quantity="other" msgid="3699169366650930415">"<xliff:g id="COUNT">%d</xliff:g> sek. temu"</item>
   </plurals>
   <plurals name="abbrev_num_minutes_ago">
     <item quantity="one" msgid="6361490147113871545">"minutę temu"</item>
@@ -612,7 +609,7 @@
   </plurals>
   <plurals name="abbrev_in_num_seconds">
     <item quantity="one" msgid="5842225370795066299">"za sekundę"</item>
-    <item quantity="other" msgid="5495880108825805108">"za <xliff:g id="COUNT">%d</xliff:g> s"</item>
+    <item quantity="other" msgid="5495880108825805108">"za <xliff:g id="COUNT">%d</xliff:g> sek."</item>
   </plurals>
   <plurals name="abbrev_in_num_minutes">
     <item quantity="one" msgid="562786149928284878">"za minutę"</item>
@@ -780,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>
@@ -795,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 a494a7a..c86fbdb 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Iniciar sessão"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de utilizador ou palavra-passe inválidos."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"A verificar..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Som activado"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Som desactivado"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
@@ -780,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>
@@ -795,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 1bae99e..70f82ee 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -390,7 +390,7 @@
     <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/excluir conteúdo do cartão SD"</string>
     <string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que um aplicativo grave no cartão SD."</string>
   <string-array name="phoneTypes">
-    <item msgid="8901098336658710359">"Residencial"</item>
+    <item msgid="8901098336658710359">"Página inicial"</item>
     <item msgid="869923650527136615">"Celular"</item>
     <item msgid="7897544654242874543">"Trabalho"</item>
     <item msgid="1103601433382158155">"Fax do trabalho"</item>
@@ -400,19 +400,19 @@
     <item msgid="9192514806975898961">"Personalizado"</item>
   </string-array>
   <string-array name="emailAddressTypes">
-    <item msgid="8073994352956129127">"Residencial"</item>
+    <item msgid="8073994352956129127">"Página inicial"</item>
     <item msgid="7084237356602625604">"Trabalho"</item>
     <item msgid="1112044410659011023">"Outros"</item>
     <item msgid="2374913952870110618">"Personalizado"</item>
   </string-array>
   <string-array name="postalAddressTypes">
-    <item msgid="6880257626740047286">"Residencial"</item>
+    <item msgid="6880257626740047286">"Página inicial"</item>
     <item msgid="5629153956045109251">"Trabalho"</item>
     <item msgid="4966604264500343469">"Outros"</item>
     <item msgid="4932682847595299369">"Personalizado"</item>
   </string-array>
   <string-array name="imAddressTypes">
-    <item msgid="1738585194601476694">"Residencial"</item>
+    <item msgid="1738585194601476694">"Página inicial"</item>
     <item msgid="1359644565647383708">"Trabalho"</item>
     <item msgid="7868549401053615677">"Outros"</item>
     <item msgid="3145118944639869809">"Personalizado"</item>
@@ -433,7 +433,7 @@
     <item msgid="1648797903785279353">"Jabber"</item>
   </string-array>
     <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string>
-    <string name="phoneTypeHome" msgid="2570923463033985887">"Residencial"</string>
+    <string name="phoneTypeHome" msgid="2570923463033985887">"Página inicial"</string>
     <string name="phoneTypeMobile" msgid="6501463557754751037">"Celular"</string>
     <string name="phoneTypeWork" msgid="8863939667059911633">"Comercial"</string>
     <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax comercial"</string>
@@ -457,16 +457,16 @@
     <string name="eventTypeAnniversary" msgid="3876779744518284000">"Data comemorativa"</string>
     <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string>
     <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string>
-    <string name="emailTypeHome" msgid="449227236140433919">"Residencial"</string>
+    <string name="emailTypeHome" msgid="449227236140433919">"Página inicial"</string>
     <string name="emailTypeWork" msgid="3548058059601149973">"Comercial"</string>
     <string name="emailTypeOther" msgid="2923008695272639549">"Outros"</string>
     <string name="emailTypeMobile" msgid="119919005321166205">"Celular"</string>
     <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizado"</string>
-    <string name="postalTypeHome" msgid="8165756977184483097">"Residencial"</string>
+    <string name="postalTypeHome" msgid="8165756977184483097">"Página inicial"</string>
     <string name="postalTypeWork" msgid="5268172772387694495">"Comercial"</string>
     <string name="postalTypeOther" msgid="2726111966623584341">"Outros"</string>
     <string name="imTypeCustom" msgid="2074028755527826046">"Personalizado"</string>
-    <string name="imTypeHome" msgid="6241181032954263892">"Residencial"</string>
+    <string name="imTypeHome" msgid="6241181032954263892">"Página inicial"</string>
     <string name="imTypeWork" msgid="1371489290242433090">"Comercial"</string>
     <string name="imTypeOther" msgid="5377007495735915478">"Outros"</string>
     <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizado"</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Fazer login"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de usuário ou senha inválida."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Verificando..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Som ativado"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Som desativado"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
@@ -780,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>
@@ -795,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 be24f4e..56b75fc 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Вход"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Неверное имя пользователя или пароль."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Проверка..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Разблокировать"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Вкл. звук"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Откл. звук"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Очистить"</string>
@@ -780,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>
@@ -795,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 fbbcb62..3e32b15 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -486,12 +486,12 @@
     <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Fel PIN-kod!"</string>
-    <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
+    <string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Meny och sedan på 0 om du vill låsa upp."</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
     <string name="lockscreen_carrier_default" msgid="8812714795156374435">"(Ingen tjänst)"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Skärmen har låsts."</string>
-    <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tryck på Menu om du vill låsa upp eller ringa nödsamtal."</string>
-    <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryck på Menu om du vill låsa upp."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Tryck på Meny om du vill låsa upp eller ringa nödsamtal."</string>
+    <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryck på Meny om du vill låsa upp."</string>
     <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Rita grafiskt lösenord för att låsa upp"</string>
     <string name="lockscreen_emergency_call" msgid="5347633784401285225">"Nödsamtal"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Logga in"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Ogiltigt användarnamn eller lösenord."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Kontrollerar ..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Lås upp"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Ljud på"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Ljud av"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ta bort"</string>
@@ -555,7 +552,7 @@
     <string name="open_permission_deny" msgid="5661861460947222274">"Du har inte behörighet att öppna den här sidan."</string>
     <string name="text_copied" msgid="4985729524670131385">"Text har kopierats till urklipp."</string>
     <string name="more_item_label" msgid="4650918923083320495">"Mer"</string>
-    <string name="prepend_shortcut_label" msgid="2572214461676015642">"Menu+"</string>
+    <string name="prepend_shortcut_label" msgid="2572214461676015642">"Meny+"</string>
     <string name="menu_space_shortcut_label" msgid="2410328639272162537">"utrymme"</string>
     <string name="menu_enter_shortcut_label" msgid="2743362785111309668">"retur"</string>
     <string name="menu_delete_shortcut_label" msgid="3658178007202748164">"ta bort"</string>
@@ -780,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>
@@ -795,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 687fe36..cae0c03 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -311,9 +311,9 @@
     <string name="permdesc_flashlight" msgid="6433045942283802309">"Uygulamaların flaş ışığını denetlemesine izin verir."</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"donanımı test et"</string>
     <string name="permdesc_hardware_test" msgid="3668894686500081699">"Uygulamanın donanım testi için çeşitli çevre birimlerini denetlemesine izin verir."</string>
-    <string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarına doğrudan çağrı yap"</string>
-    <string name="permdesc_callPhone" msgid="3369867353692722456">"Uygulamanın müdahaleniz olmadan telefon numaralarına çağrı yapmasına izin verir. Kötü amaçlı uygulamalar, telefon faturanızda beklenmedik görüşmeler çıkmasına neden olabilir. Bu işlev, uygulamanın acil numaralara çağrı yapmasına izin vermez."</string>
-    <string name="permlab_callPrivileged" msgid="4198349211108497879">"herhangi bir telefon numarasına doğrudan çağrı yap"</string>
+    <string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarını doğrudan ara"</string>
+    <string name="permdesc_callPhone" msgid="3369867353692722456">"Uygulamanın müdahaleniz olmadan telefon numaralarını aramasına izin verir. Kötü amaçlı uygulamalar, telefon faturanızda beklenmedik görüşmeler çıkmasına neden olabilir. Bu işlev, uygulamanın acil numara aramasına izin vermez."</string>
+    <string name="permlab_callPrivileged" msgid="4198349211108497879">"herhangi bir telefon numarasını doğrudan ara"</string>
     <string name="permdesc_callPrivileged" msgid="244405067160028452">"Uygulamanın, siz müdahale etmeden acil numaralar dahil herhangi bir numarayı aramasına izin verir. Kötü amaçlı uygulamalar acil servisleri gereksiz yere ve yasal olmayan şekilde arayabilir."</string>
     <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"CDMA telefon kurulumunu doğrudan başlat"</string>
     <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Uygulamaya CDMA hazırlığını başlatma izni verir. Kötü niyetli uygulamalar CDMA hazırlığını gereksiz yere başlatabilir"</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Oturum aç"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Geçersiz kullanıcı adı veya şifre."</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Kontrol ediliyor..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"Kilit Aç"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Sesi aç"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"Sesi kapat"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Temizle"</string>
@@ -658,7 +655,7 @@
     <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Tümünü seç"</string>
-    <string name="selectText" msgid="3889149123626888637">"Metin seç"</string>
+    <string name="selectText" msgid="3889149123626888637">"Metni seç"</string>
     <string name="stopSelectingText" msgid="4157931463872320996">"Metin seçmeyi durdur"</string>
     <string name="cut" msgid="3092569408438626261">"Kes"</string>
     <string name="cutAll" msgid="2436383270024931639">"Tümünü kes"</string>
@@ -780,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>
@@ -795,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 71c5751..6620a60 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -42,10 +42,10 @@
     <string name="invalidPin" msgid="3850018445187475377">"输入一个 4 至 8 位数的 PIN。"</string>
     <string name="needPuk" msgid="919668385956251611">"您的 SIM 卡被 PUK 锁定。请输入 PUK 码进行解锁。"</string>
     <string name="needPuk2" msgid="4526033371987193070">"输入 PUK2 以解锁 SIM 卡。"</string>
-    <string name="ClipMmi" msgid="6952821216480289285">"来电显示"</string>
-    <string name="ClirMmi" msgid="7784673673446833091">"本机号码"</string>
-    <string name="CfMmi" msgid="5123218989141573515">"来电转接"</string>
-    <string name="CwMmi" msgid="9129678056795016867">"来电等待"</string>
+    <string name="ClipMmi" msgid="6952821216480289285">"来电者信息"</string>
+    <string name="ClirMmi" msgid="7784673673446833091">"外拨者信息"</string>
+    <string name="CfMmi" msgid="5123218989141573515">"呼叫转接"</string>
+    <string name="CwMmi" msgid="9129678056795016867">"呼叫等待"</string>
     <string name="BaMmi" msgid="455193067926770581">"呼叫限制"</string>
     <string name="PwdMmi" msgid="7043715687905254199">"密码更改"</string>
     <string name="PinMmi" msgid="3113117780361190304">"PIN 码更改"</string>
@@ -55,12 +55,12 @@
     <string name="RuacMmi" msgid="7827887459138308886">"拒绝不想接听的骚扰电话"</string>
     <string name="CndMmi" msgid="3116446237081575808">"主叫号码传送"</string>
     <string name="DndMmi" msgid="1265478932418334331">"请勿打扰"</string>
-    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"默认不显示本机号码,在下一次通话中也不显示"</string>
-    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"默认不显示本机号码,但在下一次通话中显示"</string>
-    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"默认显示本机号码,但在下一次通话中不显示"</string>
-    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"默认显示本机号码,在下一次通话中也显示"</string>
+    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"来电者信息在默认情况下受限制。在下一次通话中:受限制"</string>
+    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"来电者信息在默认情况受限制。在下一次通话中:不受限制"</string>
+    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"来电者信息在默认情况下不受限制。在下一次通话中:受限制"</string>
+    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"来电者信息在默认情况下不受限制。在下一次通话中:不受限制"</string>
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"未提供服务。"</string>
-    <string name="CLIRPermanent" msgid="5460892159398802465">"本机号码显示设置无法更改。"</string>
+    <string name="CLIRPermanent" msgid="5460892159398802465">"无法更改来电者信息设置。"</string>
     <string name="RestrictedChangedTitle" msgid="5592189398956187498">"访问受限情况已发生变化"</string>
     <string name="RestrictedOnData" msgid="8653794784690065540">"数据服务已禁用。"</string>
     <string name="RestrictedOnEmergency" msgid="6581163779072833665">"紧急服务已禁用。"</string>
@@ -131,10 +131,10 @@
     <string name="global_action_power_off" msgid="4471879440839879722">"关机"</string>
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"静音模式"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"已关闭声音"</string>
-    <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"已开启声音"</string>
+    <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"已打开声音"</string>
     <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"飞行模式"</string>
-    <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"已开启飞行模式"</string>
-    <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"已关闭飞行模式"</string>
+    <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"飞行模式已开启"</string>
+    <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"飞行模式已关闭"</string>
     <string name="safeMode" msgid="2788228061547930246">"安全模式"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Android 系统"</string>
     <string name="permgrouplab_costMoney" msgid="5429808217861460401">"需要您付费的服务"</string>
@@ -163,8 +163,8 @@
     <string name="permdesc_statusBar" msgid="1365473595331989732">"允许应用程序停用状态栏,或者添加和删除系统图标。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/折叠状态栏"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允许应用程序展开或折叠状态栏。"</string>
-    <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截拨出电话"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"允许应用程序处理拨出电话和变更拨号。恶意应用程序可借此监视、重定向或阻止电话拨出。"</string>
+    <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string>
+    <string name="permdesc_processOutgoingCalls" msgid="2228988201852654461">"允许应用程序处理外拨电话和更改要拨打的号码。恶意应用程序可借此监视、重定向或阻止外拨电话。"</string>
     <string name="permlab_receiveSms" msgid="2697628268086208535">"接收短信"</string>
     <string name="permdesc_receiveSms" msgid="6298292335965966117">"允许应用程序接收和处理短信。恶意应用程序可借此监视您的信息,或者将信息删除而不向您显示。"</string>
     <string name="permlab_receiveMms" msgid="8894700916188083287">"接收彩信"</string>
@@ -277,12 +277,12 @@
     <string name="permdesc_readCalendar" msgid="5533029139652095734">"允许应用程序读取您手机上存储的所有日历活动。恶意应用程序可借此将您的日历活动发送给其他人。"</string>
     <string name="permlab_writeCalendar" msgid="377926474603567214">"写入日历数据"</string>
     <string name="permdesc_writeCalendar" msgid="8674240662630003173">"允许应用程序修改您手机上存储的日历活动。恶意应用程序可借此清除或修改您的日历数据。"</string>
-    <string name="permlab_accessMockLocation" msgid="8688334974036823330">"使用模拟地点来源进行测试"</string>
-    <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"创建模拟地点来源进行测试。恶意应用程序可能利用此选项覆盖由真实地点来源(如 GPS 或网络提供商)传回的地点和/或状态。"</string>
+    <string name="permlab_accessMockLocation" msgid="8688334974036823330">"禁止使用位置来源进行测试"</string>
+    <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"创建用于测试的模拟位置源。恶意应用程序可借此替代真正的位置源(如 GPS 或网络提供商)返回的位置和/或状态。"</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"访问更多位置提供者的命令"</string>
     <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"访问额外的位置提供程序命令。恶意应用程序可借此干扰 GPS 或其他位置源的运作。"</string>
     <string name="permlab_installLocationProvider" msgid="6578101199825193873">"安装位置信息提供程序"</string>
-    <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"创建模拟地点来源进行测试。恶意应用程序可能利用此选项覆盖由真实地点来源(如 GPS 或网络提供商)所传回的地点和/或状态,或者监视您的位置并将其提供给外部来源。"</string>
+    <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"创建用于测试的模拟地点信息源。恶意应用程序可借此覆盖真正的地点信息源(如 GPS 或网络提供商)返回的地点和/或状态,或者监视您的地点并向外部信息源报告。"</string>
     <string name="permlab_accessFineLocation" msgid="8116127007541369477">"精准位置 (GPS)"</string>
     <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"访问精准的位置源,例如手机上的全球定位系统(如果适用)。恶意应用程序可能借此确定您所处的位置,并消耗额外的电池电量。"</string>
     <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"粗略位置(以网络为基础)"</string>
@@ -318,13 +318,13 @@
     <string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"直接启动 CDMA 电话设置"</string>
     <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"允许应用程序启动 CDMA 服务。恶意应用程序可能会无端启动 CDMA 服务"</string>
     <string name="permlab_locationUpdates" msgid="7785408253364335740">"控制位置更新通知"</string>
-    <string name="permdesc_locationUpdates" msgid="2300018303720930256">"允许启用/停用无线通信位置更新通知。一般应用程序不会使用此功能。"</string>
+    <string name="permdesc_locationUpdates" msgid="2300018303720930256">"允许启用/停用收音机的位置更新通知。普通应用程序不能使用此权限。"</string>
     <string name="permlab_checkinProperties" msgid="7855259461268734914">"访问检入属性"</string>
     <string name="permdesc_checkinProperties" msgid="7150307006141883832">"允许对检入服务上传的属性进行读/写访问。普通应用程序不能使用此权限。"</string>
     <string name="permlab_bindGadget" msgid="776905339015863471">"选择窗口小部件"</string>
     <string name="permdesc_bindGadget" msgid="2098697834497452046">"允许应用程序告诉系统哪个应用程序可以使用哪些窗口小部件。具有该权限的应用程序可以允许其他应用程序访问个人数据。普通应用程序不适合使用此权限。"</string>
     <string name="permlab_modifyPhoneState" msgid="8423923777659292228">"修改手机状态"</string>
-    <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"允许应用程序控制设备的电话功能。拥有此权限的应用程序可自行切换网络、打开和关闭无线通信等。"</string>
+    <string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"允许应用程序控制设备的手机功能。具有此权限的应用程序可切换网络、打开和关闭手机收音机等,而不通知您。"</string>
     <string name="permlab_readPhoneState" msgid="2326172951448691631">"读取手机状态和身份"</string>
     <string name="permdesc_readPhoneState" msgid="188877305147626781">"允许应用程序访问设备的手机功能。有此权限的应用程序可确定此手机的号码和序列号,是否正在通话,以及对方的号码等。"</string>
     <string name="permlab_wakeLock" msgid="573480187941496130">"防止手机休眠"</string>
@@ -487,7 +487,7 @@
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN 码"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 码不正确!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string>
-    <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"紧急电话"</string>
+    <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"紧急电话号码"</string>
     <string name="lockscreen_carrier_default" msgid="8812714795156374435">"(无服务)"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"屏幕已锁定。"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"按 MENU 解锁或拨打紧急电话。"</string>
@@ -520,9 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"登录"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"用户名或密码无效。"</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"正在检查..."</string>
-    <string name="lockscreen_unlock_label" msgid="737440483220667054">"解锁"</string>
-    <string name="lockscreen_sound_on_label" msgid="9068877576513425970">"打开声音"</string>
-    <string name="lockscreen_sound_off_label" msgid="996822825154319026">"关闭声音"</string>
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
@@ -684,13 +681,13 @@
     <string name="chooseActivity" msgid="1009246475582238425">"选择一项操作"</string>
     <string name="noApplications" msgid="1691104391758345586">"没有应用程序可执行此操作。"</string>
     <string name="aerr_title" msgid="653922989522758100">"很抱歉!"</string>
-    <string name="aerr_application" msgid="4683614104336409186">"“<xliff:g id="APPLICATION">%1$s</xliff:g>”应用程序(“<xliff:g id="PROCESS">%2$s</xliff:g>”进程)意外停止,请重试。"</string>
-    <string name="aerr_process" msgid="1551785535966089511">"“<xliff:g id="PROCESS">%1$s</xliff:g>”进程意外停止,请重试。"</string>
+    <string name="aerr_application" msgid="4683614104336409186">"<xliff:g id="APPLICATION">%1$s</xliff:g>应用程序(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)意外停止,请重试。"</string>
+    <string name="aerr_process" msgid="1551785535966089511">"<xliff:g id="PROCESS">%1$s</xliff:g> 进程意外停止,请重试。"</string>
     <string name="anr_title" msgid="3100070910664756057">"很抱歉!"</string>
-    <string name="anr_activity_application" msgid="3538242413112507636">"“<xliff:g id="ACTIVITY">%1$s</xliff:g>”活动(在“<xliff:g id="APPLICATION">%2$s</xliff:g>”应用程序中)无响应。"</string>
-    <string name="anr_activity_process" msgid="5420826626009561014">"“<xliff:g id="ACTIVITY">%1$s</xliff:g>”活动(在“<xliff:g id="PROCESS">%2$s</xliff:g>”进程中)无响应。"</string>
-    <string name="anr_application_process" msgid="4185842666452210193">"“<xliff:g id="APPLICATION">%1$s</xliff:g>”应用程序(在“<xliff:g id="PROCESS">%2$s</xliff:g>”进程中)无响应。"</string>
-    <string name="anr_process" msgid="1246866008169975783">"“<xliff:g id="PROCESS">%1$s</xliff:g>”进程无响应。"</string>
+    <string name="anr_activity_application" msgid="3538242413112507636">"<xliff:g id="ACTIVITY">%1$s</xliff:g>活动(在<xliff:g id="APPLICATION">%2$s</xliff:g>应用程序中)无响应。"</string>
+    <string name="anr_activity_process" msgid="5420826626009561014">"<xliff:g id="ACTIVITY">%1$s</xliff:g>活动(在 <xliff:g id="PROCESS">%2$s</xliff:g> 进程中)无响应。"</string>
+    <string name="anr_application_process" msgid="4185842666452210193">"<xliff:g id="APPLICATION">%1$s</xliff:g>应用程序(在 <xliff:g id="PROCESS">%2$s</xliff:g> 进程中)无响应。"</string>
+    <string name="anr_process" msgid="1246866008169975783">"<xliff:g id="PROCESS">%1$s</xliff:g> 进程无响应。"</string>
     <string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
@@ -731,7 +728,7 @@
     <string name="perms_show_all" msgid="2671791163933091180"><b>"全部显示"</b></string>
     <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"正在载入..."</string>
     <string name="usb_storage_title" msgid="5901459041398751495">"USB 已连接"</string>
-    <string name="usb_storage_message" msgid="2759542180575016871">"已通过 USB 连接与计算机。若要在计算机和手机 SD 卡之间复制文件,请选择“安装”。"</string>
+    <string name="usb_storage_message" msgid="2759542180575016871">"您已通过 USB 将手机连接至计算机。如果要在计算机和手机的 SD 卡之间复制文件,请选择“安装”。"</string>
     <string name="usb_storage_button_mount" msgid="8063426289195405456">"安装"</string>
     <string name="usb_storage_button_unmount" msgid="6092146330053864766">"不安装"</string>
     <string name="usb_storage_error_message" msgid="2534784751603345363">"使用 SD 卡进行 USB 存储时出现问题。"</string>
@@ -745,7 +742,7 @@
     <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"取消"</string>
     <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"关闭 USB 存储设备时遇到问题。请检查是否卸载了 USB 主设备,然后重试。"</string>
     <string name="extmedia_format_title" msgid="8663247929551095854">"格式化 SD 卡"</string>
-    <string name="extmedia_format_message" msgid="3621369962433523619">"确定要将 SD 卡格式化吗?该卡上的所有数据都将丢失。"</string>
+    <string name="extmedia_format_message" msgid="3621369962433523619">"您确定要格式化 SD 卡?卡上的所有数据都会丢失。"</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"格式化"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"已连接 USB 调试"</string>
     <string name="adb_active_notification_message" msgid="8470296818270110396">"选择停用 USB 调试。"</string>
@@ -756,15 +753,15 @@
     <string name="ext_media_checking_notification_title" msgid="5457603418970994050">"正在准备 SD 卡"</string>
     <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"正在检查是否有错误。"</string>
     <string name="ext_media_nofs_notification_title" msgid="780477838241212997">"空 SD 卡"</string>
-    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD 卡无文件系统,或文件系统不受支持。"</string>
+    <string name="ext_media_nofs_notification_message" msgid="3817704088027829380">"SD 卡是空的或其文件系统不受支持。"</string>
     <string name="ext_media_unmountable_notification_title" msgid="6410723906019100189">"SD 卡受损"</string>
-    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 卡已损坏。您可能必须将其重新格式化。"</string>
-    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 卡未正常移除"</string>
-    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"请先卸载 SD 卡,再将其移除,以防数据丢失。"</string>
+    <string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 卡已损坏。可能必须重新格式化。"</string>
+    <string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 卡被意外拔除"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"先卸载 SD 卡再拔除,以避免数据丢失。"</string>
     <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD 卡已安全移除"</string>
-    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"您现在可以安全移除 SD 卡。"</string>
+    <string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"可安全地取出 SD 卡。"</string>
     <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"已移除 SD 卡"</string>
-    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 卡已移除。请插入新的 SD 卡。"</string>
+    <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 卡已取出。请插入另一 SD 卡。"</string>
     <string name="activity_list_empty" msgid="4168820609403385789">"找不到匹配的活动"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新组件使用情况统计"</string>
     <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"允许修改收集的组件使用情况统计。普通应用程序不能使用此权限。"</string>
@@ -780,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>
@@ -795,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 366ad5f..b9f344e 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -520,12 +520,6 @@
     <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"登入"</string>
     <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"使用者名稱或密碼錯誤。"</string>
     <string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"檢查中..."</string>
-    <!-- no translation found for lockscreen_unlock_label (737440483220667054) -->
-    <skip />
-    <!-- no translation found for lockscreen_sound_on_label (9068877576513425970) -->
-    <skip />
-    <!-- no translation found for lockscreen_sound_off_label (996822825154319026) -->
-    <skip />
     <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
     <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
     <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
@@ -783,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>
@@ -798,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 3c0f0a4..f17d1ff 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 1394341..3683aab 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2073,16 +2073,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>
@@ -2106,4 +2099,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/AvoidXfermode.java b/graphics/java/android/graphics/AvoidXfermode.java
index d7b0225..2ed042b 100644
--- a/graphics/java/android/graphics/AvoidXfermode.java
+++ b/graphics/java/android/graphics/AvoidXfermode.java
@@ -33,17 +33,20 @@
         final int nativeInt;
     }
     
-    /**
-     * This xfermode will draw the src everywhere except on top of the opColor
-     * or, depending on the Mode, draw only on top of the opColor.
+    /** This xfermode draws, or doesn't draw, based on the destination's
+     * distance from an op-color.
      *
-     * @param opColor The color to avoid (or to target depending on Mode). Note
-     *                that the alpha in opColor is ignored.
-     * @param tolerance How closely we compare a pixel to the opColor.
-     *                  0 - only operate if exact match
-     *                  255 - maximum gradation (blending) based on how
-     *                  similar the pixel is to our opColor (max tolerance)
-     * @param mode If we should avoid or target the opColor
+     * There are two modes, and each mode interprets a tolerance value.
+     *
+     * AVOID: In this mode, drawing is allowed only on destination pixels that
+     * are different from the op-color.
+     *   Tolerance near 0: avoid anything close to the op-color
+     *   Tolerance near 255: avoid only colors very close to the op-color
+     *
+     * TARGET: In this mode, drawing only occurs on destination pixels that
+     * are similar to the op-color
+     *   Tolerance near 0: draw on colors that are very close to op-color
+     *   Tolerance near 255: draw on colors that  to the op-color
      */
     public AvoidXfermode(int opColor, int tolerance, Mode mode) {
         if (tolerance < 0 || tolerance > 255) {
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/MediaRecorderBase.h b/include/media/MediaRecorderBase.h
new file mode 100644
index 0000000..5b787a7
--- /dev/null
+++ b/include/media/MediaRecorderBase.h
@@ -0,0 +1,59 @@
+/*
+ * 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 MEDIA_RECORDER_BASE_H_
+
+#define MEDIA_RECORDER_BASE_H_
+
+#include <media/mediarecorder.h>
+
+namespace android {
+
+class ISurface;
+
+struct MediaRecorderBase {
+    MediaRecorderBase() {}
+    virtual ~MediaRecorderBase() {}
+
+    virtual status_t init() = 0;
+    virtual status_t setAudioSource(audio_source as) = 0;
+    virtual status_t setVideoSource(video_source vs) = 0;
+    virtual status_t setOutputFormat(output_format of) = 0;
+    virtual status_t setAudioEncoder(audio_encoder ae) = 0;
+    virtual status_t setVideoEncoder(video_encoder ve) = 0;
+    virtual status_t setVideoSize(int width, int height) = 0;
+    virtual status_t setVideoFrameRate(int frames_per_second) = 0;
+    virtual status_t setCamera(const sp<ICamera>& camera) = 0;
+    virtual status_t setPreviewSurface(const sp<ISurface>& surface) = 0;
+    virtual status_t setOutputFile(const char *path) = 0;
+    virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
+    virtual status_t setParameters(const String8& params) = 0;
+    virtual status_t setListener(const sp<IMediaPlayerClient>& listener) = 0;
+    virtual status_t prepare() = 0;
+    virtual status_t start() = 0;
+    virtual status_t stop() = 0;
+    virtual status_t close() = 0;
+    virtual status_t reset() = 0;
+    virtual status_t getMaxAmplitude(int *max) = 0;
+
+private:
+    MediaRecorderBase(const MediaRecorderBase &);
+    MediaRecorderBase &operator=(const MediaRecorderBase &);
+};
+
+}  // namespace android
+
+#endif  // MEDIA_RECORDER_BASE_H_
diff --git a/include/media/PVMediaRecorder.h b/include/media/PVMediaRecorder.h
index 0c71932..4f17c1a 100644
--- a/include/media/PVMediaRecorder.h
+++ b/include/media/PVMediaRecorder.h
@@ -18,8 +18,8 @@
 #ifndef ANDROID_PVMEDIARECORDER_H
 #define ANDROID_PVMEDIARECORDER_H
 
-#include <media/mediarecorder.h>
 #include <media/IMediaPlayerClient.h>
+#include <media/MediaRecorderBase.h>
 
 namespace android {
 
@@ -27,37 +27,39 @@
 class ICamera;
 class AuthorDriverWrapper;
 
-class PVMediaRecorder
-{
+class PVMediaRecorder : public MediaRecorderBase {
 public:
     PVMediaRecorder();
-    ~PVMediaRecorder();
+    virtual ~PVMediaRecorder();
 
-    status_t init();
-    status_t setAudioSource(audio_source as);
-    status_t setVideoSource(video_source vs);
-    status_t setOutputFormat(output_format of);
-    status_t setAudioEncoder(audio_encoder ae);
-    status_t setVideoEncoder(video_encoder ve);
-    status_t setVideoSize(int width, int height);
-    status_t setVideoFrameRate(int frames_per_second);
-    status_t setCamera(const sp<ICamera>& camera);
-    status_t setPreviewSurface(const sp<ISurface>& surface);
-    status_t setOutputFile(const char *path);
-    status_t setOutputFile(int fd, int64_t offset, int64_t length);
-    status_t setParameters(const String8& params);
-    status_t setListener(const sp<IMediaPlayerClient>& listener);
-    status_t prepare();
-    status_t start();
-    status_t stop();
-    status_t close();
-    status_t reset();
-    status_t getMaxAmplitude(int *max);
+    virtual status_t init();
+    virtual status_t setAudioSource(audio_source as);
+    virtual status_t setVideoSource(video_source vs);
+    virtual status_t setOutputFormat(output_format of);
+    virtual status_t setAudioEncoder(audio_encoder ae);
+    virtual status_t setVideoEncoder(video_encoder ve);
+    virtual status_t setVideoSize(int width, int height);
+    virtual status_t setVideoFrameRate(int frames_per_second);
+    virtual status_t setCamera(const sp<ICamera>& camera);
+    virtual status_t setPreviewSurface(const sp<ISurface>& surface);
+    virtual status_t setOutputFile(const char *path);
+    virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
+    virtual status_t setParameters(const String8& params);
+    virtual status_t setListener(const sp<IMediaPlayerClient>& listener);
+    virtual status_t prepare();
+    virtual status_t start();
+    virtual status_t stop();
+    virtual status_t close();
+    virtual status_t reset();
+    virtual status_t getMaxAmplitude(int *max);
 
 private:
     status_t doStop();
 
     AuthorDriverWrapper*            mAuthorDriverWrapper;
+
+    PVMediaRecorder(const PVMediaRecorder &);
+    PVMediaRecorder &operator=(const PVMediaRecorder &);
 };
 
 }; // namespace android
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..ea435de 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -27,16 +27,19 @@
 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();
+    static CameraSource *CreateFromICamera(const sp<ICamera> &icamera);
 
     virtual ~CameraSource();
 
+    void setPreviewSurface(const sp<ISurface> &surface);
+
     virtual status_t start(MetaData *params = NULL);
     virtual status_t stop();
 
@@ -45,24 +48,26 @@
     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;
+    sp<ISurface> mPreviewSurface;
 
     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..8a215ea 100644
--- a/include/media/stagefright/FileSource.h
+++ b/include/media/stagefright/FileSource.h
@@ -29,14 +29,21 @@
 class FileSource : public DataSource {
 public:
     FileSource(const char *filename);
+    FileSource(int fd, int64_t offset, int64_t length);
+
+    virtual status_t initCheck() const;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
+
+    virtual status_t getSize(off_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;
+    int64_t mOffset;
+    int64_t mLength;
     Mutex mLock;
 
     FileSource(const FileSource &);
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/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 6b0399f..2ca04fa 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -33,6 +33,7 @@
 class MPEG4Writer : public RefBase {
 public:
     MPEG4Writer(const char *filename);
+    MPEG4Writer(int fd);
 
     void addSource(const sp<MediaSource> &source);
     status_t start();
@@ -65,6 +66,7 @@
     List<off_t> mBoxes;
 
     off_t addSample(MediaBuffer *buffer);
+    off_t addLengthPrefixedSample(MediaBuffer *buffer);
 
     MPEG4Writer(const MPEG4Writer &);
     MPEG4Writer &operator=(const MPEG4Writer &);
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/MediaPlayerImpl.h b/include/media/stagefright/MediaPlayerImpl.h
index 7adf836..53a2088 100644
--- a/include/media/stagefright/MediaPlayerImpl.h
+++ b/include/media/stagefright/MediaPlayerImpl.h
@@ -112,9 +112,7 @@
 
     MediaSource *makeShoutcastSource(const char *path);
 
-    void displayOrDiscardFrame(
-            MediaBuffer **lastBuffer, MediaBuffer *buffer, int64_t pts_us);
-
+    void displayOrDiscardFrame(MediaBuffer *buffer, int64_t pts_us);
     void populateISurface();
     void depopulateISurface();
     void sendFrameToISurface(MediaBuffer *buffer);
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..583128f 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -27,25 +27,27 @@
 
 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',
+    kKeyIsSyncFrame       = 'sync',  // int32_t (bool)
+    kKeyIsCodecConfig     = 'conf',  // int32_t (bool)
+    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 +64,7 @@
         TYPE_NONE     = 'none',
         TYPE_C_STRING = 'cstr',
         TYPE_INT32    = 'in32',
+        TYPE_INT64    = 'in64',
         TYPE_FLOAT    = 'floa',
         TYPE_POINTER  = 'ptr ',
     };
@@ -71,11 +74,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..4c60623 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,9 @@
     void setVideoInputFormat(
             const char *mime, OMX_U32 width, OMX_U32 height);
 
+    status_t setupMPEG4EncoderParameters();
+    status_t setupAVCEncoderParameters();
+
     void setVideoOutputFormat(
             const char *mime, OMX_U32 width, OMX_U32 height);
 
@@ -208,6 +214,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..4784b8e 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -18,8 +18,10 @@
 
 ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
 
-LOCAL_SRC_FILES +=              \
-    StagefrightPlayer.cpp
+LOCAL_SRC_FILES +=                      \
+    StagefrightMetadataRetriever.cpp    \
+    StagefrightPlayer.cpp               \
+    StagefrightRecorder.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/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp
index 95ee3e4..2ea7cc3 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.cpp
+++ b/media/libmediaplayerservice/MediaRecorderClient.cpp
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #include <string.h>
 #include <cutils/atomic.h>
+#include <cutils/properties.h> // for property_get
 #include <android_runtime/ActivityManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -37,6 +38,8 @@
 #include "MediaRecorderClient.h"
 #include "MediaPlayerService.h"
 
+#include "StagefrightRecorder.h"
+
 namespace android {
 
 const char* cameraPermission = "android.permission.CAMERA";
@@ -286,7 +289,18 @@
 {
     LOGV("Client constructor");
     mPid = pid;
-    mRecorder = new PVMediaRecorder();
+
+#if BUILD_WITH_FULL_STAGEFRIGHT
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("media.stagefright.enable-record", value, NULL)
+        && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+        mRecorder = new StagefrightRecorder;
+    } else
+#endif
+    {
+        mRecorder = new PVMediaRecorder();
+    }
+
     mMediaPlayerService = service;
 }
 
diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h
index 6260441..e07306b 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.h
+++ b/media/libmediaplayerservice/MediaRecorderClient.h
@@ -22,8 +22,7 @@
 
 namespace android {
 
-class PVMediaRecorder;
-class ISurface;
+class MediaRecorderBase;
 class MediaPlayerService;
 
 class MediaRecorderClient : public BnMediaRecorder
@@ -59,7 +58,7 @@
 
     pid_t			 mPid;
     Mutex			 mLock;
-    PVMediaRecorder              *mRecorder;
+    MediaRecorderBase            *mRecorder;
     sp<MediaPlayerService>       mMediaPlayerService;
 };
 
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..7a3aee86
--- /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/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
new file mode 100644
index 0000000..a55273d
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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 "StagefrightRecorder"
+#include <utils/Log.h>
+
+#include "StagefrightRecorder.h"
+
+#include <media/stagefright/CameraSource.h>
+#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <ui/ICamera.h>
+#include <ui/ISurface.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+StagefrightRecorder::StagefrightRecorder() {
+    reset();
+}
+
+StagefrightRecorder::~StagefrightRecorder() {
+    stop();
+
+    if (mOutputFd >= 0) {
+        ::close(mOutputFd);
+        mOutputFd = -1;
+    }
+}
+
+status_t StagefrightRecorder::init() {
+    return OK;
+}
+
+status_t StagefrightRecorder::setAudioSource(audio_source as) {
+    mAudioSource = as;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setVideoSource(video_source vs) {
+    mVideoSource = vs;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setOutputFormat(output_format of) {
+    mOutputFormat = of;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setAudioEncoder(audio_encoder ae) {
+    mAudioEncoder = ae;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setVideoEncoder(video_encoder ve) {
+    mVideoEncoder = ve;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setVideoSize(int width, int height) {
+    mVideoWidth = width;
+    mVideoHeight = height;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) {
+    mFrameRate = frames_per_second;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) {
+    mCamera = camera;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setPreviewSurface(const sp<ISurface> &surface) {
+    mPreviewSurface = surface;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setOutputFile(const char *path) {
+    // We don't actually support this at all, as the media_server process
+    // no longer has permissions to create files.
+
+    return UNKNOWN_ERROR;
+}
+
+status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t length) {
+    // These don't make any sense, do they?
+    CHECK_EQ(offset, 0);
+    CHECK_EQ(length, 0);
+
+    if (mOutputFd >= 0) {
+        ::close(mOutputFd);
+    }
+    mOutputFd = dup(fd);
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setParameters(const String8 &params) {
+    mParams = params;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::setListener(const sp<IMediaPlayerClient> &listener) {
+    mListener = listener;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::prepare() {
+    return OK;
+}
+
+status_t StagefrightRecorder::start() {
+    if (mWriter != NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    if (mVideoSource == VIDEO_SOURCE_CAMERA) {
+        CHECK(mCamera != NULL);
+
+        sp<CameraSource> cameraSource =
+            CameraSource::CreateFromICamera(mCamera);
+
+        CHECK(cameraSource != NULL);
+
+        cameraSource->setPreviewSurface(mPreviewSurface);
+
+        sp<MetaData> enc_meta = new MetaData;
+        switch (mVideoEncoder) {
+            case VIDEO_ENCODER_H263:
+                enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263);
+                break;
+
+            case VIDEO_ENCODER_MPEG_4_SP:
+                enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4);
+                break;
+
+            case VIDEO_ENCODER_H264:
+                enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
+                break;
+
+            default:
+                CHECK(!"Should not be here, unsupported video encoding.");
+                break;
+        }
+
+        sp<MetaData> meta = cameraSource->getFormat();
+
+        int32_t width, height;
+        CHECK(meta->findInt32(kKeyWidth, &width));
+        CHECK(meta->findInt32(kKeyHeight, &height));
+
+        enc_meta->setInt32(kKeyWidth, width);
+        enc_meta->setInt32(kKeyHeight, height);
+
+        OMXClient client;
+        CHECK_EQ(client.connect(), OK);
+
+        sp<MediaSource> encoder =
+            OMXCodec::Create(
+                    client.interface(), enc_meta,
+                    true /* createEncoder */, cameraSource);
+
+        CHECK(mOutputFd >= 0);
+        mWriter = new MPEG4Writer(dup(mOutputFd));
+        mWriter->addSource(encoder);
+        mWriter->start();
+    }
+
+    return OK;
+}
+
+status_t StagefrightRecorder::stop() {
+    if (mWriter == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    mWriter->stop();
+    mWriter = NULL;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::close() {
+    stop();
+
+    return OK;
+}
+
+status_t StagefrightRecorder::reset() {
+    stop();
+
+    mAudioSource = AUDIO_SOURCE_LIST_END;
+    mVideoSource = VIDEO_SOURCE_LIST_END;
+    mOutputFormat = OUTPUT_FORMAT_LIST_END;
+    mAudioEncoder = AUDIO_ENCODER_LIST_END;
+    mVideoEncoder = VIDEO_ENCODER_LIST_END;
+    mVideoWidth = -1;
+    mVideoHeight = -1;
+    mFrameRate = -1;
+    mOutputFd = -1;
+
+    return OK;
+}
+
+status_t StagefrightRecorder::getMaxAmplitude(int *max) {
+    return UNKNOWN_ERROR;
+}
+
+}  // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
new file mode 100644
index 0000000..56c4e0e
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -0,0 +1,76 @@
+/*
+ * 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 STAGEFRIGHT_RECORDER_H_
+
+#define STAGEFRIGHT_RECORDER_H_
+
+#include <media/MediaRecorderBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class MPEG4Writer;
+
+struct StagefrightRecorder : public MediaRecorderBase {
+    StagefrightRecorder();
+    virtual ~StagefrightRecorder();
+
+    virtual status_t init();
+    virtual status_t setAudioSource(audio_source as);
+    virtual status_t setVideoSource(video_source vs);
+    virtual status_t setOutputFormat(output_format of);
+    virtual status_t setAudioEncoder(audio_encoder ae);
+    virtual status_t setVideoEncoder(video_encoder ve);
+    virtual status_t setVideoSize(int width, int height);
+    virtual status_t setVideoFrameRate(int frames_per_second);
+    virtual status_t setCamera(const sp<ICamera>& camera);
+    virtual status_t setPreviewSurface(const sp<ISurface>& surface);
+    virtual status_t setOutputFile(const char *path);
+    virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
+    virtual status_t setParameters(const String8& params);
+    virtual status_t setListener(const sp<IMediaPlayerClient>& listener);
+    virtual status_t prepare();
+    virtual status_t start();
+    virtual status_t stop();
+    virtual status_t close();
+    virtual status_t reset();
+    virtual status_t getMaxAmplitude(int *max);
+
+private:
+    sp<ICamera> mCamera;
+    sp<ISurface> mPreviewSurface;
+    sp<IMediaPlayerClient> mListener;
+    sp<MPEG4Writer> mWriter;
+
+    audio_source mAudioSource;
+    video_source mVideoSource;
+    output_format mOutputFormat;
+    audio_encoder mAudioEncoder;
+    video_encoder mVideoEncoder;
+    int mVideoWidth, mVideoHeight;
+    int mFrameRate;
+    String8 mParams;
+    int mOutputFd;
+
+    StagefrightRecorder(const StagefrightRecorder &);
+    StagefrightRecorder &operator=(const StagefrightRecorder &);
+};
+
+}  // namespace android
+
+#endif  // STAGEFRIGHT_RECORDER_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..bd862e0 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -19,122 +19,168 @@
 #include <OMX_Component.h>
 
 #include <binder/IServiceManager.h>
+#include <cutils/properties.h> // for property_get
 #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)
+// static
+CameraSource *CameraSource::CreateFromICamera(const sp<ICamera> &icamera) {
+    sp<Camera> camera = Camera::create(icamera);
+
+    if (camera.get() == NULL) {
+        return NULL;
+    }
+
+    return new CameraSource(camera);
+}
+
+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());
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("ro.hardware", value, NULL) && !strcmp(value, "sholes")) {
+        // The hardware encoder(s) do not support yuv420, but only YCbYCr,
+        // fortunately the camera also supports this, so we needn't transcode.
+        mCamera->setParameters(String8("preview-format=yuv422i-yuyv"));
+    }
+
+    String8 s = mCamera->getParameters();
+    printf("params: \"%s\"\n", s.string());
+
+    CameraParameters params(s);
+    params.getPreviewSize(&mWidth, &mHeight);
 }
 
 CameraSource::~CameraSource() {
     if (mStarted) {
         stop();
     }
+}
 
-    mCamera->disconnect();
+void CameraSource::setPreviewSurface(const sp<ISurface> &surface) {
+    mPreviewSurface = surface;
 }
 
 status_t CameraSource::start(MetaData *) {
     CHECK(!mStarted);
 
-    status_t err = mCamera->lock();
+    mCamera->setListener(new CameraSourceListener(this));
+
+    status_t err =
+        mCamera->setPreviewDisplay(
+                mPreviewSurface != NULL ? mPreviewSurface : new DummySurface);
     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 +192,6 @@
     CHECK(mStarted);
 
     mCamera->stopPreview();
-    mCamera->unlock();
 
     mStarted = false;
 
@@ -157,8 +202,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 +220,7 @@
     }
 
     sp<IMemory> frame;
+    int64_t frameTime;
 
     {
         Mutex::Autolock autoLock(mLock);
@@ -184,41 +230,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..130239e 100644
--- a/media/libstagefright/FileSource.cpp
+++ b/media/libstagefright/FileSource.cpp
@@ -20,7 +20,17 @@
 namespace android {
 
 FileSource::FileSource(const char *filename)
-    : mFile(fopen(filename, "rb")) {
+    : mFile(fopen(filename, "rb")),
+      mOffset(0),
+      mLength(-1) {
+}
+
+FileSource::FileSource(int fd, int64_t offset, int64_t length)
+    : mFile(fdopen(fd, "rb")),
+      mOffset(offset),
+      mLength(length) {
+    CHECK(offset >= 0);
+    CHECK(length >= 0);
 }
 
 FileSource::~FileSource() {
@@ -30,19 +40,40 @@
     }
 }
 
-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);
+    if (mLength >= 0) {
+        if (offset >= mLength) {
+            return 0;  // read beyond EOF.
+        }
+        int64_t numAvailable = mLength - offset;
+        if ((int64_t)size > numAvailable) {
+            size = numAvailable;
+        }
+    }
+
+    int err = fseeko(mFile, offset + mOffset, SEEK_SET);
     CHECK(err != -1);
 
-    ssize_t result = fread(data, 1, size, mFile);
+    return fread(data, 1, size, mFile);
+}
 
-    return result;
+status_t FileSource::getSize(off_t *size) {
+    if (mLength >= 0) {
+        *size = mLength;
+
+        return OK;
+    }
+
+    fseek(mFile, SEEK_END, 0);
+    *size = ftello(mFile);
+
+    return OK;
 }
 
 }  // namespace android
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..367459f 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -75,6 +75,13 @@
     CHECK(mFile != NULL);
 }
 
+MPEG4Writer::MPEG4Writer(int fd)
+    : mFile(fdopen(fd, "wb")),
+      mOffset(0),
+      mMdatOffset(0) {
+    CHECK(mFile != NULL);
+}
+
 MPEG4Writer::~MPEG4Writer() {
     stop();
 
@@ -203,6 +210,27 @@
     return old_offset;
 }
 
+off_t MPEG4Writer::addLengthPrefixedSample(MediaBuffer *buffer) {
+    Mutex::Autolock autoLock(mLock);
+
+    off_t old_offset = mOffset;
+
+    size_t length = buffer->range_length();
+    CHECK(length < 65536);
+
+    uint8_t x = length >> 8;
+    fwrite(&x, 1, 1, mFile);
+    x = length & 0xff;
+    fwrite(&x, 1, 1, mFile);
+
+    fwrite((const uint8_t *)buffer->data() + buffer->range_offset(),
+           1, length, mFile);
+
+    mOffset += length + 2;
+
+    return old_offset;
+}
+
 void MPEG4Writer::beginBox(const char *fourcc) {
     CHECK_EQ(strlen(fourcc), 4);
 
@@ -348,11 +376,12 @@
 }
 
 void MPEG4Writer::Track::threadEntry() {
-    bool is_mpeg4 = false;
     sp<MetaData> meta = mSource->getFormat();
     const char *mime;
     meta->findCString(kKeyMIMEType, &mime);
-    is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4);
+    bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4);
+    bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+    int32_t count = 0;
 
     MediaBuffer *buffer;
     while (!mDone && mSource->read(&buffer) == OK) {
@@ -363,6 +392,55 @@
             continue;
         }
 
+        ++count;
+
+        if (is_avc && count < 3) {
+            size_t size = buffer->range_length();
+
+            switch (count) {
+                case 1:
+                {
+                    CHECK_EQ(mCodecSpecificData, NULL);
+                    mCodecSpecificData = malloc(size + 8);
+                    uint8_t *header = (uint8_t *)mCodecSpecificData;
+                    header[0] = 1;
+                    header[1] = 0x42;  // profile
+                    header[2] = 0x80;
+                    header[3] = 0x1e;  // level
+                    header[4] = 0xfc | 3;
+                    header[5] = 0xe0 | 1;
+                    header[6] = size >> 8;
+                    header[7] = size & 0xff;
+                    memcpy(&header[8],
+                            (const uint8_t *)buffer->data() + buffer->range_offset(),
+                            size);
+
+                    mCodecSpecificDataSize = size + 8;
+                    break;
+                }
+
+                case 2:
+                {
+                    size_t offset = mCodecSpecificDataSize;
+                    mCodecSpecificDataSize += size + 3;
+                    mCodecSpecificData = realloc(mCodecSpecificData, mCodecSpecificDataSize);
+                    uint8_t *header = (uint8_t *)mCodecSpecificData;
+                    header[offset] = 1;
+                    header[offset + 1] = size >> 8;
+                    header[offset + 2] = size & 0xff;
+                    memcpy(&header[offset + 3],
+                            (const uint8_t *)buffer->data() + buffer->range_offset(),
+                            size);
+                    break;
+                }
+            }
+
+            buffer->release();
+            buffer = NULL;
+
+            continue;
+        }
+
         if (mCodecSpecificData == NULL && is_mpeg4) {
             const uint8_t *data =
                 (const uint8_t *)buffer->data() + buffer->range_offset();
@@ -393,21 +471,18 @@
             buffer->set_range(buffer->range_offset() + offset, size - offset);
         }
 
-        off_t offset = mOwner->addSample(buffer);
+        off_t offset = is_avc ? mOwner->addLengthPrefixedSample(buffer)
+                              : mOwner->addSample(buffer);
 
         SampleInfo info;
-        info.size = buffer->range_length();
+        info.size = is_avc ? buffer->range_length() + 2 : 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);
 
@@ -560,6 +635,8 @@
                     mOwner->beginBox("mp4v");
                 } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_H263, mime)) {
                     mOwner->beginBox("s263");
+                } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
+                    mOwner->beginBox("avc1");
                 } else {
                     LOGE("Unknown mime type '%s'.", mime);
                     CHECK(!"should not be here, unknown mime type.");
@@ -635,8 +712,13 @@
                           mOwner->writeInt8(0);   // profile: 0
 
                       mOwner->endBox();  // d263
+                  } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
+                      mOwner->beginBox("avcC");
+                        mOwner->write(mCodecSpecificData, mCodecSpecificDataSize);
+                      mOwner->endBox();  // avcC
                   }
-                mOwner->endBox();  // mp4v or s263
+
+                mOwner->endBox();  // mp4v, s263 or avc1
             }
           mOwner->endBox();  // stsd
 
diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp
index f3c0e73..b973745b 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 8300422..c1044a3 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>
@@ -40,13 +41,6 @@
 
 namespace android {
 
-static void releaseBufferIfNonNULL(MediaBuffer **buffer) {
-    if (*buffer) {
-        (*buffer)->release();
-        *buffer = NULL;
-    }
-}
-
 MediaPlayerImpl::MediaPlayerImpl(const char *uri)
     : mInitCheck(NO_INIT),
       mTimeSource(NULL),
@@ -76,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;
@@ -236,8 +219,6 @@
     bool firstFrame = true;
     bool eof = false;
 
-    MediaBuffer *lastBuffer = NULL;
-
     status_t err = mVideoDecoder->start();
     CHECK_EQ(err, OK);
 
@@ -250,8 +231,6 @@
         {
             Mutex::Autolock autoLock(mLock);
             if (mSeeking) {
-                releaseBufferIfNonNULL(&lastBuffer);
-
                 LOGV("seek-options to %lld", mSeekTimeUs);
                 options.setSeekTo(mSeekTimeUs);
 
@@ -269,6 +248,13 @@
         status_t err = mVideoDecoder->read(&buffer, &options);
         CHECK((err == OK && buffer != NULL) || (err != OK && buffer == NULL));
 
+        if (err == INFO_FORMAT_CHANGED) {
+            LOGV("format changed.");
+            depopulateISurface();
+            populateISurface();
+            continue;
+        }
+
         if (err == ERROR_END_OF_STREAM || err != OK) {
             eof = true;
             continue;
@@ -280,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;
@@ -312,21 +292,19 @@
             firstFrame = false;
         }
 
-        displayOrDiscardFrame(&lastBuffer, buffer, pts_us);
+        displayOrDiscardFrame(buffer, pts_us);
     }
 
-    releaseBufferIfNonNULL(&lastBuffer);
-
     mVideoDecoder->stop();
 }
 
 void MediaPlayerImpl::displayOrDiscardFrame(
-        MediaBuffer **lastBuffer,
         MediaBuffer *buffer, int64_t pts_us) {
     for (;;) {
         if (!mPlaying || mPaused) {
-            releaseBufferIfNonNULL(lastBuffer);
-            *lastBuffer = buffer;
+            buffer->release();
+            buffer = NULL;
+
             return;
         }
 
@@ -348,10 +326,12 @@
             LOGV("we're late by %lld ms, dropping a frame\n",
                  -delay_us / 1000);
 
-            releaseBufferIfNonNULL(lastBuffer);
-            *lastBuffer = buffer;
+            buffer->release();
+            buffer = NULL;
             return;
         } else if (delay_us > 100000) {
+            LOGV("we're much too early (by %lld ms)\n",
+                 delay_us / 1000);
             usleep(100000);
             continue;
         } else if (delay_us > 0) {
@@ -363,14 +343,13 @@
 
     {
         Mutex::Autolock autoLock(mLock);
-
         if (mVideoRenderer.get() != NULL) {
             sendFrameToISurface(buffer);
         }
     }
 
-    releaseBufferIfNonNULL(lastBuffer);
-    *lastBuffer = buffer;
+    buffer->release();
+    buffer = NULL;
 }
 
 void MediaPlayerImpl::init() {
@@ -403,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;
                 }
             }
 
@@ -427,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) {
@@ -633,6 +617,9 @@
     success = success && meta->findInt32(kKeyHeight, &decodedHeight);
     CHECK(success);
 
+    LOGV("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..d36653e 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)) {
@@ -509,19 +594,18 @@
         CHECK(!"Should not be here. Not a supported video mime type.");
     }
 
-    OMX_COLOR_FORMATTYPE colorFormat =
-        0 ? OMX_COLOR_FormatYCbYCr : OMX_COLOR_FormatCbYCrY;
-
-    if (!strncmp("OMX.qcom.video.encoder.", mComponentName, 23)) {
-        colorFormat = OMX_COLOR_FormatYUV420SemiPlanar;
+    OMX_COLOR_FORMATTYPE colorFormat = OMX_COLOR_FormatYUV420SemiPlanar;
+    if (!strcasecmp("OMX.TI.Video.encoder", mComponentName)) {
+        colorFormat = OMX_COLOR_FormatYCbYCr;
     }
 
-    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 +638,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 +648,172 @@
     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;
+
+        case OMX_VIDEO_CodingAVC:
+        {
+            CHECK_EQ(setupAVCEncoderParameters(), OK);
+            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;
+}
+
+status_t OMXCodec::setupAVCEncoderParameters() {
+    OMX_VIDEO_PARAM_AVCTYPE h264type;
+    InitOMXParams(&h264type);
+    h264type.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->getParameter(
+            mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+    CHECK_EQ(err, OK);
+
+    h264type.nAllowedPictureTypes =
+        OMX_VIDEO_PictureTypeI | OMX_VIDEO_PictureTypeP;
+
+    h264type.nSliceHeaderSpacing = 0;
+    h264type.nBFrames = 0;
+    h264type.bUseHadamard = OMX_TRUE;
+    h264type.nRefFrames = 1;
+    h264type.nRefIdx10ActiveMinus1 = 0;
+    h264type.nRefIdx11ActiveMinus1 = 0;
+    h264type.bEnableUEP = OMX_FALSE;
+    h264type.bEnableFMO = OMX_FALSE;
+    h264type.bEnableASO = OMX_FALSE;
+    h264type.bEnableRS = OMX_FALSE;
+    h264type.eProfile = OMX_VIDEO_AVCProfileBaseline;
+    h264type.eLevel = OMX_VIDEO_AVCLevel1b;
+    h264type.bFrameMBsOnly = OMX_TRUE;
+    h264type.bMBAFF = OMX_FALSE;
+    h264type.bEntropyCodingCABAC = OMX_FALSE;
+    h264type.bWeightedPPrediction = OMX_FALSE;
+    h264type.bconstIpred = OMX_FALSE;
+    h264type.bDirect8x8Inference = OMX_FALSE;
+    h264type.bDirectSpatialTemporal = OMX_FALSE;
+    h264type.nCabacInitIdc = 0;
+    h264type.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterEnable;
+
+    err = mOMX->setParameter(
+            mNode, OMX_IndexParamVideoAvc, &h264type, sizeof(h264type));
+    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);
+
+    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 +881,7 @@
     video_def->nFrameWidth = width;
     video_def->nFrameHeight = height;
 
+    video_def->eCompressionFormat = compressionFormat;
     video_def->eColorFormat = OMX_COLOR_FormatUnused;
 
     err = mOMX->setParameter(
@@ -668,7 +911,6 @@
     CHECK_EQ(err, OK);
 }
 
-
 OMXCodec::OMXCodec(
         const sp<IOMX> &omx, IOMX::node_id node, uint32_t quirks,
         bool isEncoder,
@@ -687,6 +929,7 @@
       mInitialBufferSubmit(true),
       mSignalledEOS(false),
       mNoMoreOutputData(false),
+      mOutputPortSettingsHaveChanged(false),
       mSeekTimeUs(-1) {
     mPortStatus[kPortIndexInput] = ENABLED;
     mPortStatus[kPortIndexOutput] = ENABLED;
@@ -990,16 +1233,15 @@
 
                 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);
                 }
+                if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_CODECCONFIG) {
+                    buffer->meta_data()->setInt32(kKeyIsCodecConfig, true);
+                }
 
                 buffer->meta_data()->setPointer(
                         kKeyPlatformPrivate,
@@ -1064,6 +1306,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 +1393,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 +1759,7 @@
     }
 
     OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
-    OMX_TICKS timestamp = 0;
+    OMX_TICKS timestampUs = 0;
     size_t srcLength = 0;
 
     if (err != OK) {
@@ -1463,15 +1779,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 +1794,7 @@
 
     err = mOMX->emptyBuffer(
             mNode, info->mBuffer, 0, srcLength,
-            flags, timestamp);
+            flags, timestampUs);
 
     if (err != OK) {
         setState(ERROR);
@@ -1490,6 +1802,13 @@
     }
 
     info->mOwnedByComponent = true;
+
+    // This component does not ever signal the EOS flag on output buffers,
+    // Thanks for nothing.
+    if (mSignalledEOS && !strcmp(mComponentName, "OMX.TI.Video.encoder")) {
+        mNoMoreOutputData = true;
+        mBufferFilled.signal();
+    }
 }
 
 void OMXCodec::fillOutputBuffer(BufferInfo *info) {
@@ -1794,6 +2113,7 @@
     mInitialBufferSubmit = true;
     mSignalledEOS = false;
     mNoMoreOutputData = false;
+    mOutputPortSettingsHaveChanged = false;
     mSeekTimeUs = -1;
     mFilledBuffers.clear();
 
@@ -1864,6 +2184,8 @@
 }
 
 sp<MetaData> OMXCodec::getFormat() {
+    Mutex::Autolock autoLock(mLock);
+
     return mOutputFormat;
 }
 
@@ -1941,6 +2263,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 +2369,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 +2736,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..fa68771 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -22,15 +22,17 @@
 #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 {
 
 TimedEventQueue::TimedEventQueue()
-    : mRunning(false),
+    : mNextEventID(1),
+      mRunning(false),
       mStopped(false) {
 }
 
@@ -75,26 +77,29 @@
     mRunning = false;
 }
 
-void TimedEventQueue::postEvent(const sp<Event> &event) {
+TimedEventQueue::event_id TimedEventQueue::postEvent(const sp<Event> &event) {
     // Reserve an earlier timeslot an INT64_MIN to be able to post
     // the StopEvent to the absolute head of the queue.
-    postTimedEvent(event, INT64_MIN + 1);
+    return postTimedEvent(event, INT64_MIN + 1);
 }
 
-void TimedEventQueue::postEventToBack(const sp<Event> &event) {
-    postTimedEvent(event, INT64_MAX);
+TimedEventQueue::event_id TimedEventQueue::postEventToBack(
+        const sp<Event> &event) {
+    return postTimedEvent(event, INT64_MAX);
 }
 
-void TimedEventQueue::postEventWithDelay(
+TimedEventQueue::event_id TimedEventQueue::postEventWithDelay(
         const sp<Event> &event, int64_t delay_us) {
     CHECK(delay_us >= 0);
-    postTimedEvent(event, getRealTimeUs() + delay_us);
+    return postTimedEvent(event, getRealTimeUs() + delay_us);
 }
 
-void TimedEventQueue::postTimedEvent(
+TimedEventQueue::event_id TimedEventQueue::postTimedEvent(
         const sp<Event> &event, int64_t realtime_us) {
     Mutex::Autolock autoLock(mLock);
 
+    event->setEventID(mNextEventID++);
+
     List<QueueItem>::iterator it = mQueue.begin();
     while (it != mQueue.end() && realtime_us >= (*it).realtime_us) {
         ++it;
@@ -111,29 +116,60 @@
     mQueue.insert(it, item);
 
     mQueueNotEmptyCondition.signal();
+
+    return event->eventID();
 }
 
-bool TimedEventQueue::cancelEvent(const sp<Event> &event) {
-    Mutex::Autolock autoLock(mLock);
+static bool MatchesEventID(
+        void *cookie, const sp<TimedEventQueue::Event> &event) {
+    TimedEventQueue::event_id *id =
+        static_cast<TimedEventQueue::event_id *>(cookie);
 
-    List<QueueItem>::iterator it = mQueue.begin();
-    while (it != mQueue.end() && (*it).event != event) {
-        ++it;
-    }
-
-    if (it == mQueue.end()) {
+    if (event->eventID() != *id) {
         return false;
     }
 
-    if (it == mQueue.begin()) {
-        mQueueHeadChangedCondition.signal();
-    }
-
-    mQueue.erase(it);
+    *id = 0;
 
     return true;
 }
 
+bool TimedEventQueue::cancelEvent(event_id id) {
+    CHECK(id != 0);
+
+    cancelEvents(&MatchesEventID, &id, true /* stopAfterFirstMatch */);
+
+    // if MatchesEventID found a match, it will have set id to 0
+    // (which is not a valid event_id).
+
+    return id == 0;
+}
+
+void TimedEventQueue::cancelEvents(
+        bool (*predicate)(void *cookie, const sp<Event> &event),
+        void *cookie,
+        bool stopAfterFirstMatch) {
+    Mutex::Autolock autoLock(mLock);
+
+    List<QueueItem>::iterator it = mQueue.begin();
+    while (it != mQueue.end()) {
+        if (!(*predicate)(cookie, (*it).event)) {
+            ++it;
+            continue;
+        }
+
+        if (it == mQueue.begin()) {
+            mQueueHeadChangedCondition.signal();
+        }
+
+        it = mQueue.erase(it);
+
+        if (stopAfterFirstMatch) {
+            return;
+        }
+    }
+}
+
 // static
 int64_t TimedEventQueue::getRealTimeUs() {
     struct timeval tv;
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/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/TimedEventQueue.h b/media/libstagefright/include/TimedEventQueue.h
similarity index 74%
rename from include/media/stagefright/TimedEventQueue.h
rename to media/libstagefright/include/TimedEventQueue.h
index a264421..21eade37 100644
--- a/include/media/stagefright/TimedEventQueue.h
+++ b/media/libstagefright/include/TimedEventQueue.h
@@ -28,16 +28,31 @@
 
 struct TimedEventQueue {
 
+    typedef int32_t event_id;
+
     struct Event : public RefBase {
-        Event() {}
+        Event()
+            : mEventID(0) {
+        }
+
         virtual ~Event() {}
 
+        event_id eventID() {
+            return mEventID;
+        }
+
     protected:
         virtual void fire(TimedEventQueue *queue, int64_t now_us) = 0;
 
     private:
         friend class TimedEventQueue;
 
+        event_id mEventID;
+
+        void setEventID(event_id id) {
+            mEventID = id;
+        }
+
         Event(const Event &);
         Event &operator=(const Event &);
     };
@@ -55,21 +70,29 @@
 
     // Posts an event to the front of the queue (after all events that
     // have previously been posted to the front but before timed events).
-    void postEvent(const sp<Event> &event);
+    event_id postEvent(const sp<Event> &event);
 
-    void postEventToBack(const sp<Event> &event);
+    event_id postEventToBack(const sp<Event> &event);
 
     // It is an error to post an event with a negative delay.
-    void postEventWithDelay(const sp<Event> &event, int64_t delay_us);
+    event_id postEventWithDelay(const sp<Event> &event, int64_t delay_us);
 
     // If the event is to be posted at a time that has already passed,
     // it will fire as soon as possible.
-    void postTimedEvent(const sp<Event> &event, int64_t realtime_us);
+    event_id postTimedEvent(const sp<Event> &event, int64_t realtime_us);
 
     // Returns true iff event is currently in the queue and has been
     // successfully cancelled. In this case the event will have been
     // removed from the queue and won't fire.
-    bool cancelEvent(const sp<Event> &event);
+    bool cancelEvent(event_id id);
+
+    // Cancel any pending event that satisfies the predicate.
+    // If stopAfterFirstMatch is true, only cancels the first event
+    // satisfying the predicate (if any).
+    void cancelEvents(
+            bool (*predicate)(void *cookie, const sp<Event> &event),
+            void *cookie,
+            bool stopAfterFirstMatch = false);
 
     static int64_t getRealTimeUs();
 
@@ -90,6 +113,7 @@
     Mutex mLock;
     Condition mQueueNotEmptyCondition;
     Condition mQueueHeadChangedCondition;
+    event_id mNextEventID;
 
     bool mRunning;
     bool mStopped;
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 25da813..389c2c9 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -9,6 +9,7 @@
 LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
 
 LOCAL_SRC_FILES:=                 \
+        ColorConverter.cpp        \
 	OMX.cpp                   \
         OMXNodeInstance.cpp       \
         SoftwareRenderer.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 4ccd4bd..5b3cc1b 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -24,10 +24,10 @@
 #include "pv_omxcore.h"
 
 #include "../include/OMXNodeInstance.h"
+#include "../include/SoftwareRenderer.h"
 
 #include <binder/IMemory.h>
 #include <media/stagefright/MediaDebug.h>
-#include <media/stagefright/SoftwareRenderer.h>
 #include <media/stagefright/VideoRenderer.h>
 
 #include <OMX_Component.h>
@@ -233,7 +233,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/SoftwareRenderer.cpp b/media/libstagefright/omx/SoftwareRenderer.cpp
index 39de504..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 %d", 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/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 e60859b..df5f96e 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -29,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;
@@ -685,7 +687,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];
 
@@ -695,8 +700,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");
@@ -821,11 +828,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);
 
             /*
@@ -1091,6 +1104,7 @@
 
                     if (createEglSurface) {
                         gl = (GL10) mEglHelper.createSurface(getHolder());
+                        sGLThreadManager.checkGLDriver(gl);
                         if (LOG_RENDERER) {
                             Log.w("GLThread", "onSurfaceCreated");
                         }
@@ -1304,9 +1318,14 @@
         }
 
         /*
+<<<<<<< HEAD
+         * Tries to acquire the right to use an EGL
+         * surface. Does not block.
+=======
          * Tries once to acquire the right to use an EGL
          * surface. Does not block. Requires that we are already
          * in the sGLThreadManager monitor when this is called.
+>>>>>>> dc49acb0
          * @return true if the right to use an EGL surface was acquired.
          */
         public boolean tryAcquireEglSurfaceLocked(GLThread thread) {
@@ -1315,6 +1334,10 @@
                 notifyAll();
                 return true;
             }
+            checkGLESVersion();
+            if (mMultipleGLESContextsAllowed) {
+                return true;
+            }
             return false;
         }
         /*
@@ -1328,6 +1351,40 @@
             notifyAll();
         }
 
+        public synchronized void checkGLDriver(GL10 gl) {
+            if (! mGLESDriverCheckComplete) {
+                checkGLESVersion();
+                if (mGLESVersion < kGLES_20) {
+                    String renderer = gl.glGetString(GL10.GL_RENDERER);
+                    mMultipleGLESContextsAllowed =
+                        ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
+                    notifyAll();
+                }
+                mGLESDriverCheckComplete = true;
+            }
+        }
+
+        private void checkGLESVersion() {
+            if (! mGLESVersionCheckComplete) {
+                mGLESVersion = SystemProperties.getInt(
+                        "ro.opengles.version",
+                        ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+                if (mGLESVersion >= kGLES_20) {
+                    mMultipleGLESContextsAllowed = true;
+                }
+                mGLESVersionCheckComplete = true;
+            }
+
+        }
+
+        private boolean mGLESVersionCheckComplete;
+        private int mGLESVersion;
+        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 GLThread mEglOwner;
     }
 
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/packages/SettingsProvider/res/values-da/strings.xml b/packages/SettingsProvider/res/values-da/strings.xml
index bc160f3..f0afc0b 100644
--- a/packages/SettingsProvider/res/values-da/strings.xml
+++ b/packages/SettingsProvider/res/values-da/strings.xml
@@ -15,5 +15,5 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4567566098528588863">"Lagring af indstillinger"</string>
+    <string name="app_label" msgid="4567566098528588863">"Indstillingslagring"</string>
 </resources>
diff --git a/preloaded-classes b/preloaded-classes
index c4b1c66..67a4f65 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -733,7 +733,6 @@
 android.text.util.Linkify
 android.text.util.Linkify$1
 android.text.util.Linkify$4
-android.text.util.Regex
 android.text.util.Rfc822Tokenizer
 android.text.util.Rfc822Validator
 android.util.AttributeSet
@@ -1118,44 +1117,6 @@
 com.google.android.net.ParentalControlState$1
 com.google.android.net.UrlRules
 com.google.android.net.UrlRules$Rule
-com.google.common.Config
-com.google.common.I18n
-com.google.common.StaticUtil
-com.google.common.android.AndroidClock
-com.google.common.android.AndroidConfig
-com.google.common.android.AndroidConfig$1
-com.google.common.async.AbstractRequest
-com.google.common.async.AsyncHttpRequestFactory
-com.google.common.async.AsyncHttpRequestFactory$AsyncHttpRequestImpl
-com.google.common.graphics.android.AndroidFontFactory
-com.google.common.graphics.android.AndroidImageFactory
-com.google.common.io.BaseConnectionFactory
-com.google.common.io.BaseHttpConnectionFactory
-com.google.common.io.IoUtil
-com.google.common.io.android.AndroidFixedPersistentStore
-com.google.common.io.android.AndroidHttpClient
-com.google.common.io.android.AndroidHttpClient$1
-com.google.common.io.android.AndroidHttpClient$2
-com.google.common.io.android.AndroidHttpClient$LoggingConfiguration
-com.google.common.io.android.AndroidHttpConnectionFactory
-com.google.common.io.android.AndroidHttpConnectionFactory$AndroidGoogleHttpConnection
-com.google.common.io.android.AndroidPersistentStore
-com.google.common.io.android.GoogleHttpClient
-com.google.common.io.android.J2SeTcpConnectionFactory
-com.google.common.io.protocol.ProtoBuf
-com.google.common.io.protocol.ProtoBufType
-com.google.common.lang.ThreadFactory
-com.google.common.task.AbstractTask
-com.google.common.task.Task
-com.google.common.task.TaskRunner
-com.google.common.util.text.TextUtil
-com.google.masf.MobileServiceMux
-com.google.masf.protocol.HeaderRequest
-com.google.masf.protocol.PlainRequest
-com.google.masf.protocol.Request
-com.google.masf.services.CookieService
-com.google.masf.services.EventLogService
-com.google.masf.services.resume.WindowResumeService
 com.google.wireless.gdata.calendar.client.CalendarClient
 com.google.wireless.gdata.calendar.data.CalendarEntry
 com.google.wireless.gdata.calendar.data.CalendarsFeed
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index 590b1e4..1d31d09 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -1,40 +1,108 @@
 /*
-**
-** 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) 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.BroadcastReceiver;
+import android.os.Build;
+import android.os.DropBoxManager;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
 import android.provider.Settings;
+import android.util.Log;
 
-public class BootReceiver extends BroadcastReceiver
-{
+import com.android.internal.os.RecoverySystem;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Performs a number of miscellaneous, non-system-critical actions
+ * after the system has finished booting.
+ */
+public class BootReceiver extends BroadcastReceiver {
+    private static final String TAG = "BootReceiver";
+
     @Override
-    public void onReceive(Context context, Intent intent)
-    {
-        Intent service = new Intent(context, com.android.server.LoadAverageService.class);
-        ContentResolver res = context.getContentResolver();
-        boolean shown = Settings.System.getInt(
-                res, Settings.System.SHOW_PROCESSES, 0) != 0;
-        if (shown) {
-            context.startService(service);
+    public void onReceive(Context context, Intent intent) {
+        try {
+            logBootEvents(context);
+        } catch (Exception e) {
+            Log.e(TAG, "Can't log boot events", e);
+        }
+
+        try {
+            RecoverySystem.handleAftermath();
+        } catch (Exception e) {
+            Log.e(TAG, "Can't handle recovery aftermath", e);
+        }
+
+        try {
+            // Start the load average overlay, if activated
+            ContentResolver res = context.getContentResolver();
+            if (Settings.System.getInt(res, Settings.System.SHOW_PROCESSES, 0) != 0) {
+                Intent loadavg = new Intent(context, com.android.server.LoadAverageService.class);
+                context.startService(loadavg);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Can't start load average service", e);
         }
     }
-}
 
+    private void logBootEvents(Context context) throws IOException {
+        DropBoxManager db = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+
+        String build =
+                "Build: " + Build.FINGERPRINT + "\nKernel: " +
+                FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n");
+
+        if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
+            String now = Long.toString(System.currentTimeMillis());
+            SystemProperties.set("ro.runtime.firstboot", now);
+            if (db != null) db.addText("SYSTEM_BOOT", build);
+        } else {
+            if (db != null) db.addText("SYSTEM_RESTART", build);
+            return;  // Subsequent boot, don't log kernel boot log
+        }
+
+        ContentResolver cr = context.getContentResolver();
+        logBootFile(cr, db, "/cache/recovery/log", "SYSTEM_RECOVERY_LOG");
+        logBootFile(cr, db, "/data/dontpanic/last_kmsg", "SYSTEM_LAST_KMSG");
+        logBootFile(cr, db, "/data/dontpanic/apanic_console", "APANIC_CONSOLE");
+        logBootFile(cr, db, "/data/dontpanic/apanic_threads", "APANIC_THREADS");
+    }
+
+    private void logBootFile(ContentResolver cr, DropBoxManager db, String filename, String tag)
+            throws IOException {
+        if (cr == null || db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
+
+        File file = new File(filename);
+        long fileTime = file.lastModified();
+        if (fileTime <= 0) return;  // File does not exist
+
+        String setting = "logfile:" + filename;
+        long lastTime = Settings.Secure.getLong(cr, setting, 0);
+        if (lastTime == fileTime) return;  // Already logged this particular file
+
+        db.addFile(tag,
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
+                DropBoxManager.IS_TEXT);
+    }
+}
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/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
new file mode 100644
index 0000000..5103cf4
--- /dev/null
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -0,0 +1,698 @@
+/*
+ * 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.Debug;
+import android.os.DropBoxManager;
+import android.os.ParcelFileDescriptor;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.format.Time;
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+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 IDropBoxManagerService} using the filesystem.
+ * Clients use {@link DropBoxManager} to access this service.
+ */
+public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
+    private static final String TAG = "DropBoxManagerService";
+    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;
+
+    private static final boolean PROFILE_DUMP = false;
+
+    // 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 DropBoxManagerService(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(DropBoxManager.Entry entry) {
+        File temp = null;
+        OutputStream output = null;
+        final String tag = entry.getTag();
+        try {
+            int flags = entry.getFlags();
+            if ((flags & DropBoxManager.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 & DropBoxManager.IS_GZIPPED) == 0)) {
+                output = new GZIPOutputStream(output);
+                flags = flags | DropBoxManager.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 DropBoxManager.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 & DropBoxManager.IS_EMPTY) != 0) {
+                return new DropBoxManager.Entry(entry.tag, entry.timestampMillis);
+            }
+            try {
+                return new DropBoxManager.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 DropBoxManagerService");
+            return;
+        }
+
+        try {
+            init();
+        } catch (IOException e) {
+            pw.println("Can't initialize: " + e);
+            Log.e(TAG, "Can't init", e);
+            return;
+        }
+
+        if (PROFILE_DUMP) Debug.startMethodTracing("/data/trace/dropbox.dump");
+
+        Formatter out = new Formatter();
+        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("-")) {
+                out.format("Unknown argument: %s\n", args[i]);
+            } else {
+                searchArgs.add(args[i]);
+            }
+        }
+
+        out.format("Drop box contents: %d entries\n", mAllFiles.contents.size());
+
+        if (!searchArgs.isEmpty()) {
+            out.format("Searching for:");
+            for (String a : searchArgs) out.format(" %s", a);
+            out.format("\n");
+        }
+
+        int numFound = 0, numArgs = searchArgs.size();
+        Time time = new Time();
+        out.format("\n");
+        for (EntryFile entry : mAllFiles.contents) {
+            time.set(entry.timestampMillis);
+            String date = time.format("%Y-%m-%d %H:%M:%S");
+            boolean match = true;
+            for (int i = 0; i < numArgs && match; i++) {
+                String arg = searchArgs.get(i);
+                match = (date.contains(arg) || arg.equals(entry.tag));
+            }
+            if (!match) continue;
+
+            numFound++;
+            out.format("%s.%03d %s", date, entry.timestampMillis % 1000,
+                     entry.tag == null ? "(no tag)" : entry.tag);
+            if (entry.file == null) {
+                out.format(" (no file)\n");
+                continue;
+            } else if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) {
+                out.format(" (contents lost)\n");
+                continue;
+            } else {
+                out.format(" (%s%s, %d bytes)\n",
+                        (entry.flags & DropBoxManager.IS_GZIPPED) != 0 ? "compressed " : "",
+                        (entry.flags & DropBoxManager.IS_TEXT) != 0 ? "text" : "data",
+                        entry.file.length());
+            }
+
+            if (doFile || (doPrint && (entry.flags & DropBoxManager.IS_TEXT) == 0)) {
+                out.format("%s%s\n", (doPrint ? "" : "    "), entry.file.getPath());
+            }
+
+            if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) {
+                DropBoxManager.Entry dbe = null;
+                try {
+                    dbe = new DropBoxManager.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;
+                            out.format("%s", new String(buf, 0, n));
+                            newline = (buf[n - 1] == '\n');
+                        }
+                        if (!newline) out.format("\n");
+                    } else {
+                        String text = dbe.getText(70);
+                        boolean truncated = (text.length() == 70);
+                        out.format("    %s%s\n", text.trim().replace('\n', '/'),
+                                truncated ? " ..." : "");
+                    }
+                } catch (IOException e) {
+                    out.format("*** %s\n", e.toString());
+                    Log.e(TAG, "Can't read: " + entry.file, e);
+                } finally {
+                    if (dbe != null) dbe.close();
+                }
+            }
+
+            if (doPrint) out.format("\n");
+        }
+
+        if (numFound == 0) out.format("(No entries found.)\n");
+
+        if (args == null || args.length == 0) {
+            if (!doPrint) out.format("\n");
+            out.format("Usage: dumpsys dropbox [--print|--file] [YYYY-mm-dd] [HH:MM:SS] [tag]\n");
+        }
+
+        pw.write(out.toString());
+        if (PROFILE_DUMP) Debug.stopMethodTracing();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** 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 & DropBoxManager.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 & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
+                    ((flags & DropBoxManager.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 = DropBoxManager.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 = DropBoxManager.IS_EMPTY;
+                return;
+            }
+
+            int flags = 0;
+            this.tag = Uri.decode(name.substring(0, at));
+            if (name.endsWith(".gz")) {
+                flags |= DropBoxManager.IS_GZIPPED;
+                name = name.substring(0, name.length() - 3);
+            }
+            if (name.endsWith(".lost")) {
+                flags |= DropBoxManager.IS_EMPTY;
+                name = name.substring(at + 1, name.length() - 5);
+            } else if (name.endsWith(".txt")) {
+                flags |= DropBoxManager.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 = DropBoxManager.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 = DropBoxManager.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 & DropBoxManager.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..3cfd1a9 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 DropBoxManagerService(context, new File("/data/system/dropbox")));
+            } catch (Throwable e) {
+                Log.e(TAG, "Failure starting DropBoxManagerService", 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 887c46d..4bac178 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -9395,15 +9395,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 425682a..c3d4940 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -819,6 +819,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 a5188ce..2843cde 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/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java b/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java
index cb85002..02b061c 100644
--- a/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/PdpConnection.java
@@ -17,10 +17,10 @@
 package com.android.internal.telephony.gsm;
 
 import android.os.*;
-import android.text.util.Regex;
 import android.util.EventLog;
 import android.util.Log;
 
+import com.android.common.Patterns;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.DataConnection;
@@ -318,7 +318,7 @@
     private boolean isIpAddress(String address) {
         if (address == null) return false;
 
-        return Regex.IP_ADDRESS_PATTERN.matcher(apn.mmsProxy).matches();
+        return Patterns.IP_ADDRESS.matcher(apn.mmsProxy).matches();
     }
 
     public ApnSetting getApn() {
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_invalid_comment_line.vcf b/tests/AndroidTests/res/raw/v21_invalid_comment_line.vcf
new file mode 100644
index 0000000..f910710
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_invalid_comment_line.vcf
@@ -0,0 +1,10 @@
+BEGIN:vCard

+VERSION:2.1

+UID:357

+N:;Conference Call

+FN:Conference Call

+# This line must be ignored.

+NOTE;ENCODING=QUOTED-PRINTABLE:This is an (sharp ->=

+#<- sharp) example. This message must NOT be ignored.

+# This line must be ignored too.

+END:vCard

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/v21_winmo_65.vcf b/tests/AndroidTests/res/raw/v21_winmo_65.vcf
new file mode 100644
index 0000000..f380d0d
--- /dev/null
+++ b/tests/AndroidTests/res/raw/v21_winmo_65.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD

+VERSION:2.1

+N:Example;;;;

+FN:Example

+ANNIVERSARY;VALUE=DATE:20091010

+AGENT:Invalid line which must be handled correctly.

+X-CLASS:PUBLIC

+X-REDUCTION:

+X-NO:

+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..a0d096e
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/DropBoxTest.java
@@ -0,0 +1,544 @@
+/*
+ * 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.DropBoxManager;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+import com.android.server.DropBoxManagerService;
+
+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 DropBoxManager} 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 {
+        DropBoxManager dropbox = (DropBoxManager) 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();
+
+        DropBoxManager.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBoxManager.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+        DropBoxManager.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 {
+        DropBoxManager dropbox = (DropBoxManager) getContext().getSystemService(
+                Context.DROPBOX_SERVICE);
+        long before = System.currentTimeMillis();
+        dropbox.addData("DropBoxTest", "TEST".getBytes(), 0);
+        long after = System.currentTimeMillis();
+
+        DropBoxManager.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();
+
+        DropBoxManager dropbox = (DropBoxManager) 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, DropBoxManager.IS_TEXT);
+        dropbox.addFile("DropBoxTest", pfd1, DropBoxManager.IS_TEXT | DropBoxManager.IS_GZIPPED);
+        dropbox.addFile("DropBoxTest", pfd2, 0);
+        dropbox.addFile("DropBoxTest", pfd3, DropBoxManager.IS_GZIPPED);
+
+        pfd0.close();
+        pfd1.close();
+        pfd2.close();
+        pfd3.close();
+
+        DropBoxManager.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBoxManager.Entry e1 = dropbox.getNextEntry("DropBoxTest", e0.getTimeMillis());
+        DropBoxManager.Entry e2 = dropbox.getNextEntry("DropBoxTest", e1.getTimeMillis());
+        DropBoxManager.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(DropBoxManager.IS_TEXT, e0.getFlags());
+        assertEquals(DropBoxManager.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();
+
+        DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+        DropBoxManager dropbox = new DropBoxManager(service);
+
+        // Until a write, the timestamps are taken at face value
+        DropBoxManager.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBoxManager.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        DropBoxManager.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+        DropBoxManager.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 {
+        DropBoxManager dropbox = (DropBoxManager) 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"));
+
+        DropBoxManager.Entry e0 = dropbox.getNextEntry("DropBoxTest", before);
+        DropBoxManager.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");
+        DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+        DropBoxManager dropbox = new DropBoxManager(service);
+
+        long before = System.currentTimeMillis();
+        dropbox.addText("DropBoxTest.A", "A0");
+        dropbox.addText("DropBoxTest.B", "B0");
+        dropbox.addText("DropBoxTest.A", "A1");
+
+        DropBoxManager.Entry a0 = dropbox.getNextEntry("DropBoxTest.A", before);
+        DropBoxManager.Entry a1 = dropbox.getNextEntry("DropBoxTest.A", a0.getTimeMillis());
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest.A", a1.getTimeMillis()));
+
+        DropBoxManager.Entry b0 = dropbox.getNextEntry("DropBoxTest.B", before);
+        assertTrue(null == dropbox.getNextEntry("DropBoxTest.B", b0.getTimeMillis()));
+
+        DropBoxManager.Entry x0 = dropbox.getNextEntry(null, before);
+        DropBoxManager.Entry x1 = dropbox.getNextEntry(null, x0.getTimeMillis());
+        DropBoxManager.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();
+        DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+        DropBoxManager dropbox = new DropBoxManager(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);
+
+        DropBoxManager.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBoxManager.Entry e1 = dropbox.getNextEntry(null, e0.getTimeMillis());
+        DropBoxManager.Entry e2 = dropbox.getNextEntry(null, e1.getTimeMillis());
+        DropBoxManager.Entry e3 = dropbox.getNextEntry(null, e2.getTimeMillis());
+        DropBoxManager.Entry e4 = dropbox.getNextEntry(null, e3.getTimeMillis());
+        DropBoxManager.Entry e5 = dropbox.getNextEntry(null, e4.getTimeMillis());
+        DropBoxManager.Entry e6 = dropbox.getNextEntry(null, e5.getTimeMillis());
+        DropBoxManager.Entry e7 = dropbox.getNextEntry(null, e6.getTimeMillis());
+        DropBoxManager.Entry e8 = dropbox.getNextEntry(null, e7.getTimeMillis());
+        DropBoxManager.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.
+
+        DropBoxManager.Entry t0 = dropbox.getNextEntry("DropBoxTest1", before);
+        DropBoxManager.Entry t1 = dropbox.getNextEntry("DropBoxTest1", t0.getTimeMillis());
+        DropBoxManager.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();
+        DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+        DropBoxManager dropbox = new DropBoxManager(service);
+
+        dropbox.addText("DropBoxTest", "TEST");
+        addRandomEntry(dropbox, "DropBoxTest", blockSize * 20);
+
+        // Verify that things are as expected
+        DropBoxManager.Entry e0 = dropbox.getNextEntry(null, before);
+        DropBoxManager.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 testCreateDropBoxManagerWithInvalidDirectory() throws Exception {
+        // If created with an invalid directory, the DropBoxManager 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("testCreateDropBoxManagerWith"), "InvalidDirectory");
+        new FileOutputStream(dir).close();  // Create an empty file
+        DropBoxManagerService service = new DropBoxManagerService(getContext(), dir);
+        DropBoxManager dropbox = new DropBoxManager(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");
+        DropBoxManager.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(DropBoxManager 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(DropBoxManager.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..14a789a 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,22 @@
 package com.android.unit_tests.vcard;
 
 import android.content.ContentValues;
-
-import org.apache.commons.codec.binary.Base64;
+import android.pim.vcard.VCardEntry;
+import android.util.Log;
 
 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 VCardEntry}, 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 +104,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;
@@ -110,18 +122,17 @@
         
         if (propName == null || !propName.equals(node.propName)) {
             return false;
-        } else if (!paramMap.equals(node.paramMap)) {
+        } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
             return false;
         } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
             return false;
         } else if (!propGroupSet.equals(node.propGroupSet)) {
             return false;
         }
-        
+
         if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
             return true;
         } else {
-            // Log.d("@@@", propValue + ", " + node.propValue);
             if (!propValue.equals(node.propValue)) {
                 return false;
             }
@@ -143,10 +154,34 @@
         builder.append(propName);
         builder.append(", paramMap: ");
         builder.append(paramMap.toString());
-        builder.append(", propmMap_TYPE: ");
-        builder.append(paramMap_TYPE.toString());
-        builder.append(", propGroupSet: ");
-        builder.append(propGroupSet.toString());
+        builder.append(", paramMap_TYPE: [");
+        boolean first = true;
+        for (String elem : paramMap_TYPE) {
+            if (first) {
+                first = false;
+            } else {
+                builder.append(", ");
+            }
+            builder.append('"');
+            builder.append(elem);
+            builder.append('"');
+        }
+        builder.append("]");
+        if (!propGroupSet.isEmpty()) {
+            builder.append(", propGroupSet: [");
+            first = true;
+            for (String elem : propGroupSet) {
+                if (first) {
+                    first = false;
+                } else {
+                    builder.append(", ");
+                }
+                builder.append('"');
+                builder.append(elem);
+                builder.append('"');
+            }
+            builder.append("]");
+        }
         if (propValue_vector != null && propValue_vector.size() > 1) {
             builder.append(", propValue_vector size: ");
             builder.append(propValue_vector.size());
@@ -155,168 +190,9 @@
             builder.append(", propValue_bytes size: ");
             builder.append(propValue_bytes.length);
         }
-        builder.append(", propValue: ");
+        builder.append(", propValue: \"");
         builder.append(propValue);
+        builder.append("\"");
         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..190a355
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
@@ -0,0 +1,383 @@
+/*
+ * 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 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(int resId, int vCardType, final VCardParser vCardParser)
+            throws IOException, VCardException {
+        verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
+                vCardType, vCardParser);
+    }
+
+    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();
+        }
+        verify(is, vCardType, vCardParser);
+    }
+
+    public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+            throws IOException, VCardException {
+        try {
+            vCardParser.parse(is, this);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void endEntry() {
+        super.endEntry();
+        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,
+            List<String> propValueList, ContentValues contentValues) {
+        return addNodeWithOrder(propName, null, propValueList,
+                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) {
+        return addNodeWithOrder(propName, null, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithOrder(String propName, String propValue,
+            ContentValues paramMap, TypeSet paramMap_TYPE) {
+        return addNodeWithOrder(propName, propValue, null, null,
+                paramMap, 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) {
+        if (propValue == null && propValueList != null) {
+            propValue = concatinateListWithSemiColon(propValueList);
+        }
+        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,
+            List<String> propValueList, ContentValues contentValues) {
+        return addNodeWithoutOrder(propName, null,
+                propValueList, 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) {
+        return addNodeWithoutOrder(propName, null, 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,
+            ContentValues paramMap, TypeSet paramMap_TYPE) {
+        return addNodeWithoutOrder(propName, propValue, null, null,
+                paramMap, paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifierElem addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList, byte[] propValue_bytes,
+            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+        if (propValue == null && propValueList != null) {
+            propValue = concatinateListWithSemiColon(propValueList);
+        }
+        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..212795b
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -0,0 +1,1034 @@
+/*
+ * 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.Relation;
+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() {
+        VCardVerifier verifier = new VCardVerifier(V21);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "Ando")
+                .put(StructuredName.GIVEN_NAME, "Roid");
+
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("FN", "Roid Ando")
+                .addNodeWithoutOrder("N", "Ando;Roid;;;",
+                        Arrays.asList("Ando", "Roid", "", "", ""));
+        verifier.verify();
+    }
+
+    private void testStructuredNameBasic(int vcardType) {
+        final boolean isV30 = VCardConfig.isV30(vcardType);
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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");
+
+        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 vcardType) {
+        final boolean isV30 = (vcardType == V30);
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        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 vcardType) {
+        final boolean isV30 = (vcardType == V30);
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        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() {
+        VCardVerifier verifier = new VCardVerifier(V30);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Nickname.CONTENT_ITEM_TYPE)
+                .put(Nickname.NAME, "Nicky");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+            .addNodeWithOrder("NICKNAME", "Nicky");
+
+        verifier.verify();
+    }
+
+    private void testPhoneBasicCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 testMiscPhoneTypeHandling(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "1")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "Modem");
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "2")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "MSG");
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "3")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "BBS");
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "4")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "VIDEO");
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "5")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "6")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "_AUTO_CELL");  // The old indicator for the type mobile.
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "7")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "\u643A\u5E2F");  // Mobile phone in Japanese Kanji
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "8")
+                .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+                .put(Phone.LABEL, "invalid");
+
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElemWithEmptyName();
+        elem.addNodeWithoutOrder("TEL", "1", new TypeSet("MODEM"))
+                .addNodeWithoutOrder("TEL", "2", new TypeSet("MSG"))
+                .addNodeWithoutOrder("TEL", "3", new TypeSet("BBS"))
+                .addNodeWithoutOrder("TEL", "4", new TypeSet("VIDEO"))
+                .addNodeWithoutOrder("TEL", "5", new TypeSet("VOICE"))
+                .addNodeWithoutOrder("TEL", "6", new TypeSet("CELL"))
+                .addNodeWithoutOrder("TEL", "7", new TypeSet("CELL"))
+                .addNodeWithoutOrder("TEL", "8", new TypeSet("X-invalid"));
+        verifier.verify();
+    }
+
+    public void testPhoneTypeHandlingV21() {
+        testMiscPhoneTypeHandling(V21);
+    }
+
+    public void testPhoneTypeHandlingV30() {
+        testMiscPhoneTypeHandling(V30);
+    }
+
+    private void testEmailBasicCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Email.CONTENT_ITEM_TYPE)
+                .put(Email.DATA, "sample@example.com");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+            .addNodeWithoutOrder("EMAIL", "sample@example.com");
+
+        verifier.verify();
+    }
+
+    public void testEmailBasicV21() {
+        testEmailBasicCommon(V21);
+    }
+
+    public void testEmailBasicV30() {
+        testEmailBasicCommon(V30);
+    }
+
+    private void testEmailVariousTypeSupportCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 testPostalAddressCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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")
+                .put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address")
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
+
+        // adr-value    = 0*6(text-value ";") text-value
+        //              ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
+        //              ; Country Name
+        //
+        // The NEIGHBORHOOD field is appended after the CITY field.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("Pobox", "", "Street", "City Neighborhood",
+                                "Region", "100", "Country"), new TypeSet("WORK"));
+        verifier.verify();
+    }
+
+    public void testPostalAddressV21() {
+        testPostalAddressCommon(V21);
+    }
+
+    public void testPostalAddressV30() {
+        testPostalAddressCommon(V30);
+    }
+
+    private void testPostalAddressNonNeighborhood(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.CITY, "City");
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    public void testPostalAddressNonNeighborhoodV21() {
+        testPostalAddressNonNeighborhood(V21);
+    }
+
+    public void testPostalAddressNonNeighborhoodV30() {
+        testPostalAddressNonNeighborhood(V30);
+    }
+
+    private void testPostalAddressNonCity(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood");
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    public void testPostalAddressNonCityV21() {
+        testPostalAddressNonCity(V21);
+    }
+
+    public void testPostalAddressNonCityV30() {
+        testPostalAddressNonCity(V30);
+    }
+
+    private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.REGION, "")  // Must be ignored.
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                "Formatted address CA 123-334 United Statue");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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");
+
+        // Currently we do not use group but depend on the order.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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...
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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);
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .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 String getAndroidPropValue(final String mimeType, String value, Integer type) {
+        return getAndroidPropValue(mimeType, value, type, null);
+    }
+
+    private String getAndroidPropValue(final String mimeType, String value,
+            Integer type, String label) {
+        return (mimeType + ";" + value + ";"
+                + (type != null ? type : "") + ";"
+                + (label != null ? label : "") + ";;;;;;;;;;;;");
+    }
+
+    private void testEventCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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");  // Should be ignored.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("BDAY", "2008-10-22")
+                .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+                        getAndroidPropValue(
+                                Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
+                .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+                        getAndroidPropValue(
+                                Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
+                .addNodeWithoutOrder("X-ANDROID-CUSTOM",
+                        getAndroidPropValue(
+                                Event.CONTENT_ITEM_TYPE,
+                                "When the Tower of Hanoi with 64 rings is completed.",
+                                Event.TYPE_CUSTOM, "The last day"));
+        verifier.verify();
+    }
+
+    public void testEventV21() {
+        testEventCommon(V21);
+    }
+
+    public void testEventV30() {
+        testEventCommon(V30);
+    }
+
+    private void testNoteCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithOrder("NOTE", "note1")
+                .addNodeWithOrder("NOTE", "note2");
+        verifier.verify();
+    }
+
+    public void testNoteV21() {
+        testNoteCommon(V21);
+    }
+
+    public void testNoteV30() {
+        testNoteCommon(V30);
+    }
+
+    private void testPhotoCommon(int vcardType) {
+        final boolean isV30 = vcardType == V30;
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        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"));
+        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);
+    }
+
+    private void testRelationCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Relation.CONTENT_ITEM_TYPE)
+                .put(Relation.TYPE, Relation.TYPE_MOTHER)
+                .put(Relation.NAME, "Ms. Mother");
+        ImportVerifierElem elem = verifier.addImportVerifier();
+        elem.addExpected(Relation.CONTENT_ITEM_TYPE)
+                .put(Relation.TYPE, Relation.TYPE_MOTHER)
+                .put(Relation.NAME, "Ms. Mother");
+        verifier.verify();
+    }
+
+    public void testRelationV21() {
+        testRelationCommon(V21);
+    }
+
+    public void testRelationV30() {
+        testRelationCommon(V30);
+    }
+
+    public void testV30HandleEscape() {
+        final int vcardType = V30;
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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}>]");
+        // 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() {
+        VCardVerifier verifier = new VCardVerifier(V21);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Nickname.CONTENT_ITEM_TYPE)
+                .put(Nickname.NAME, "Nicky");
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithOrder("X-ANDROID-CUSTOM",
+                        Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
+        verifier.addImportVerifier()
+                .addExpected(Nickname.CONTENT_ITEM_TYPE)
+                        .put(Nickname.NAME, "Nicky");
+        verifier.verify();
+    }
+
+    public void testTolerateBrokenPhoneNumberEntryV21() {
+        VCardVerifier verifier = new VCardVerifier(V21);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_HOME)
+                .put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);"
+                        + "777-888-9999 (Chicago);111-222-3333 (Miami)");
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "111-222-3333", new TypeSet("HOME"))
+                .addNodeWithoutOrder("TEL", "444-555-5666", new TypeSet("HOME"))
+                .addNodeWithoutOrder("TEL", "777-888-9999", new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.IS_PRIMARY, 1);  // Empty name. Should be ignored.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "family1");  // Not primary. Should be ignored.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.IS_PRIMARY, 1)
+                .put(StructuredName.FAMILY_NAME, "family2");  // This entry is what we want.
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.IS_PRIMARY, 1)
+                .put(StructuredName.FAMILY_NAME, "family3");
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "family4");
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("N", Arrays.asList("family2", "", "", "", ""))
+                .addNodeWithoutOrder("FN", "family2");
+        verifier.verify();
+    }
+
+    public void testPickUpNonEmptyContentValuesV21() {
+        testPickUpNonEmptyContentValuesCommon(V21);
+    }
+
+    public void testPickUpNonEmptyContentValuesV30() {
+        testPickUpNonEmptyContentValuesCommon(V30);
+    }
+}
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..d891497
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
@@ -0,0 +1,1066 @@
+/*
+ * 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_V21;
+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_GIVEN_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.STREET,
+                        "\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_GIVEN_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_GIVEN_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_GIVEN_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 testIgnoreAgentV21_Parse() throws IOException, VCardException {
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this);
+        ContentValues contentValuesForValue = new ContentValues();
+        contentValuesForValue.put("VALUE", "DATE");
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithOrder("VERSION", "2.1")
+                .addNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
+                .addNodeWithOrder("FN", "Example")
+                .addNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
+                .addNodeWithOrder("AGENT", "")
+                .addNodeWithOrder("X-CLASS", "PUBLIC")
+                .addNodeWithOrder("X-REDUCTION", "")
+                .addNodeWithOrder("X-NO", "");
+
+        // Only scan mode lets vCard parser accepts invalid AGENT lines like above.
+        verifier.verify(R.raw.v21_winmo_65, V21, new VCardParser_V21());
+    }
+
+    public void testIgnoreAgentV21()  throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "Example")
+                .put(StructuredName.DISPLAY_NAME, "Example");
+        verifier.verify(R.raw.v21_winmo_65, V21, new VCardParser_V21());
+    }
+
+    public void testTolerateInvalidCommentLikeLineV21() throws IOException, VCardException {
+        ImportVerifier verifier = new ImportVerifier();
+        ImportVerifierElem elem = verifier.addImportVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.GIVEN_NAME, "Conference Call")
+                .put(StructuredName.DISPLAY_NAME, "Conference Call");
+        elem.addExpected(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "This is an (sharp ->#<- sharp) example. "
+                        + "This message must NOT be ignored.");
+        verifier.verify(R.raw.v21_invalid_comment_line, V21);
+    }
+
+    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..5ce0dee
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardJapanizationTests.java
@@ -0,0 +1,419 @@
+/*
+ * 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.Note;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+
+import com.android.unit_tests.vcard.PropertyNodesVerifierElem.TypeSet;
+import com.android.unit_tests.vcard.VCardTestsBase.ContactEntry;
+import com.android.unit_tests.vcard.VCardTestsBase.VCardVerifier;
+
+import java.util.Arrays;
+
+public class VCardJapanizationTests extends VCardTestsBase {
+    private void testNameUtf8Common(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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");
+        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() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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");
+
+        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() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.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");
+
+        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();
+    }
+
+    private void testPhoneticNameCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+        final ContentValues contentValues =
+            (VCardConfig.usesShiftJis(vcardType) ?
+                    (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+                            mContentValuesForQPAndSJis) :
+                    (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElemWithEmptyName();
+        elem.addNodeWithoutOrder("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
+                        contentValues)
+                .addNodeWithoutOrder("X-PHONETIC-MIDDLE-NAME",
+                        "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0",
+                        contentValues)
+                .addNodeWithoutOrder("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
+                        contentValues);
+        if (VCardConfig.isV30(vcardType)) {
+            elem.addNodeWithoutOrder("SORT-STRING",
+                    "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
+                    contentValues);
+        }
+        ContentValuesBuilder builder = verifier.addImportVerifier()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046")
+                .put(StructuredName.DISPLAY_NAME,
+                        "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " +
+                        "\u305F\u308D\u3046");
+        verifier.verify();
+    }
+
+    public void testPhoneticNameForJapaneseV21Utf8() {
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+    }
+
+    public void testPhoneticNameForJapaneseV21Sjis() {
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    public void testPhoneticNameForJapaneseV30Utf8() {
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+    }
+
+    public void testPhoneticNameForJapaneseV30SJis() {
+        testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+    }
+
+    public void testPhoneticNameForMobileV21_1() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("SOUND",
+                        "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+                        "\uFF80\uFF9B\uFF73;;;;",
+                        mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+        ContentValuesBuilder builder = verifier.addImportVerifier()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+                .put(StructuredName.PHONETIC_MIDDLE_NAME,
+                        "\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+                .put(StructuredName.DISPLAY_NAME,
+                        "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+                        "\uFF80\uFF9B\uFF73");
+        verifier.verify();
+    }
+
+    public void testPhoneticNameForMobileV21_2() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+        verifier.addPropertyNodesVerifierElem()
+                .addNodeWithoutOrder("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;",
+                                mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+        ContentValuesBuilder builder = verifier.addImportVerifier()
+                .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+        builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+                .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+                .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
+        verifier.verify();
+    }
+
+    private void testPostalAddressWithJapaneseCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+                .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+                .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+                .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+                .put(StructuredPostal.POSTCODE, "494-1313")
+                .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B"
+                        + "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F")
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+                .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
+
+        ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
+                (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+                    mContentValuesForQPAndSJis) :
+                (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
+                    mContentValuesForQPAndUtf8));
+
+        PropertyNodesVerifierElem elem = verifier.addPropertyNodesVerifierElemWithEmptyName();
+        // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
+        // same as that in vCard 3.0, which can be changed in the future.
+        elem.addNodeWithoutOrder("ADR", Arrays.asList("\u79C1\u66F8\u7BB107",
+                "", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C",
+                "494-1313", "\u65E5\u672C"),
+                contentValues);
+        verifier.addImportVerifier().addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+                .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+                .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+                .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+                .put(StructuredPostal.POSTCODE, "494-1313")
+                .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+                .put(StructuredPostal.FORMATTED_ADDRESS,
+                        "\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " +
+                        "\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107")
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+        verifier.verify();
+    }
+    public void testPostalAddresswithJapaneseV21() {
+        testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+    }
+
+    /**
+     * Verifies that only one address field is emitted toward DoCoMo phones.
+     * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
+     */
+    public void testPostalAdrressForDoCoMo_1() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+                .put(StructuredPostal.POBOX, "1");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+                .put(StructuredPostal.POBOX, "2");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+                .put(StructuredPostal.POBOX, "3");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+                .put(StructuredPostal.LABEL, "custom")
+                .put(StructuredPostal.POBOX, "4");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "")
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    public void testPostalAdrressForDoCoMo_2() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+                .put(StructuredPostal.POBOX, "1");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+                .put(StructuredPostal.POBOX, "2");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+                .put(StructuredPostal.LABEL, "custom")
+                .put(StructuredPostal.POBOX, "3");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "")
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK"));
+        verifier.verify();
+    }
+
+    public void testPostalAdrressForDoCoMo_3() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+                .put(StructuredPostal.LABEL, "custom1")
+                .put(StructuredPostal.POBOX, "1");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+                .put(StructuredPostal.POBOX, "2");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+                .put(StructuredPostal.LABEL, "custom2")
+                .put(StructuredPostal.POBOX, "3");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "")
+                .addNodeWithoutOrder("ADR", Arrays.asList("2", "", "", "", "", "", ""));
+        verifier.verify();
+    }
+
+    /**
+     * Verifies the vCard exporter tolerates null TYPE.
+     */
+    public void testPostalAdrressForDoCoMo_4() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "1");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+                .put(StructuredPostal.POBOX, "2");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+                .put(StructuredPostal.POBOX, "3");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+                .put(StructuredPostal.POBOX, "4");
+        entry.buildData(StructuredPostal.CONTENT_ITEM_TYPE)
+                .put(StructuredPostal.POBOX, "5");
+
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "")
+                .addNodeWithoutOrder("ADR",
+                        Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+        verifier.verify();
+    }
+
+    private void testJapanesePhoneNumberCommon(int vcardType) {
+        VCardVerifier verifier = new VCardVerifier(vcardType);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "0312341234")
+                .put(Phone.TYPE, Phone.TYPE_HOME);
+        entry.buildData(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.NUMBER, "09012341234")
+                .put(Phone.TYPE, Phone.TYPE_MOBILE);
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "03-1234-1234", new TypeSet("HOME"))
+                .addNodeWithoutOrder("TEL", "090-1234-1234", new TypeSet("WORK"));
+    }
+
+    public void testJapanesePhoneNumberV21_1() {
+        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+    }
+
+    public void testJapanesePhoneNumberDoCoMo() {
+        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_DOCOMO);
+    }
+
+    public void testJapanesePhoneNumberV30() {
+        testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+    }
+
+    public void testNoteDoCoMo() {
+        VCardVerifier verifier = new VCardVerifier(VCardConfig.VCARD_TYPE_DOCOMO);
+        ContactEntry entry = verifier.addInputEntry();
+        entry.buildData(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "note1");
+        entry.buildData(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "note2");
+        entry.buildData(Note.CONTENT_ITEM_TYPE)
+                .put(Note.NOTE, "note3");
+
+        // More than one note fields must be aggregated into one note.
+        verifier.addPropertyNodesVerifierElemWithEmptyName()
+                .addNodeWithoutOrder("TEL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("EMAIL", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("X-CLASS", "PUBLIC")
+                .addNodeWithoutOrder("X-REDUCTION", "")
+                .addNodeWithoutOrder("X-NO", "")
+                .addNodeWithoutOrder("X-DCM-HMN-MODE", "")
+                .addNodeWithoutOrder("ADR", "", new TypeSet("HOME"))
+                .addNodeWithoutOrder("NOTE", "note1\nnote2\nnote3", mContentValuesForQP);
+        verifier.verify();
+    }
+}
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..7b5f216
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardTestsBase.java
@@ -0,0 +1,1000 @@
+/*
+ * 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.VCardEntry;
+import android.pim.vcard.VCardEntryCommitter;
+import android.pim.vcard.VCardEntryHandler;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardInterpreterCollection;
+import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntryConstructor;
+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 StringBuilder builder = new StringBuilder();
+                        builder.append("Unexpected: ");
+                        builder.append(convertToEasilyReadableString(actualContentValues));
+                        builder.append("\nExpected: ");
+                        for (ContentValues expectedContentValues : contentValuesCollection) {
+                            builder.append(convertToEasilyReadableString(expectedContentValues));
+                        }
+                        fail(builder.toString());
+                    }
+                } 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 VCardEntryHandler mHandler;
+
+        public ImportVerifierElem() {
+            mResolver = new ImportTestResolver();
+            mHandler = new VCardEntryCommitter(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();
+            }
+            VCardEntryConstructor builder =
+                    new VCardEntryConstructor(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.onStart();
+        }
+
+        public void onEntryCreated(VCardEntry entry) {
+            mHandler.onEntryCreated(entry);
+        }
+
+        public void onParsingEnd() {
+            mHandler.onEnd();
+        }
+    }
+
+    class ImportVerifier implements VCardEntryHandler {
+        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(int resId, int vCardType, final VCardParser vCardParser)
+                throws IOException, VCardException {
+            verify(getContext().getResources().openRawResource(resId), vCardType, vCardParser);
+        }
+
+        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();
+            }
+            verify(is, vCardType, vCardParser);
+        }
+
+        public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+                throws IOException, VCardException {
+            VCardEntryConstructor builder =
+                new VCardEntryConstructor(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 onStart() {
+            for (ImportVerifierElem elem : mImportVerifierElemList) {
+                elem.onParsingStart();
+            }
+        }
+
+        public void onEntryCreated(VCardEntry entry) {
+            assertTrue(mIndex < mImportVerifierElemList.size());
+            mImportVerifierElemList.get(mIndex).onEntryCreated(entry);
+            mIndex++;
+        }
+
+        public void onEnd() {
+            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 int mVCardType;
+        private final boolean mIsV30;
+        private final boolean mIsDoCoMo;
+
+        private ExportTestResolver mInputResolver;
+
+        // 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(int vcardType) {
+            mVCardVerifierInternal = new VCardVerifierInternal();
+            mIsV30 = VCardConfig.isV30(vcardType);
+            mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+            mVCardType = vcardType;
+
+            mInputResolver = null;
+        }
+
+        public ContactEntry addInputEntry() {
+            if (mInputResolver == null) {
+                mInputResolver = new ExportTestResolver();
+            }
+            return mInputResolver.buildContactEntry();
+        }
+
+        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 addPropertyNodesVerifierElemWithEmptyName() {
+            PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+            if (mIsV30) {
+                elem.addNodeWithOrder("N", "").addNodeWithOrder("FN", "");
+            } else if (mIsDoCoMo) {
+                elem.addNodeWithOrder("N", "");
+            }
+            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) {
+            // Log.d("@@@", vcard);
+            final VCardInterpreter builder;
+            if (mImportVerifier != null) {
+                final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
+                final VCardEntryConstructor vcardDataBuilder =
+                        new VCardEntryConstructor(mVCardType);
+                vcardDataBuilder.addEntryHandler(mImportVerifier);
+                if (mPropertyNodesVerifier != null) {
+                    builder = new VCardInterpreterCollection(Arrays.asList(
+                            mPropertyNodesVerifier, vcardDataBuilder));
+                } 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(mInputResolver), 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);
+            builder.append('"');
+        }
+        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/VCardUtilsTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java
new file mode 100644
index 0000000..592c285
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardUtilsTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.VCardUtils;
+
+import junit.framework.TestCase;
+
+public class VCardUtilsTests extends TestCase {
+    public void testContainsOnlyPrintableAscii() {
+        assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null));
+        assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null));
+        assertTrue(VCardUtils.containsOnlyPrintableAscii(""));
+        assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+        assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0x20; i < 0x7F; i++) {
+            builder.append((char)i);
+        }
+        assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString()));
+        assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n"));
+        assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019"));
+        assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F"));
+    }
+
+    public void testContainsOnlyNonCrLfPrintableAscii() {
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(""));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0x20; i < 0x7F; i++) {
+            builder.append((char)i);
+        }
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString()));
+        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019"));
+        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F"));
+        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r"));
+        assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n"));
+    }
+
+    public void testContainsOnlyAlphaDigitHyphen() {
+        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null));
+        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null));
+        assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen(""));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+        assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-"));
+        for (int i = 0; i < 0x30; i++) {
+            if (i == 0x2D) {  // -
+                continue;
+            }
+            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+        }
+        for (int i = 0x3A; i < 0x41; i++) {
+            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+        }
+        for (int i = 0x5B; i < 0x61; i++) {
+            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+        }
+        for (int i = 0x7B; i < 0x100; i++) {
+            assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+        }
+    }
+}
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..971fe0d 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
@@ -16,7 +16,7 @@
 package com.android.unit_tests.vcard;
 
 import android.content.ContentValues;
-import android.pim.vcard.VCardBuilder;
+import android.pim.vcard.VCardInterpreter;
 import android.pim.vcard.VCardConfig;
 import android.util.CharsetUtils;
 import android.util.Log;
@@ -36,10 +36,11 @@
  * 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"; 
+public class VNodeBuilder implements VCardInterpreter {
+    static private String LOG_TAG = "VNodeBuilder"; 
     
     /**
      * If there's no other information available, this class uses this charset for encoding
@@ -114,11 +115,10 @@
     // I leave this code as is since I'm not familiar with vcalendar specification.
     // But I believe we should refactor this code in the future.
     // Until this, the last entry has to be removed when some error occurs.
-    public void startRecord(String type) {
-        
+    public void startEntry() {
         VNode vnode = new VNode();
         vnode.parseStatus = 1;
-        vnode.VName = type;
+        vnode.VName = "VCARD";
         // I feel this should be done in endRecord(), but it cannot be done because of
         // the reason above.
         vNodeList.add(vnode);
@@ -126,7 +126,7 @@
         mCurrentVNode = vNodeList.get(mNodeListPos);
     }
 
-    public void endRecord() {
+    public void endEntry() {
         VNode endNode = vNodeList.get(mNodeListPos);
         endNode.parseStatus = 0;
         while(mNodeListPos > 0){
@@ -189,6 +189,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/BrowserTestPlugin/jni/main.cpp b/tests/BrowserTestPlugin/jni/main.cpp
index e3ad4a7..2f295f0 100644
--- a/tests/BrowserTestPlugin/jni/main.cpp
+++ b/tests/BrowserTestPlugin/jni/main.cpp
@@ -65,7 +65,6 @@
 ANPLogInterfaceV0           gLogI;
 ANPPaintInterfaceV0         gPaintI;
 ANPPathInterfaceV0          gPathI;
-ANPSystemInterfaceV0        gSystemI;
 ANPTypefaceInterfaceV0      gTypefaceI;
 ANPWindowInterfaceV0        gWindowI;
 
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/RegexTest.java b/tests/CoreTests/android/core/RegexTest.java
index a7f79e8..743afc1 100644
--- a/tests/CoreTests/android/core/RegexTest.java
+++ b/tests/CoreTests/android/core/RegexTest.java
@@ -17,7 +17,6 @@
 package android.core;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.text.util.Regex;
 
 import junit.framework.TestCase;
 
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 27755ed9..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;