am febb94cd: Make standard roaming indicator display indications configurable

Merge commit 'febb94cdbb7c7b7091401d21824bfab32b9daca5' into eclair-mr2

* commit 'febb94cdbb7c7b7091401d21824bfab32b9daca5':
  Make standard roaming indicator display indications configurable
diff --git a/api/current.xml b/api/current.xml
index ca2cceb..15ea245 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -117682,6 +117682,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"
@@ -117714,6 +117731,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"
@@ -118139,6 +118177,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"
@@ -118171,6 +118226,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"
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ff48583..5b34ef9 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -46,7 +46,7 @@
  */
 public final class BluetoothAdapter {
     private static final String TAG = "BluetoothAdapter";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;  //STOPSHIP: Remove excess logging
 
     /**
      * Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/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/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
index ca41ce5..aaa7215 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/Constants.java
@@ -16,15 +16,46 @@
 package android.pim.vcard;
 
 /**
- * Constants used in both composer and parser.
+ * Constants used in both exporter and importer code.
  */
 /* package */ class Constants {
 
-    public static final String ATTR_TYPE = "TYPE";
-    
     public static final String VERSION_V21 = "2.1";
     public static final String VERSION_V30 = "3.0";
+
+    // The property names valid both in vCard 2.1 and 3.0.
+    public static final String PROPERTY_BEGIN = "BEGIN";
+    public static final String PROPERTY_VERSION = "VERSION";
+    public static final String PROPERTY_N = "N";
+    public static final String PROPERTY_FN = "FN";
+    public static final String PROPERTY_ADR = "ADR";
+    public static final String PROPERTY_EMAIL = "EMAIL";
+    public static final String PROPERTY_NOTE = "NOTE";
+    public static final String PROPERTY_ORG = "ORG";
+    public static final String PROPERTY_SOUND = "SOUND";  // Not fully supported.
+    public static final String PROPERTY_TEL = "TEL";
+    public static final String PROPERTY_TITLE = "TITLE";
+    public static final String PROPERTY_ROLE = "ROLE";
+    public static final String PROPERTY_PHOTO = "PHOTO";
+    public static final String PROPERTY_LOGO = "LOGO";
+    public static final String PROPERTY_URL = "URL";
+    public static final String PROPERTY_BDAY = "BDAY";  // Birthday
+    public static final String PROPERTY_END = "END";
+
+    // Valid property names not supported (not appropriately handled) by our vCard importer now.
+    public static final String PROPERTY_REV = "REV";
+    public static final String PROPERTY_AGENT = "AGENT";
+
+    // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+    public static final String PROPERTY_NAME = "NAME";
+    public static final String PROPERTY_NICKNAME = "NICKNAME";
+    public static final String PROPERTY_SORT_STRING = "SORT-STRING";
     
+    // 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 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";
@@ -39,7 +70,19 @@
     // 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";
-    
+
+    // Android specific properties
+    // Use only in vCard paser code.
+    public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
+
+    // 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 ATTR_TYPE = "TYPE";
+
     // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
     //
     // e.g.
@@ -59,6 +102,7 @@
     public static final String ATTR_TYPE_VOICE = "VOICE";
     public static final String ATTR_TYPE_INTERNET = "INTERNET";
 
+    // Abbreviation of "preferable"? We interpret this value as "primary" property.
     public static final String ATTR_TYPE_PREF = "PREF";
 
     // Phone types valid in vCard and known to ContactsContract, but not so common.
@@ -73,17 +117,26 @@
     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)
+    // Attribute for Phones, which are not formally 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";
+    public static final String ATTR_PHONE_EXTRA_TYPE_OTHER = "OTHER";
+    public static final String ATTR_PHONE_EXTRA_TYPE_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";
+    public static final String ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+    public static final String ATTR_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+    public static final String ATTR_PHONE_EXTRA_TYPE_TELEX = "TELEX";
+    public static final String ATTR_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+    public static final String ATTR_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+
+    // Attribute for addresses.
+    public static final String ATTR_ADR_TYPE_PARCEL = "PARCEL";
+    public static final String ATTR_ADR_TYPE_DOM = "DOM";
+    public static final String ATTR_ADR_TYPE_INTL = "INTL";
+
+    // Attribute types not officially valid but used in some vCard exporter.
+    // Do not use in composer side.
+    public static final String ATTR_EXTRA_TYPE_COMPANY = "COMPANY";
 
     // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
     // vCard 3.0.
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 36e5e23..046fb02 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -38,6 +38,7 @@
 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.util.Log;
 
@@ -46,7 +47,6 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -55,11 +55,11 @@
  */
 public class ContactStruct {
     private static final String LOG_TAG = "vcard.ContactStruct";
-    
+
     // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
     // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
     private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-    
+
     static {
         sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
         sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
@@ -71,9 +71,6 @@
         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;
@@ -90,7 +87,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof PhoneData) {
+            if (!(obj instanceof PhoneData)) {
                 return false;
             }
             PhoneData phoneData = (PhoneData)obj;
@@ -125,7 +122,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof EmailData) {
+            if (!(obj instanceof EmailData)) {
                 return false;
             }
             EmailData emailData = (EmailData)obj;
@@ -202,7 +199,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof PostalData) {
+            if (!(obj instanceof PostalData)) {
                 return false;
             }
             PostalData postalData = (PostalData)obj;
@@ -251,40 +248,46 @@
         }
     }
     
-    /**
-     * @hide only for testing.
-     */
     static public class OrganizationData {
         public final int type;
-        public final String companyName;
-        // can be changed in some VCard format. 
-        public String positionName;
+        // non-final is Intended: we may change the values since this info is separated into
+        // two parts in vCard: "ORG" + "TITLE".
+        public String companyName;
+        public String departmentName;
+        public String titleName;
         // isPrimary is changable only when there's no appropriate one existing in
         // the original VCard.
         public boolean isPrimary;
-        public OrganizationData(int type, String companyName, String positionName,
+        public OrganizationData(int type,
+                String companyName,
+                String departmentName,
+                String titleName,
                 boolean isPrimary) {
             this.type = type;
             this.companyName = companyName;
-            this.positionName = positionName;
+            this.departmentName = departmentName;
+            this.titleName = titleName;
             this.isPrimary = isPrimary;
         }
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof OrganizationData) {
+            if (!(obj instanceof OrganizationData)) {
                 return false;
             }
             OrganizationData organization = (OrganizationData)obj;
-            return (type == organization.type && companyName.equals(organization.companyName) &&
-                    positionName.equals(organization.positionName) &&
+            return (type == organization.type &&
+                    TextUtils.equals(companyName, organization.companyName) &&
+                    TextUtils.equals(departmentName, organization.departmentName) &&
+                    TextUtils.equals(titleName, organization.titleName) &&
                     isPrimary == organization.isPrimary);
         }
-        
+
         @Override
         public String toString() {
-            return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
-                    type, companyName, positionName, isPrimary);
+            return String.format(
+                    "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+                    type, companyName, departmentName, titleName, isPrimary);
         }
     }
     
@@ -304,7 +307,7 @@
         
         @Override
         public boolean equals(Object obj) {
-            if (obj instanceof ImData) {
+            if (!(obj instanceof ImData)) {
                 return false;
             }
             ImData imData = (ImData)obj;
@@ -319,19 +322,37 @@
         }
     }
     
-    /**
-     * @hide only for testing.
-     */
     static public class PhotoData {
         public static final String FORMAT_FLASH = "SWF";
         public final int type;
         public final String formatName;  // used when type is not defined in ContactsContract.
         public final byte[] photoBytes;
+        public final boolean isPrimary;
 
-        public PhotoData(int type, String formatName, byte[] photoBytes) {
+        public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
             this.type = type;
             this.formatName = formatName;
             this.photoBytes = photoBytes;
+            this.isPrimary = isPrimary;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof PhotoData)) {
+                return false;
+            }
+            PhotoData photoData = (PhotoData)obj;
+            return (type == photoData.type &&
+                    (formatName == null ? (photoData.formatName == null) :
+                            formatName.equals(photoData.formatName)) &&
+                    (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+                    (isPrimary == photoData.isPrimary));
+        }
+
+        @Override
+        public String toString() {
+            return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+                    type, formatName, photoBytes.length, isPrimary);
         }
     }
     
@@ -342,10 +363,6 @@
         private List<String> mPropertyValueList = new ArrayList<String>();
         private byte[] mPropertyBytes;
         
-        public Property() {
-            clear();
-        }
-        
         public void setPropertyName(final String propertyName) {
             mPropertyName = propertyName;
         }
@@ -385,6 +402,7 @@
             mPropertyName = null;
             mParameterMap.clear();
             mPropertyValueList.clear();
+            mPropertyBytes = null;
         }
     }
     
@@ -421,15 +439,6 @@
     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);
     }
@@ -444,186 +453,6 @@
     }
 
     /**
-     * @hide only for testing.
-     */
-    public ContactStruct(String givenName,
-            String familyName,
-            String middleName,
-            String prefix,
-            String suffix,
-            String phoneticGivenName,
-            String pheneticFamilyName,
-            String phoneticMiddleName,
-            List<byte[]> photoBytesList,
-            List<String> notes,
-            List<PhoneData> phoneList, 
-            List<EmailData> emailList,
-            List<PostalData> postalList,
-            List<OrganizationData> organizationList,
-            List<PhotoData> photoList,
-            List<String> websiteList) {
-        this(VCardConfig.VCARD_TYPE_DEFAULT);
-        mGivenName = givenName;
-        mFamilyName = familyName;
-        mPrefix = prefix;
-        mSuffix = suffix;
-        mPhoneticGivenName = givenName;
-        mPhoneticFamilyName = familyName;
-        mPhoneticMiddleName = middleName;
-        mEmailList = emailList;
-        mPostalList = postalList;
-        mOrganizationList = organizationList;
-        mPhotoList = photoList;
-        mWebsiteList = websiteList;
-    }
-
-    // All getter methods should be used carefully, since they may change
-    // in the future as of 2009-09-24, on which I cannot be sure this structure
-    // is completely consolidated.
-    // When we are sure we will no longer change them, we'll be happy to
-    // make it complete public (withouth @hide tag)
-    //
-    // Also note that these getter methods should be used only after
-    // all properties being pushed into this object. If not, incorrect
-    // value will "be stored in the local cache and" be returned to you.
-    
-    /**
-     * @hide
-     */
-    public String getFamilyName() {
-        return mFamilyName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getGivenName() {
-        return mGivenName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getMiddleName() {
-        return mMiddleName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPrefix() {
-        return mPrefix;
-    }
-
-    /**
-     * @hide
-     */
-    public String getSuffix() {
-        return mSuffix;
-    }
-
-    /**
-     * @hide
-     */
-    public String getFullName() {
-        return mFullName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticFamilyName() {
-        return mPhoneticFamilyName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticGivenName() {
-        return mPhoneticGivenName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticMiddleName() {
-        return mPhoneticMiddleName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getPhoneticFullName() {
-        return mPhoneticFullName;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<String> getNickNameList() {
-        return mNickNameList;
-    }
-
-    /**
-     * @hide
-     */
-    public String getDisplayName() {
-        if (mDisplayName == null) {
-            constructDisplayName();
-        }
-        return mDisplayName;
-    }
-
-    /**
-     * @hide
-     */
-    public String getBirthday() {
-        return mBirthday;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<PhotoData> getPhotoList() {
-        return mPhotoList;
-    }
-
-    /**
-     * @hide
-     */
-    public final List<String> getNotes() {
-        return mNoteList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<PhoneData> getPhoneList() {
-        return mPhoneList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<EmailData> getEmailList() {
-        return mEmailList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<PostalData> getPostalList() {
-        return mPostalList;
-    }
-    
-    /**
-     * @hide
-     */
-    public final List<OrganizationData> getOrganizationList() {
-        return mOrganizationList;
-    }
-    
-    /**
      * Add a phone info to phoneList.
      * @param data phone number
      * @param type type col of content://contacts/phones
@@ -643,10 +472,24 @@
             }
         }
 
-        PhoneData phoneData = new PhoneData(type,
-                PhoneNumberUtils.formatNumber(builder.toString()),
-                label, isPrimary);
-
+        final String formattedPhoneNumber;
+        {
+            final String rawPhoneNumber = builder.toString();
+            if (VCardConfig.isJapaneseDevice(mVCardType)) {
+                // As of 2009-10-07, there's no formatNumber() which accepts
+                // the second argument and returns String directly. 
+                final SpannableStringBuilder tmpBuilder =
+                    new SpannableStringBuilder(rawPhoneNumber);
+                PhoneNumberUtils.formatNumber(tmpBuilder, PhoneNumberUtils.FORMAT_JAPAN);
+                formattedPhoneNumber = tmpBuilder.toString();
+            } else {
+                // There's no information available on vCard side. Depend on the default
+                // behavior, which may cause problem in the future when the additional format
+                // rule is supported (e.g. PhoneNumberUtils.FORMAT_KLINGON)
+                formattedPhoneNumber = PhoneNumberUtils.formatNumber(rawPhoneNumber);
+            }
+        }
+        PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary);
         mPhoneList.add(phoneData);
     }
 
@@ -666,19 +509,116 @@
     
     private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
         if (mPostalList == null) {
-            mPostalList = new ArrayList<PostalData>();
+            mPostalList = new ArrayList<PostalData>(0);
         }
         mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
     }
     
-    private void addOrganization(int type, final String companyName,
-            final String positionName, boolean isPrimary) {
+    /**
+     * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+     * {@link #handleTitleValue(String)}.
+     */
+    private void addNewOrganization(int type, final String companyName,
+            final String departmentName,
+            final String titleName, boolean isPrimary) {
         if (mOrganizationList == null) {
             mOrganizationList = new ArrayList<OrganizationData>();
         }
-        mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+        mOrganizationList.add(new OrganizationData(type, companyName,
+                departmentName, titleName, isPrimary));
     }
+
+    private static final List<String> sEmptyList = new ArrayList<String>(0);
     
+    /**
+     * Set "ORG" related values to the appropriate data. If there's more than one
+     * OrganizationData objects, this input data are attached to the last one which does not
+     * have valid values (not including empty but only null). If there's no
+     * OrganizationData object, a new OrganizationData is created, whose title is set to null.
+     */
+    private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+        if (orgList == null) {
+            orgList = sEmptyList;
+        }
+        final String companyName;
+        final String departmentName;
+        final int size = orgList.size();
+        switch (size) {
+            case 0: {
+                companyName = "";
+                departmentName = null;
+                break;
+            }
+            case 1: {
+                companyName = orgList.get(0);
+                departmentName = null;
+                break;
+            }
+            default: {  // More than 1.
+                companyName = orgList.get(0);
+                // We're not sure which is the correct string for department.
+                // In order to keep all the data, concatinate the rest of elements.
+                StringBuilder builder = new StringBuilder();
+                for (int i = 1; i < size; i++) {
+                    if (i > 1) {
+                        builder.append(' ');
+                    }
+                    builder.append(orgList.get(i));
+                }
+                departmentName = builder.toString();
+            }
+        }
+        if (mOrganizationList == null) {
+            // Create new first organization entry, with "null" title which may be
+            // added via handleTitleValue().
+            addNewOrganization(type, companyName, departmentName, null, isPrimary);
+            return;
+        }
+        for (OrganizationData organizationData : mOrganizationList) {
+            // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+            // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+            if (organizationData.companyName == null &&
+                    organizationData.departmentName == null) {
+                // Probably the "TITLE" property comes before the "ORG" property via
+                // handleTitleLine().
+                organizationData.companyName = companyName;
+                organizationData.departmentName = departmentName;
+                organizationData.isPrimary = isPrimary;
+                return;
+            }
+        }
+        // No OrganizatioData is available. Create another one, with "null" title, which may be
+        // added via handleTitleValue().
+        addNewOrganization(type, companyName, departmentName, null, isPrimary);
+    }
+
+    private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+    /**
+     * Set "title" value to the appropriate data. If there's more than one
+     * OrganizationData objects, this input is attached to the last one which does not
+     * have valid title value (not including empty but only null). If there's no
+     * OrganizationData object, a new OrganizationData is created, whose company name is
+     * set to null.
+     */
+    private void handleTitleValue(final String title) {
+        if (mOrganizationList == null) {
+            // Create new first organization entry, with "null" other info, which may be
+            // added via handleOrgValue().
+            addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+            return;
+        }
+        for (OrganizationData organizationData : mOrganizationList) {
+            if (organizationData.titleName == null) {
+                organizationData.titleName = title;
+                return;
+            }
+        }
+        // No Organization is available. Create another one, with "null" other info, which may be
+        // added via handleOrgValue().
+        addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+    }
+
     private void addIm(int type, String data, String label, boolean isPrimary) {
         if (mImList == null) {
             mImList = new ArrayList<ImData>();
@@ -693,43 +633,14 @@
         mNoteList.add(note);
     }
     
-    private void addPhotoBytes(String formatName, byte[] photoBytes) {
+    private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
         if (mPhotoList == null) {
             mPhotoList = new ArrayList<PhotoData>(1);
         }
-        final PhotoData photoData = new PhotoData(0, null, photoBytes);
+        final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
         mPhotoList.add(photoData);
     }
 
-    /**
-     * Set "position" value to the appropriate data. If there's more than one
-     * OrganizationData objects, the value is set to the last one. If there's no
-     * OrganizationData object, a new OrganizationData is created, whose company name is
-     * empty.  
-     * 
-     * TODO: incomplete logic. fix this:
-     * 
-     * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
-     * know how to handle it in general cases...
-     * ----
-     * TITLE:Software Engineer
-     * ORG:Google
-     * ----
-     */
-    private void setPosition(String positionValue) {
-        if (mOrganizationList == null) {
-            mOrganizationList = new ArrayList<OrganizationData>();
-        }
-        int size = mOrganizationList.size();
-        if (size == 0) {
-            addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
-                    "", null, false);
-            size = 1;
-        }
-        OrganizationData lastData = mOrganizationList.get(size - 1);
-        lastData.positionName = positionValue;
-    }
- 
     @SuppressWarnings("fallthrough")
     private void handleNProperty(List<String> elems) {
         // Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -755,7 +666,7 @@
             mFamilyName = elems.get(0);
         }
     }
-    
+
     /**
      * Some Japanese mobile phones use this field for phonetic name,
      *  since vCard 2.1 does not have "SORT-STRING" type.
@@ -796,28 +707,36 @@
         }
         final String propValue = listToString(propValueList).trim();
         
-        if (propName.equals("VERSION")) {
+        if (propName.equals(Constants.PROPERTY_VERSION)) {
             // vCard version. Ignore this.
-        } else if (propName.equals("FN")) {
+        } else if (propName.equals(Constants.PROPERTY_FN)) {
             mFullName = propValue;
-        } else if (propName.equals("NAME") && mFullName == null) {
+        } else if (propName.equals(Constants.PROPERTY_NAME) && mFullName == null) {
             // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
             // actually exist in the real vCard data, does not exist.
             mFullName = propValue;
-        } else if (propName.equals("N")) {
+        } else if (propName.equals(Constants.PROPERTY_N)) {
             handleNProperty(propValueList);
-        } else if (propName.equals("SORT-STRING")) {
+        } else if (propName.equals(Constants.PROPERTY_NICKNAME)) {
             mPhoneticFullName = propValue;
-        } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+        } else if (propName.equals(Constants.PROPERTY_NICKNAME) ||
+                propName.equals(Constants.PROPERTY_X_NICKNAME)) {
             addNickName(propValue);
-        } else if (propName.equals("SOUND")) {
+        } else if (propName.equals(Constants.PROPERTY_SOUND)) {
             Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
             if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
-                handlePhoneticNameFromSound(propValueList);
+                // As of 2009-10-08, Parser side does not split a property value into separated
+                // values using ';' (in other words, propValueList.size() == 1),
+                // which is correct behavior from the view of vCard 2.1.
+                // But we want it to be separated, so do the separation here.
+                final List<String> phoneticNameList =
+                    VCardUtils.constructListFromValue(propValue,    
+                            VCardConfig.isV30(mVCardType));
+                handlePhoneticNameFromSound(phoneticNameList);
             } else {
                 // Ignore this field since Android cannot understand what it is.
             }
-        } else if (propName.equals("ADR")) {
+        } else if (propName.equals(Constants.PROPERTY_ADR)) {
             boolean valuesAreAllEmpty = true;
             for (String value : propValueList) {
                 if (value.length() > 0) {
@@ -836,23 +755,21 @@
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
                     typeString = typeString.toUpperCase();
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
-                        // Only first "PREF" is considered.
-                        mPrefIsSet_Address = true;
+                    if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
                         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")) {
+                            typeString.equalsIgnoreCase(Constants.ATTR_EXTRA_TYPE_COMPANY)) {
                         // "COMPANY" seems emitted by Windows Mobile, which is not
                         // specifically supported by vCard 2.1. We assume this is same
                         // as "WORK".
                         type = StructuredPostal.TYPE_WORK;
                         label = "";
-                    } else if (typeString.equals("PARCEL") || 
-                            typeString.equals("DOM") ||
-                            typeString.equals("INTL")) {
+                    } else if (typeString.equals(Constants.ATTR_ADR_TYPE_PARCEL) ||
+                            typeString.equals(Constants.ATTR_ADR_TYPE_DOM) ||
+                            typeString.equals(Constants.ATTR_ADR_TYPE_INTL)) {
                         // We do not have any appropriate way to store this information.
                     } else {
                         if (typeString.startsWith("X-") && type < 0) {
@@ -871,7 +788,7 @@
             }
 
             addPostal(type, propValueList, label, isPrimary);
-        } else if (propName.equals("EMAIL")) {
+        } else if (propName.equals(Constants.PROPERTY_EMAIL)) {
             int type = -1;
             String label = null;
             boolean isPrimary = false;
@@ -879,9 +796,7 @@
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
                     typeString = typeString.toUpperCase();
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
-                        // Only first "PREF" is considered.
-                        mPrefIsSet_Email = true;
+                    if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
                         isPrimary = true;
                     } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
                         type = Email.TYPE_HOME;
@@ -905,50 +820,47 @@
                 type = Email.TYPE_OTHER;
             }
             addEmail(type, propValue, label, isPrimary);
-        } else if (propName.equals("ORG")) {
+        } else if (propName.equals(Constants.PROPERTY_ORG)) {
             // vCard specification does not specify other types.
-            int type = Organization.TYPE_WORK;
+            final int type = Organization.TYPE_WORK;
             boolean isPrimary = false;
-            
             Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
             if (typeCollection != null) {
                 for (String typeString : typeCollection) {
-                    if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
-                        // vCard specification officially does not have PREF in ORG.
-                        // This is just for safety.
-                        mPrefIsSet_Organization = true;
+                    if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
                         isPrimary = true;
                     }
                 }
             }
-
-            StringBuilder builder = new StringBuilder();
-            for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
-                builder.append(iter.next());
-                if (iter.hasNext()) {
-                    builder.append(' ');
-                }
-            }
-            addOrganization(type, builder.toString(), "", isPrimary);
-        } else if (propName.equals("TITLE")) {
-            setPosition(propValue);
-        } else if (propName.equals("ROLE")) {
-            setPosition(propValue);
-        } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
-            String formatName = null;
-            Collection<String> typeCollection = paramMap.get("TYPE");
-            if (typeCollection != null) {
-                formatName = typeCollection.iterator().next();
-            }
+            handleOrgValue(type, propValueList, isPrimary);
+        } else if (propName.equals(Constants.PROPERTY_TITLE)) {
+            handleTitleValue(propValue);
+        } else if (propName.equals(Constants.PROPERTY_ROLE)) {
+            // This conflicts with TITLE. Ignore for now...
+            // handleTitleValue(propValue);
+        } else if (propName.equals(Constants.PROPERTY_PHOTO) ||
+                propName.equals(Constants.PROPERTY_LOGO)) {
             Collection<String> paramMapValue = paramMap.get("VALUE");
             if (paramMapValue != null && paramMapValue.contains("URL")) {
                 // Currently we do not have appropriate example for testing this case.
             } else {
-                addPhotoBytes(formatName, propBytes);
+                final Collection<String> typeCollection = paramMap.get("TYPE");
+                String formatName = null;
+                boolean isPrimary = false;
+                if (typeCollection != null) {
+                    for (String typeValue : typeCollection) {
+                        if (Constants.ATTR_TYPE_PREF.equals(typeValue)) {
+                            isPrimary = true;
+                        } else if (formatName == null){
+                            formatName = typeValue;
+                        }
+                    }
+                }
+                addPhotoBytes(formatName, propBytes, isPrimary);
             }
-        } else if (propName.equals("TEL")) {
-            Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
-            Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+        } else if (propName.equals(Constants.PROPERTY_TEL)) {
+            final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+            final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
             final int type;
             final String label;
             if (typeObject instanceof Integer) {
@@ -960,9 +872,7 @@
             }
             
             final boolean isPrimary;
-            if (!mPrefIsSet_Phone && typeCollection != null &&
-                    typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
-                mPrefIsSet_Phone = true;
+            if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
                 isPrimary = true;
             } else {
                 isPrimary = false;
@@ -975,9 +885,7 @@
             int type = Phone.TYPE_OTHER;
             final String label = null;
             final boolean isPrimary;
-            if (!mPrefIsSet_Phone && typeCollection != null &&
-                    typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
-                mPrefIsSet_Phone = true;
+            if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
                 isPrimary = true;
             } else {
                 isPrimary = false;
@@ -1002,20 +910,20 @@
                 type = Phone.TYPE_HOME;
             }
             addIm(type, propValue, null, isPrimary);
-        } else if (propName.equals("NOTE")) {
+        } else if (propName.equals(Constants.PROPERTY_NOTE)) {
             addNote(propValue);
-        } else if (propName.equals("URL")) {
+        } else if (propName.equals(Constants.PROPERTY_URL)) {
             if (mWebsiteList == null) {
                 mWebsiteList = new ArrayList<String>(1);
             }
             mWebsiteList.add(propValue);
-        } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
             mPhoneticGivenName = propValue;
-        } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
             mPhoneticMiddleName = propValue;
-        } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
+        } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
             mPhoneticFamilyName = propValue;
-        } else if (propName.equals("BDAY")) {
+        } else if (propName.equals(Constants.PROPERTY_BDAY)) {
             mBirthday = propValue;
         /*} else if (propName.equals("REV")) {                
             // Revision of this VCard entry. I think we can ignore this.
@@ -1048,7 +956,10 @@
      * Construct the display name. The constructed data must not be null.
      */
     private void constructDisplayName() {
-        if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+        // FullName (created via "FN" or "NAME" field) is prefered.
+        if (!TextUtils.isEmpty(mFullName)) {
+            mDisplayName = mFullName;
+        } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
             StringBuilder builder = new StringBuilder();
             List<String> nameList;
             switch (VCardConfig.getNameOrderType(mVCardType)) {
@@ -1079,8 +990,6 @@
                 }
             }
             mDisplayName = builder.toString();
-        } else if (!TextUtils.isEmpty(mFullName)) {
-            mDisplayName = mFullName;
         } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
                 TextUtils.isEmpty(mPhoneticGivenName))) {
             mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1103,25 +1012,10 @@
      */
     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.
@@ -1181,22 +1075,16 @@
         }
 
         if (mNickNameList != null && mNickNameList.size() > 0) {
-            boolean first = true;
             for (String nickName : mNickNameList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
                 builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
                 builder.withValue(Nickname.NAME, nickName);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                    first = false;
-                }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mPhoneList != null) {
             for (PhoneData phoneData : mPhoneList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1097,34 @@
                 }
                 builder.withValue(Phone.NUMBER, phoneData.data);
                 if (phoneData.isPrimary) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
+                    builder.withValue(Phone.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mOrganizationList != null) {
-            boolean first = true;
             for (OrganizationData organizationData : mOrganizationList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
-                // Currently, we do not use TYPE_CUSTOM.
                 builder.withValue(Organization.TYPE, organizationData.type);
-                builder.withValue(Organization.COMPANY, organizationData.companyName);
-                builder.withValue(Organization.TITLE, organizationData.positionName);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
+                if (organizationData.companyName != null) {
+                    builder.withValue(Organization.COMPANY, organizationData.companyName);
+                }
+                if (organizationData.departmentName != null) {
+                    builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+                }
+                if (organizationData.titleName != null) {
+                    builder.withValue(Organization.TITLE, organizationData.titleName);
+                }
+                if (organizationData.isPrimary) {
+                    builder.withValue(Organization.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mEmailList != null) {
             for (EmailData emailData : mEmailList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1265,7 +1157,6 @@
                 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);
@@ -1282,22 +1173,19 @@
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
                 builder.withValue(Note.NOTE, note);
                 operationList.add(builder.build());
             }
         }
-        
+
         if (mPhotoList != null) {
-            boolean first = true;
             for (PhotoData photoData : mPhotoList) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
                 builder.withValue(Photo.PHOTO, photoData.photoBytes);
-                if (first) {
-                    builder.withValue(Data.IS_PRIMARY, 1);
-                    first = false;
+                if (photoData.isPrimary) {
+                    builder.withValue(Photo.IS_PRIMARY, 1);
                 }
                 operationList.add(builder.build());
             }
@@ -1310,12 +1198,12 @@
                 builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
                 builder.withValue(Website.URL, website);
                 // There's no information about the type of URL in vCard.
-                // We use TYPE_HOME for safety. 
-                builder.withValue(Website.TYPE, Website.TYPE_HOME);
+                // We use TYPE_HOMEPAGE for safety. 
+                builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
                 operationList.add(builder.build());
             }
         }
-        
+
         if (!TextUtils.isEmpty(mBirthday)) {
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
             builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1364,4 +1252,99 @@
             return "";
         }
     }
+
+    // All getter methods should be used carefully, since they may change
+    // in the future as of 2009-10-05, on which I cannot be sure this structure
+    // is completely consolidated.
+    //
+    // Also note that these getter methods should be used only after
+    // all properties being pushed into this object. If not, incorrect
+    // value will "be stored in the local cache and" be returned to you.
+
+    public String getFamilyName() {
+        return mFamilyName;
+    }
+
+    public String getGivenName() {
+        return mGivenName;
+    }
+
+    public String getMiddleName() {
+        return mMiddleName;
+    }
+
+    public String getPrefix() {
+        return mPrefix;
+    }
+
+    public String getSuffix() {
+        return mSuffix;
+    }
+
+    public String getFullName() {
+        return mFullName;
+    }
+
+    public String getPhoneticFamilyName() {
+        return mPhoneticFamilyName;
+    }
+
+    public String getPhoneticGivenName() {
+        return mPhoneticGivenName;
+    }
+
+    public String getPhoneticMiddleName() {
+        return mPhoneticMiddleName;
+    }
+
+    public String getPhoneticFullName() {
+        return mPhoneticFullName;
+    }
+
+    public final List<String> getNickNameList() {
+        return mNickNameList;
+    }
+
+    public String getBirthday() {
+        return mBirthday;
+    }
+
+    public final List<String> getNotes() {
+        return mNoteList;
+    }
+
+    public final List<PhoneData> getPhoneList() {
+        return mPhoneList;
+    }
+
+    public final List<EmailData> getEmailList() {
+        return mEmailList;
+    }
+
+    public final List<PostalData> getPostalList() {
+        return mPostalList;
+    }
+
+    public final List<OrganizationData> getOrganizationList() {
+        return mOrganizationList;
+    }
+
+    public final List<ImData> getImList() {
+        return mImList;
+    }
+
+    public final List<PhotoData> getPhotoList() {
+        return mPhotoList;
+    }
+
+    public final List<String> getWebsiteList() {
+        return mWebsiteList;
+    }
+
+    public String getDisplayName() {
+        if (mDisplayName == null) {
+            constructDisplayName();
+        }
+        return mDisplayName;
+    }
 }
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index c4711f8..b23fc3d 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -72,14 +72,30 @@
  * 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";
@@ -95,28 +111,72 @@
     public static final String FAILURE_REASON_NOT_INITIALIZED =
         "The vCard composer object is not correctly initialized";
 
+    /** Should be visible only from developers... (no need to translate, hopefully) */
+    public static final String FAILURE_REASON_UNSUPPORTED_URI =
+        "The Uri vCard composer received is not supported by the composer.";
+
     public static final String NO_ERROR = "No error";
 
+    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+    // Property for call log entry
+    private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
+    private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
+    private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
+    private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
+
+    private static final String VCARD_DATA_VCARD = "VCARD";
+    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+    private static final String VCARD_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 = "=";
+
+    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";
+
+    /**
+     * 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, 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.
     }
 
     public static interface OneEntryHandler {
         public boolean onInit(Context context);
-
         public boolean onEntryCreated(String vcard);
-
         public void onTerminate();
     }
 
     /**
      * <p>
-     * An useful example handler, which emits VCard String to outputstream one
-     * by one.
+     * An useful example handler, which emits VCard String to outputstream one by one.
      * </p>
      * <p>
      * The input OutputStream object is closed() on {{@link #onTerminate()}.
@@ -211,65 +271,6 @@
         }
     }
 
-    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
-    private static final String VCARD_PROPERTY_ADR = "ADR";
-    private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
-    private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
-    private static final String VCARD_PROPERTY_END = "END";
-    private static final String VCARD_PROPERTY_NAME = "N";
-    private static final String VCARD_PROPERTY_FULL_NAME = "FN";
-    private static final String VCARD_PROPERTY_NOTE = "NOTE";
-    private static final String VCARD_PROPERTY_ORG = "ORG";
-    private static final String VCARD_PROPERTY_SOUND = "SOUND";
-    private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
-    private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
-    private static final String VCARD_PROPERTY_TEL = "TEL";
-    private static final String VCARD_PROPERTY_TITLE = "TITLE";
-    private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
-    private static final String VCARD_PROPERTY_VERSION = "VERSION";
-    private static final String VCARD_PROPERTY_URL = "URL";
-    private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
-    private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
-    private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
-    private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
-    // Android specific properties
-    // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
-    private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
-    // Property for call log entry
-    private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
-    private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
-    private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
-    private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
-    // Properties for DoCoMo vCard.
-    private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
-    private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
-    private static final String VCARD_PROPERTY_X_NO = "X-NO";
-    private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
-    private static final String VCARD_DATA_VCARD = "VCARD";
-    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
-    private static final String VCARD_ATTR_SEPARATOR = ";";
-    private static final String VCARD_COL_SEPARATOR = "\r\n";
-    private static final String VCARD_DATA_SEPARATOR = ":";
-    private static final String VCARD_ITEM_SEPARATOR = ";";
-    private static final String VCARD_WS = " ";
-    private static final String VCARD_ATTR_EQUAL = "=";
-
-    // Type strings are now in VCardConstants.java.
-
-    private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
-    private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
-    private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-
     private final Context mContext;
     private final int mVCardType;
     private final boolean mCareHandlerErrors;
@@ -298,20 +299,7 @@
 
     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 mIsCallLogComposer;
 
     private static final String[] sContactsProjection = new String[] {
         Contacts._ID,
@@ -332,30 +320,24 @@
     private static final String FLAG_TIMEZONE_UTC = "Z";
 
     public VCardComposer(Context context) {
-        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false);
+        this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
     }
 
-    public VCardComposer(Context context, String vcardTypeStr,
-            boolean careHandlerErrors) {
-        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
-                careHandlerErrors, false);
+    public VCardComposer(Context context, int vcardType) {
+        this(context, vcardType, true);
     }
 
-    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
-        this(context, vcardType, careHandlerErrors, false);
+    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) {
+    public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
         mContext = context;
         mVCardType = vcardType;
         mCareHandlerErrors = careHandlerErrors;
-        mIsCallLogComposer = isCallLogComposer;
         mContentResolver = context.getContentResolver();
 
         mIsV30 = VCardConfig.isV30(vcardType);
@@ -389,50 +371,32 @@
     }
 
     /**
-     * This static function is to compose vCard for phone own number
-     */
-    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
-            String phoneNumber, boolean vcardVer21) {
-        final StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        if (!vcardVer21) {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
-        } else {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
-        }
-
-        boolean needCharset = false;
-        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
-            needCharset = true;
-        }
-        // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
-        appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
-        appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
-        String label = Integer.toString(phonetype);
-        appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
-
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
-        return builder.toString();
-    }
-
-    /**
      * Must call before {{@link #init()}.
      */
     public void addHandler(OneEntryHandler handler) {
         mHandlerList.add(handler);
     }
 
-    public boolean init() {
-        return init(null, null);
-    }
-
     /**
      * @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());
@@ -451,13 +415,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;
@@ -534,89 +504,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>>();
@@ -633,8 +520,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) {
@@ -663,11 +549,11 @@
         }
 
         final StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
         if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
         } else {
-            appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
         }
 
         appendStructuredNames(builder, contentValuesListMap);
@@ -684,13 +570,13 @@
         // 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, Constants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+            appendVCardLine(builder, Constants.PROPERTY_X_REDUCTION, "");
+            appendVCardLine(builder, Constants.PROPERTY_X_NO, "");
+            appendVCardLine(builder, Constants.PROPERTY_X_DCM_HMN_MODE, "");
         }
 
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
 
         return builder.toString();
     }
@@ -748,11 +634,11 @@
         if (contentValuesList != null && contentValuesList.size() > 0) {
             appendStructuredNamesInternal(builder, contentValuesList);
         } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
         } else if (mIsV30) {
             // vCard 3.0 requires "N" and "FN" properties.
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
+            appendVCardLine(builder, Constants.PROPERTY_FN, "");
         }
     }
 
@@ -822,7 +708,7 @@
             }
 
             // N property. This order is specified by vCard spec and does not depend on countries.
-            builder.append(VCARD_PROPERTY_NAME);
+            builder.append(Constants.PROPERTY_N);
             if (shouldAppendCharsetAttribute(Arrays.asList(
                     familyName, givenName, middleName, prefix, suffix))) {
                 builder.append(VCARD_ATTR_SEPARATOR);
@@ -858,7 +744,7 @@
                             escapeCharacters(fullname);
 
             // FN property
-            builder.append(VCARD_PROPERTY_FULL_NAME);
+            builder.append(Constants.PROPERTY_FN);
             if (shouldAppendCharsetAttribute(encodedFullname)) {
                 builder.append(VCARD_ATTR_SEPARATOR);
                 builder.append(mVCardAttributeCharset);
@@ -879,7 +765,7 @@
                             encodeQuotedPrintable(displayName) :
                                 escapeCharacters(displayName);
 
-            builder.append(VCARD_PROPERTY_NAME);
+            builder.append(Constants.PROPERTY_N);
             if (shouldAppendCharsetAttribute(encodedDisplayName)) {
                 builder.append(VCARD_ATTR_SEPARATOR);
                 builder.append(mVCardAttributeCharset);
@@ -896,10 +782,10 @@
             builder.append(VCARD_ITEM_SEPARATOR);
             builder.append(VCARD_COL_SEPARATOR);
         } else if (mIsDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
         } else if (mIsV30) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-            appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
+            appendVCardLine(builder, Constants.PROPERTY_N, "");
+            appendVCardLine(builder, Constants.PROPERTY_FN, "");
         }
 
         String phoneticFamilyName = primaryContentValues
@@ -926,7 +812,7 @@
                                 phoneticFamilyName,
                                 phoneticMiddleName,
                                 phoneticGivenName);
-                builder.append(VCARD_PROPERTY_SORT_STRING);
+                builder.append(Constants.PROPERTY_SORT_STRING);
 
                 // Do not need to care about QP, since vCard 3.0 does not allow it.
                 final String encodedSortString = escapeCharacters(sortString);
@@ -944,7 +830,7 @@
                 //       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(Constants.PROPERTY_SOUND);
                 builder.append(VCARD_ATTR_SEPARATOR);
                 builder.append(Constants.ATTR_TYPE_X_IRMC_N);
 
@@ -987,7 +873,7 @@
                 builder.append(VCARD_COL_SEPARATOR);
             }
         } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_SOUND);
+            builder.append(Constants.PROPERTY_SOUND);
             builder.append(VCARD_ATTR_SEPARATOR);
             builder.append(Constants.ATTR_TYPE_X_IRMC_N);
             builder.append(VCARD_DATA_SEPARATOR);
@@ -1009,7 +895,7 @@
                 } else {
                     encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
+                builder.append(Constants.PROPERTY_X_PHONETIC_FIRST_NAME);
                 if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
                     builder.append(VCARD_ATTR_SEPARATOR);
                     builder.append(mVCardAttributeCharset);
@@ -1032,7 +918,7 @@
                 } else {
                     encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
+                builder.append(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
                 if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
                     builder.append(VCARD_ATTR_SEPARATOR);
                     builder.append(mVCardAttributeCharset);
@@ -1055,7 +941,7 @@
                 } else {
                     encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
                 }
-                builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
+                builder.append(Constants.PROPERTY_X_PHONETIC_LAST_NAME);
                 if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
                     builder.append(VCARD_ATTR_SEPARATOR);
                     builder.append(mVCardAttributeCharset);
@@ -1078,9 +964,9 @@
         if (contentValuesList != null) {
             final String propertyNickname;
             if (mIsV30) {
-                propertyNickname = VCARD_PROPERTY_NICKNAME;
-            } else if (mUsesAndroidProperty) {
-                propertyNickname = VCARD_PROPERTY_X_NICKNAME;
+                propertyNickname = Constants.PROPERTY_NICKNAME;
+            /*} else if (mUsesAndroidProperty) {
+                propertyNickname = VCARD_PROPERTY_X_NICKNAME;*/
             } else {
                 // There's no way to add this field.
                 return;
@@ -1194,7 +1080,7 @@
                 appendPostalsForGeneric(builder, contentValuesList);
             }
         } else if (mIsDoCoMo) {
-            builder.append(VCARD_PROPERTY_ADR);
+            builder.append(Constants.PROPERTY_ADR);
             builder.append(VCARD_ATTR_SEPARATOR);
             builder.append(Constants.ATTR_TYPE_HOME);
             builder.append(VCARD_DATA_SEPARATOR);
@@ -1290,7 +1176,7 @@
                     website = website.trim();
                 }
                 if (!TextUtils.isEmpty(website)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_URL, website);
+                    appendVCardLine(builder, Constants.PROPERTY_URL, website);
                 }
             }
         }
@@ -1313,7 +1199,7 @@
                 birthday = birthday.trim();
             }
             if (!TextUtils.isEmpty(birthday)) {
-                appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+                appendVCardLine(builder, Constants.PROPERTY_BDAY, birthday);
             }
         }
     }
@@ -1336,13 +1222,13 @@
                 }
 
                 if (!TextUtils.isEmpty(company)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
+                    appendVCardLine(builder, Constants.PROPERTY_ORG, company,
                             !VCardUtils.containsOnlyPrintableAscii(company),
                             (mUsesQuotedPrintable &&
                                     !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
                 }
                 if (!TextUtils.isEmpty(title)) {
-                    appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
+                    appendVCardLine(builder, Constants.PROPERTY_TITLE, title,
                             !VCardUtils.containsOnlyPrintableAscii(title),
                             (mUsesQuotedPrintable &&
                                     !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
@@ -1420,7 +1306,7 @@
                 final boolean reallyUseQuotedPrintable =
                         (mUsesQuotedPrintable &&
                             !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+                appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
                         shouldAppendCharsetInfo, reallyUseQuotedPrintable);
             } else {
                 for (ContentValues contentValues : contentValuesList) {
@@ -1431,7 +1317,7 @@
                         final boolean reallyUseQuotedPrintable =
                                 (mUsesQuotedPrintable &&
                                     !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
-                        appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
+                        appendVCardLine(builder, Constants.PROPERTY_NOTE, noteStr,
                                 shouldAppendCharsetInfo, reallyUseQuotedPrintable);
                     }
                 }
@@ -1517,7 +1403,7 @@
     private void appendVCardPhotoLine(final StringBuilder builder,
             final String encodedData, final String photoType) {
         StringBuilder tmpBuilder = new StringBuilder();
-        tmpBuilder.append(VCARD_PROPERTY_PHOTO);
+        tmpBuilder.append(Constants.PROPERTY_PHOTO);
         tmpBuilder.append(VCARD_ATTR_SEPARATOR);
         if (mIsV30) {
             tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
@@ -1550,7 +1436,7 @@
     private void appendVCardPostalLine(final StringBuilder builder,
             final Integer typeAsObject, final String label,
             final ContentValues contentValues) {
-        builder.append(VCARD_PROPERTY_ADR);
+        builder.append(Constants.PROPERTY_ADR);
         builder.append(VCARD_ATTR_SEPARATOR);
 
         // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
@@ -1684,7 +1570,7 @@
 
     private void appendVCardEmailLine(final StringBuilder builder,
             final Integer typeAsObject, final String label, final String data) {
-        builder.append(VCARD_PROPERTY_EMAIL);
+        builder.append(Constants.PROPERTY_EMAIL);
 
         final int typeAsPrimitive;
         if (typeAsObject == null) {
@@ -1743,7 +1629,7 @@
     private void appendVCardTelephoneLine(final StringBuilder builder,
             final Integer typeAsObject, final String label,
             String encodedData) {
-        builder.append(VCARD_PROPERTY_TEL);
+        builder.append(Constants.PROPERTY_TEL);
         builder.append(VCARD_ATTR_SEPARATOR);
 
         final int typeAsPrimitive;
@@ -1961,4 +1847,118 @@
 
         return tmpBuilder.toString();
     }
+
+    //// The methods bellow are for call log history ////
+
+    /**
+     * This static function is to compose vCard for phone own number
+     */
+    public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
+            String phoneNumber, boolean vcardVer21) {
+        final StringBuilder builder = new StringBuilder();
+        appendVCardLine(builder, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        if (!vcardVer21) {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+        } else {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+        }
+
+        boolean needCharset = false;
+        if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
+            needCharset = true;
+        }
+        // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
+        appendVCardLine(builder, Constants.PROPERTY_FN, phoneName, needCharset, false);
+        appendVCardLine(builder, Constants.PROPERTY_N, phoneName, needCharset, false);
+
+        String label = Integer.toString(phonetype);
+        appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
+
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+
+        return builder.toString();
+    }
+
+    /**
+     * Format according to RFC 2445 DATETIME type.
+     * The format is: ("%Y%m%dT%H%M%SZ").
+     */
+    private final String toRfc2455Format(final long millSecs) {
+        Time startDate = new Time();
+        startDate.set(millSecs);
+        String date = startDate.format2445();
+        return date + FLAG_TIMEZONE_UTC;
+    }
+
+    /**
+     * Try to append the property line for a call history time stamp field if possible.
+     * Do nothing if the call log type gotton from the database is invalid.
+     */
+    private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
+        // Extension for call history as defined in
+        // in the Specification for Ic Mobile Communcation - ver 1.1,
+        // Oct 2000. This is used to send the details of the call
+        // history - missed, incoming, outgoing along with date and time
+        // to the requesting device (For example, transferring phone book
+        // when connected over bluetooth)
+        //
+        // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
+        final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
+        final String callLogTypeStr;
+        switch (callLogType) {
+            case Calls.INCOMING_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
+                break;
+            }
+            case Calls.OUTGOING_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
+                break;
+            }
+            case Calls.MISSED_TYPE: {
+                callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
+                break;
+            }
+            default: {
+                Log.w(LOG_TAG, "Call log type not correct.");
+                return;
+            }
+        }
+
+        final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
+        builder.append(VCARD_PROPERTY_X_TIMESTAMP);
+        builder.append(VCARD_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, Constants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+        if (mIsV30) {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V30);
+        } else {
+            appendVCardLine(builder, Constants.PROPERTY_VERSION, Constants.VERSION_V21);
+        }
+        String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
+        if (TextUtils.isEmpty(name)) {
+            name = mCursor.getString(NUMBER_COLUMN_INDEX);
+        }
+        final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
+        // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
+        appendVCardLine(builder, Constants.PROPERTY_FN, name, needCharset, false);
+        appendVCardLine(builder, Constants.PROPERTY_N, name, needCharset, false);
+
+        String number = mCursor.getString(NUMBER_COLUMN_INDEX);
+        int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+        String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+        if (TextUtils.isEmpty(label)) {
+            label = Integer.toString(type);
+        }
+        appendVCardTelephoneLine(builder, type, label, number);
+        tryAppendCallHistoryTimeStampField(builder);
+        appendVCardLine(builder, Constants.PROPERTY_END, VCARD_DATA_VCARD);
+        return builder.toString();
+    }
 }
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..665fd4b 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -41,8 +41,8 @@
     // TODO: make the other codes use this flag
     public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
     
-    private static final int FLAG_V21 = 0;
-    private static final int FLAG_V30 = 1;
+    public static final int FLAG_V21 = 0;
+    public static final int FLAG_V30 = 1;
 
     // 0x2 is reserved for the future use ...
 
@@ -105,8 +105,8 @@
      * behavior around this flag in the future. Do not use this flag without any reason.
      */
     public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-    
-    // VCard types
+
+    //// The followings are VCard types available from importer/exporter. ////
 
     /**
      * General vCard format with the version 2.1. Uses UTF-8 for the charset.
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java
index d2026d0..d00f616 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardDataBuilder.java
@@ -86,7 +86,7 @@
             boolean strictLineBreakParsing, int vcardType, Account account) {
         this(null, charset, strictLineBreakParsing, vcardType, account);
     }
-    
+
     /**
      * @hide
      */
@@ -127,6 +127,18 @@
     }
 
     /**
+     * Called when the parse failed between startRecord() and endRecord().
+     * Currently it happens only when the vCard format is 3.0.
+     * (VCardVersionException is thrown by VCardParser_V21 and this object is reused by
+     * VCardParser_V30. At that time, startRecord() is called twice before endRecord() is called.)
+     * TODO: Should this be in VCardBuilder interface?
+     */
+    public void clear() {
+        mCurrentContactStruct = null;
+        mCurrentProperty = new ContactStruct.Property();
+    }
+
+    /**
      * Assume that VCard is not nested. In other words, this code does not accept 
      */
     public void startRecord(String type) {
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..b3ff8fa 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -144,10 +144,14 @@
         }
     }
 
-    protected String getVersion() {
-        return "2.1";
+    protected int getVersion() {
+        return VCardConfig.FLAG_V21;
     }
-    
+
+    protected String getVersionString() {
+        return Constants.VERSION_V21;
+    }
+
     /**
      * @return true when the propertyName is a valid property name.
      */
@@ -356,7 +360,7 @@
      *      / [groups "."] "ADR"   [params] ":" addressparts CRLF
      *      / [groups "."] "ORG"   [params] ":" orgparts CRLF
      *      / [groups "."] "N"     [params] ":" nameparts CRLF
-     *      / [groups "."] "AGENT" [params] ":" vcard CRLF 
+     *      / [groups "."] "AGENT" [params] ":" vcard CRLF
      */
     protected boolean parseItem() throws IOException, VCardException {
         mEncoding = sDefaultEncoding;
@@ -392,9 +396,10 @@
                 } else {
                     throw new VCardException("Unknown BEGIN type: " + propertyValue);
                 }
-            } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+            } else if (propertyName.equals("VERSION") &&
+                    !propertyValue.equals(getVersionString())) {
                 throw new VCardVersionException("Incompatible version: " + 
-                        propertyValue + " != " + getVersion());
+                        propertyValue + " != " + getVersionString());
             }
             start = System.currentTimeMillis();
             handlePropertyValue(propertyName, propertyValue);
@@ -520,11 +525,19 @@
                 throw new VCardException("Unknown type \"" + paramName + "\"");
             }
         } else {
-            handleType(strArray[0]);
+            handleParamWithoutName(strArray[0]);
         }
     }
     
     /**
+     * vCard 3.0 parser may throw VCardException.
+     */
+    @SuppressWarnings("unused")
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        handleType(paramValue);
+    }
+
+    /**
      * ptypeval = knowntype / "X-" word
      */
     protected void handleType(String ptypeval) {
@@ -761,32 +774,11 @@
         }
 
         if (mBuilder != null) {
-            StringBuilder builder = new StringBuilder();
-            ArrayList<String> list = new ArrayList<String>();
-            int length = propertyValue.length();
-            for (int i = 0; i < length; i++) {
-                char ch = propertyValue.charAt(i);
-                if (ch == '\\' && i < length - 1) {
-                    char nextCh = propertyValue.charAt(i + 1);
-                    String unescapedString = maybeUnescapeCharacter(nextCh); 
-                    if (unescapedString != null) {
-                        builder.append(unescapedString);
-                        i++;
-                    } else {
-                        builder.append(ch);
-                    }
-                } else if (ch == ';') {
-                    list.add(builder.toString());
-                    builder = new StringBuilder();
-                } else {
-                    builder.append(ch);
-                }
-            }
-            list.add(builder.toString());
-            mBuilder.propertyValues(list);
+            mBuilder.propertyValues(VCardUtils.constructListFromValue(
+                    propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
         }
     }
-    
+
     /**
      * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
      * 
@@ -819,12 +811,16 @@
     protected String maybeUnescapeText(String text) {
         return text;
     }
-    
+
     /**
      * Returns unescaped String if the character should be unescaped. Return null otherwise.
      * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
      */
     protected String maybeUnescapeCharacter(char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(char ch) {
         // Original vCard 2.1 specification does not allow transformation
         // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
         // this class allowed them, so keep it as is.
@@ -844,6 +840,9 @@
     @Override
     public boolean parse(InputStream is, String charset, VCardBuilder builder)
             throws IOException, VCardException {
+        if (charset == null) {
+            charset = VCardConfig.DEFAULT_CHARSET;
+        }
         final InputStreamReader tmpReader = new InputStreamReader(is, charset);
         if (VCardConfig.showPerformanceLog()) {
             mReader = new CustomBufferedReader(tmpReader);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..86e7625 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -46,14 +46,42 @@
     private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
 
     private String mPreviousLine;
-    
+
     private boolean mEmittedAgentWarning = false;
-    
+
+    /**
+     * True when the caller wants the parser to be strict about the input.
+     * Currently this is only for testing.
+     */
+    private final boolean mStrictParsing;
+
+    public VCardParser_V30() {
+        super();
+        mStrictParsing = false;
+    }
+
+    /**
+     * @param strictParsing when true, this object throws VCardException when the vcard is not
+     * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
+     * is not fully yet for being used with this flag and may not notice invalid line(s).
+     *
+     * @hide currently only for testing! 
+     */
+    public VCardParser_V30(boolean strictParsing) {
+        super();
+        mStrictParsing = strictParsing;
+    }
+
     @Override
-    protected String getVersion() {
+    protected int getVersion() {
+        return VCardConfig.FLAG_V30;
+    }
+
+    @Override
+    protected String getVersionString() {
         return Constants.VERSION_V30;
     }
-    
+
     @Override
     protected boolean isValidPropertyName(String propertyName) {
         if (!(sAcceptablePropsWithParam.contains(propertyName) ||
@@ -199,7 +227,16 @@
         // TODO: fix this.
         super.handleAnyParam(paramName, paramValue);
     }
-    
+
+    @Override
+    protected void handleParamWithoutName(final String paramValue) throws VCardException {
+        if (mStrictParsing) {
+            throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
+        } else {
+            super.handleParamWithoutName(paramValue);
+        }
+    }
+
     /**
      *  vCard 3.0 defines
      *  
@@ -284,6 +321,10 @@
      */ 
     @Override
     protected String maybeUnescapeText(String text) {
+        return unescapeText(text);
+    }
+
+    public static String unescapeText(String text) {
         StringBuilder builder = new StringBuilder();
         int length = text.length();
         for (int i = 0; i < length; i++) {
@@ -299,15 +340,19 @@
                 builder.append(ch);
             }
         }
-        return builder.toString();
+        return builder.toString();        
     }
     
     @Override
     protected String maybeUnescapeCharacter(char ch) {
+        return unescapeCharacter(ch);
+    }
+
+    public static String unescapeCharacter(char ch) {
         if (ch == 'n' || ch == 'N') {
             return "\n";
         } else {
             return String.valueOf(ch);
-        }
+        }        
     }
 }
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..b3bf426 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -22,9 +22,11 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -41,9 +43,9 @@
     // 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;
-    
+
     static {
         sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
         sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
@@ -59,14 +61,15 @@
         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
         sKnownPhoneTypesMap_StoI.put(Constants.ATTR_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_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_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);
+                Constants.ATTR_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TELEX, Phone.TYPE_TELEX);
+        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_TTY_TDD, Phone.TYPE_TTY_TDD);
+        sKnownPhoneTypesMap_StoI.put(Constants.ATTR_PHONE_EXTRA_TYPE_ASSISTANT,
+                Phone.TYPE_ASSISTANT);
 
         sPhoneTypesSetUnknownToContacts = new HashSet<String>();
         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
@@ -74,11 +77,11 @@
         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
         sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
     }
-    
+
     public static String getPhoneAttributeString(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. 
@@ -187,7 +190,10 @@
         }
 
         builder.withValue(StructuredPostal.POBOX, postalData.pobox);
-        // Extended address is dropped since there's no relevant entry in ContactsContract.
+        // TODO: Japanese phone seems to use this field for expressing all the address including
+        // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be
+        // better than dropping them all.
+        builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress);
         builder.withValue(StructuredPostal.STREET, postalData.street);
         builder.withValue(StructuredPostal.CITY, postalData.localty);
         builder.withValue(StructuredPostal.REGION, postalData.region);
@@ -282,7 +288,36 @@
         }
         return builder.toString();
     }
-    
+
+    public static List<String> constructListFromValue(final String value,
+            final boolean isV30) {
+        final List<String> list = new ArrayList<String>();
+        StringBuilder builder = new StringBuilder();
+        int length = value.length();
+        for (int i = 0; i < length; i++) {
+            char ch = value.charAt(i);
+            if (ch == '\\' && i < length - 1) {
+                char nextCh = value.charAt(i + 1);
+                final String unescapedString =
+                    (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+                        VCardParser_V21.unescapeCharacter(nextCh));
+                if (unescapedString != null) {
+                    builder.append(unescapedString);
+                    i++;
+                } else {
+                    builder.append(ch);
+                }
+            } else if (ch == ';') {
+                list.add(builder.toString());
+                builder = new StringBuilder();
+            } else {
+                builder.append(ch);
+            }
+        }
+        list.add(builder.toString());
+        return list;
+    }
+
     public static boolean containsOnlyPrintableAscii(String str) {
         if (TextUtils.isEmpty(str)) {
             return true;
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/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..cd71682 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -238,6 +238,7 @@
         private static final int FULL_SCREEN_KIND = 2;
         private static final int MICRO_KIND = 3;
         private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+        static final int DEFAULT_GROUP_ID = 0;
 
         /**
          * This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +247,14 @@
          *
          * @param cr ContentResolver
          * @param origId original image or video id. use -1 to cancel all requests.
+         * @param groupId the same groupId used in getThumbnail
          * @param baseUri the base URI of requested thumbnails
          */
-        static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+        static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+                long groupId) {
             Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
-                    .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+                    .appendQueryParameter("orig_id", String.valueOf(origId))
+                    .appendQueryParameter("group_id", String.valueOf(groupId)).build();
             Cursor c = null;
             try {
                 c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,9 +275,10 @@
          * @param kind could be MINI_KIND or MICRO_KIND
          * @param options this is only used for MINI_KIND when decoding the Bitmap
          * @param baseUri the base URI of requested thumbnails
+         * @param groupId the id of group to which this request belongs
          * @return Bitmap bitmap of specified thumbnail kind
          */
-        static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+        static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
                 BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
             Bitmap bitmap = null;
             String filePath = null;
@@ -297,7 +302,8 @@
             Cursor c = null;
             try {
                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
-                        .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+                        .appendQueryParameter("orig_id", String.valueOf(origId))
+                        .appendQueryParameter("group_id", String.valueOf(groupId)).build();
                 c = cr.query(blockingUri, PROJECTION, null, null, null);
                 // This happens when original image/video doesn't exist.
                 if (c == null) return null;
@@ -354,7 +360,7 @@
                     }
                     if (isVideo) {
                         bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
-                        if (kind == MICRO_KIND) {
+                        if (kind == MICRO_KIND && bitmap != null) {
                             bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
                                     ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
                                     ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
@@ -669,7 +675,8 @@
              * @param origId original image id
              */
             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
-                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+                        InternalThumbnails.DEFAULT_GROUP_ID);
             }
 
             /**
@@ -685,7 +692,39 @@
              */
             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
                     BitmapFactory.Options options) {
-                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+                return InternalThumbnails.getThumbnail(cr, origId,
+                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+                        EXTERNAL_CONTENT_URI, false);
+            }
+
+            /**
+             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+             * interrupted and return immediately. Only the original process which made the getThumbnail
+             * requests can cancel their own requests.
+             *
+             * @param cr ContentResolver
+             * @param origId original image id
+             * @param groupId the same groupId used in getThumbnail.
+             */
+            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+            }
+
+            /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param groupId the id of group to which this request belongs
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image
+             *         associated with origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+                    int kind, BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
                         EXTERNAL_CONTENT_URI, false);
             }
 
@@ -1598,7 +1637,8 @@
              * @param origId original video id
              */
             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
-                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+                        InternalThumbnails.DEFAULT_GROUP_ID);
             }
 
             /**
@@ -1607,18 +1647,50 @@
              *
              * @param cr ContentResolver used to dispatch queries to MediaProvider.
              * @param origId Original image id associated with thumbnail of interest.
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image
+             *         associated with origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+                    BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId,
+                        InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+                        EXTERNAL_CONTENT_URI, true);
+            }
+
+            /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param groupId the id of group to which this request belongs
              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
              * @param options this is only used for MINI_KIND when decoding the Bitmap
              * @return A Bitmap instance. It could be null if the original image associated with
              *         origId doesn't exist or memory is not enough.
              */
-            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
-                    BitmapFactory.Options options) {
-                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+                    int kind, BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
                         EXTERNAL_CONTENT_URI, true);
             }
 
             /**
+             * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+             * interrupted and return immediately. Only the original process which made the getThumbnail
+             * requests can cancel their own requests.
+             *
+             * @param cr ContentResolver
+             * @param origId original video id
+             * @param groupId the same groupId used in getThumbnail.
+             */
+            public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+                InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+            }
+
+            /**
              * Get the content:// style URI for the image media table on the
              * given volume.
              *
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 67b30a9..0db29a4 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/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index a6d644b..5c2b3c9 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*/
@@ -898,6 +899,7 @@
                         // all at once.
                         newSurface = true;
                         fullRedrawNeeded = true;
+                        mPreviousTransparentRegion.setEmpty();
 
                         if (mGlWanted && !mUseGL) {
                             initializeGL();
@@ -1243,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);
@@ -1355,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..d2861cb 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,60 @@
     }
 
     /**
+     * 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.
+     * @return int The size of the given file, or zero if it fails.
+     */
+    private int getFile(String uri, byte[] buffer, int offset) {
+        int size = 0;
+        try {
+            InputStream stream = mContext.getContentResolver()
+                            .openInputStream(Uri.parse(uri));
+            size = stream.available();
+            if (buffer != null && buffer.length - offset >= size) {
+                stream.read(buffer, offset, size);
+            } else {
+                size = 0;
+            }
+            stream.close();
+        } catch (java.io.FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException:" + e);
+            size = 0;
+        } catch (java.io.IOException e2) {
+            Log.e(LOGTAG, "IOException: " + e2);
+            size = 0;
+        }
+        return size;
+    }
+
+    /**
      * Start loading a resource.
      * @param loaderHandle The native ResourceLoader that is the target of the
      *                     data.
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 8d55247..f9dec7f 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.
      */
@@ -662,6 +673,12 @@
                     mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
                 }
                 break;
+
+            case OPEN_FILE_CHOOSER:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+                }
+                break;
         }
     }
 
@@ -1348,4 +1365,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/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 5995121..5145e03 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -1211,8 +1211,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/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/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 7f5b862..ae4f7c2 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Message;
 import android.view.View;
 
@@ -302,4 +303,13 @@
     public void getVisitedHistory(ValueCallback<String[]> callback) {
     }
 
+    /**
+     * Tell the client to open a file chooser.
+     * @param uploadFile A ValueCallback to set the URI of the file to upload.
+     *      onReceiveValue must be called to wake up the thread.
+     * @hide
+     */
+    public void openFileChooser(ValueCallback<Uri> uploadFile) {
+        uploadFile.onReceiveValue(null);
+    }
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 4fedec9..79d8c03 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1007,7 +1007,8 @@
      *     should never be null.
      */
     public synchronized void setGeolocationDatabasePath(String databasePath) {
-        if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+        if (databasePath != null
+                && !databasePath.equals(mGeolocationDatabasePath)) {
             mGeolocationDatabasePath = databasePath;
             postSync();
         }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 4bc1a0e..3dd3b37 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2630,6 +2630,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.
@@ -2836,6 +2846,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) {
@@ -2863,19 +2888,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()) {
@@ -2951,6 +2970,10 @@
         if (mFindIsUp && !animateScroll) {
             nativeDrawMatches(canvas);
         }
+        if (mFocusSizeChanged) {
+            mFocusSizeChanged = false;
+            didUpdateTextViewBounds(true);
+        }
     }
 
     // draw history
@@ -3304,7 +3327,7 @@
             }
         }
 
-        if (nativeCursorIsPlugin()) {
+        if (nativeFocusCandidateIsPlugin()) {
             nativeUpdatePluginReceivesEvents();
             invalidate();
         } else if (nativeCursorIsTextInput()) {
@@ -3316,7 +3339,9 @@
             // our view system's notion of focus
             rebuildWebTextView();
             // Now we need to pass the event to it
-            return mWebTextView.onKeyDown(keyCode, event);
+            if (inEditingMode()) {
+                return mWebTextView.onKeyDown(keyCode, 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.
@@ -4037,6 +4062,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;
@@ -4679,8 +4705,12 @@
     // called by JNI
     private void sendPluginState(int state) {
         WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
-        psd.mFrame = nativeCursorFramePointer();
-        psd.mNode = nativeCursorNodePointer();
+        psd.mFrame = nativeFocusCandidateFramePointer();
+        psd.mNode = nativeFocusCandidatePointer();
+        if (DebugFlags.WEB_VIEW) {
+            Log.v(LOGTAG, "sendPluginState frame=" + psd.mFrame
+                    + " node=" + psd.mNode);
+        }
         psd.mState = state;
         mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
     }
@@ -4871,9 +4901,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) {
@@ -5035,6 +5066,9 @@
                                     / mZoomOverviewWidth, false);
                         }
                     }
+                    if (draw.mFocusSizeChanged && inEditingMode()) {
+                        mFocusSizeChanged = true;
+                    }
                     break;
                 }
                 case WEBCORE_INITIALIZED_MSG_ID:
@@ -5161,6 +5195,11 @@
                         hideSoftKeyboard();
                     } else {
                         displaySoftKeyboard(false);
+                        if (DebugFlags.WEB_VIEW) {
+                            Log.v(LOGTAG, "REQUEST_KEYBOARD"
+                                    + " focusCandidateIsPlugin="
+                                    + nativeFocusCandidateIsPlugin());
+                        }
                     }
                     break;
 
@@ -5587,7 +5626,6 @@
     /* package */ native boolean nativeCursorMatchesFocus();
     private native boolean  nativeCursorIntersects(Rect visibleRect);
     private native boolean  nativeCursorIsAnchor();
-    private native boolean  nativeCursorIsPlugin();
     private native boolean  nativeCursorIsTextInput();
     private native Point    nativeCursorPosition();
     private native String   nativeCursorText();
@@ -5606,7 +5644,9 @@
     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();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 86685fb..3fe6961 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.graphics.Canvas;
 import android.graphics.DrawFilter;
 import android.graphics.Paint;
@@ -26,11 +27,13 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.provider.Browser;
+import android.provider.OpenableColumns;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 import android.view.KeyEvent;
@@ -273,6 +276,39 @@
         mCallbackProxy.onJsAlert(url, message);
     }
 
+
+    /**
+     * Called by JNI.  Open a file chooser to upload a file.
+     * @return String version of the URI plus the name of the file.
+     * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+     * into Java to get the filename.
+     */
+    private String openFileChooser() {
+        Uri uri = mCallbackProxy.openFileChooser();
+        if (uri == null) return "";
+        // Find out the name, and append it to the URI.
+        // Webkit will treat the name as the filename, and
+        // the URI as the path.  The URI will be used
+        // in BrowserFrame to get the actual data.
+        Cursor cursor = mContext.getContentResolver().query(
+                uri,
+                new String[] { OpenableColumns.DISPLAY_NAME },
+                null,
+                null,
+                null);
+        String name = "";
+        if (cursor != null) {
+            try {
+                if (cursor.moveToNext()) {
+                    name = cursor.getString(0);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+        return uri.toString() + "/" + name;
+    }
+
     /**
      * Notify the browser that the origin has exceeded it's database quota.
      * @param url The URL that caused the overflow.
@@ -422,6 +458,8 @@
      */
     private native boolean nativeRecordContent(Region invalRegion, Point wh);
 
+    private native boolean nativeFocusBoundsChanged();
+
     /**
      * Splits slow parts of the picture set. Called from the webkit
      * thread after nativeDrawContent returns true.
@@ -1593,6 +1631,7 @@
         int mMinPrefWidth;
         RestoreState mRestoreState; // only non-null if it is for the first
                                     // picture set after the first layout
+        boolean mFocusSizeChanged;
     }
 
     private void webkitDraw() {
@@ -1607,6 +1646,7 @@
         if (mWebView != null) {
             // Send the native view size that was used during the most recent
             // layout.
+            draw.mFocusSizeChanged = nativeFocusBoundsChanged();
             draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
             if (mSettings.getUseWideViewPort()) {
                 draw.mMinPrefWidth = Math.max(
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 165794a..5991ad4 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3560,6 +3560,7 @@
             // into the scrap heap
             int viewType = lp.viewType;
             if (!shouldRecycleViewType(viewType)) {
+                removeDetachedView(scrap, false);
                 return;
             }
 
diff --git a/core/java/android/widget/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 bf3d26e..3ed995bc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5198,7 +5198,7 @@
             mDesiredHeightAtMeasure = desired;
 
             if (heightMode == MeasureSpec.AT_MOST) {
-                height = Math.min(desired, height);
+                height = Math.min(desired, heightSize);
             }
         }
 
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 38f3fda..46000c9 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -20,6 +20,7 @@
 #include <utils/misc.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
+#include <utils/String16.h>
 
 #include "wifi.h"
 
@@ -92,7 +93,8 @@
     if (doCommand(cmd, reply, sizeof(reply)) != 0) {
         return env->NewStringUTF(NULL);
     } else {
-        return env->NewStringUTF(reply);
+        String16 str((char *)reply);
+        return env->NewString((const jchar *)str.string(), str.size());
     }
 }
 
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
index 42fc0c5..fe220e4 100644
--- a/core/res/assets/images/combobox-disabled.png
+++ b/core/res/assets/images/combobox-disabled.png
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
index 838dc65..abcdf72 100644
--- a/core/res/assets/images/combobox-noHighlight.png
+++ b/core/res/assets/images/combobox-noHighlight.png
Binary files differ
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index 2aaaa15..45d9c5e 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -30,14 +30,8 @@
   </head>
   <body>
     <div id="bg">
-      <table height="100%" width="100%" border="0" cellpadding="0"
-        cellspacing="0">
-        <tr>
-          <td valign="middle">
-            <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg" width="100%"/>
-          </td>
-        </tr>
-      </table>
+      <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg"
+      style="width:100%; height:100%"/>
     </div>
     <div id="main">
       <table height="100%" width="100%">
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 88f16a9..e2059d8 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -111,7 +111,8 @@
     <string name="httpErrorFile" msgid="8250549644091165175">"No se ha podido acceder al archivo."</string>
     <string name="httpErrorFileNotFound" msgid="5588380756326017105">"No se ha encontrado el archivo solicitado."</string>
     <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Se están procesando demasiadas solicitudes. Vuelve a intentarlo más tarde."</string>
-    <string name="notification_title" msgid="1259940370369187045">"Error al iniciar la sesión de <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+    <!-- no translation found for notification_title (1259940370369187045) -->
+    <skip />
     <string name="contentServiceSync" msgid="8353523060269335667">"Sincronización"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sincronización"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Demasiadas eliminaciones de <xliff:g id="CONTENT_TYPE">%s</xliff:g>"</string>
@@ -147,8 +148,8 @@
     <string name="permgroupdesc_location" msgid="2430258821648348660">"Controla tu ubicación física"</string>
     <string name="permgrouplab_network" msgid="5808983377727109831">"Comunicación de red"</string>
     <string name="permgroupdesc_network" msgid="5035763698958415998">"Admite aplicaciones que acceden a varias funciones de red."</string>
-    <string name="permgrouplab_accounts" msgid="3359646291125325519">"Tus cuentas"</string>
-    <string name="permgroupdesc_accounts" msgid="4948732641827091312">"Acceder a las cuentas disponibles."</string>
+    <string name="permgrouplab_accounts" msgid="7140261692496314430">"Tus cuentas de Google"</string>
+    <string name="permgroupdesc_accounts" msgid="6735915929704895193">"Acceder a las cuentas disponibles de Google."</string>
     <string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controles de hardware"</string>
     <string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Acceso directo al hardware en el teléfono."</string>
     <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Llamadas telefónicas"</string>
@@ -432,58 +433,6 @@
     <item msgid="2506857312718630823">"ICQ"</item>
     <item msgid="1648797903785279353">"Jabber"</item>
   </string-array>
-    <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string>
-    <string name="phoneTypeHome" msgid="2570923463033985887">"Página principal"</string>
-    <string name="phoneTypeMobile" msgid="6501463557754751037">"Celular"</string>
-    <string name="phoneTypeWork" msgid="8863939667059911633">"Trabajo"</string>
-    <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax laboral"</string>
-    <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax personal"</string>
-    <string name="phoneTypePager" msgid="7582359955394921732">"Localizador"</string>
-    <string name="phoneTypeOther" msgid="1544425847868765990">"Otro"</string>
-    <string name="phoneTypeCallback" msgid="2712175203065678206">"Devolución de llamada"</string>
-    <string name="phoneTypeCar" msgid="8738360689616716982">"Automóvil"</string>
-    <string name="phoneTypeCompanyMain" msgid="540434356461478916">"Empresa principal"</string>
-    <string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string>
-    <string name="phoneTypeMain" msgid="6766137010628326916">"Principal"</string>
-    <string name="phoneTypeOtherFax" msgid="8587657145072446565">"Otro fax"</string>
-    <string name="phoneTypeRadio" msgid="4093738079908667513">"Radio"</string>
-    <string name="phoneTypeTelex" msgid="3367879952476250512">"Télex"</string>
-    <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
-    <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Celular del trabajo"</string>
-    <string name="phoneTypeWorkPager" msgid="649938731231157056">"Localizador del trabajo"</string>
-    <string name="phoneTypeAssistant" msgid="5596772636128562884">"Asistente"</string>
-    <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
-    <string name="eventTypeBirthday" msgid="2813379844211390740">"Cumpleaños"</string>
-    <string name="eventTypeAnniversary" msgid="3876779744518284000">"Aniversario"</string>
-    <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string>
-    <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string>
-    <string name="emailTypeHome" msgid="449227236140433919">"Página principal"</string>
-    <string name="emailTypeWork" msgid="3548058059601149973">"Trabajo"</string>
-    <string name="emailTypeOther" msgid="2923008695272639549">"Otro"</string>
-    <string name="emailTypeMobile" msgid="119919005321166205">"Celular"</string>
-    <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizado"</string>
-    <string name="postalTypeHome" msgid="8165756977184483097">"Página principal"</string>
-    <string name="postalTypeWork" msgid="5268172772387694495">"Trabajo"</string>
-    <string name="postalTypeOther" msgid="2726111966623584341">"Otro"</string>
-    <string name="imTypeCustom" msgid="2074028755527826046">"Personalizado"</string>
-    <string name="imTypeHome" msgid="6241181032954263892">"Página principal"</string>
-    <string name="imTypeWork" msgid="1371489290242433090">"Trabajo"</string>
-    <string name="imTypeOther" msgid="5377007495735915478">"Otro"</string>
-    <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizado"</string>
-    <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string>
-    <string name="imProtocolMsn" msgid="144556545420769442">"Windows Live"</string>
-    <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string>
-    <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string>
-    <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string>
-    <string name="imProtocolGoogleTalk" msgid="3808393979157698766">"Google Talk"</string>
-    <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string>
-    <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string>
-    <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string>
-    <string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string>
-    <string name="orgTypeOther" msgid="3951781131570124082">"Otro"</string>
-    <string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
-    <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
-    <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ingresar el código de PIN"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"¡Código de PIN incorrecto!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, presiona el menú y luego 0."</string>
@@ -498,7 +447,6 @@
     <string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lo sentimos, vuelve a intentarlo"</string>
     <string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Cargada."</string>
-    <string name="lockscreen_battery_short" msgid="3617549178603354656">"Segmento <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecta tu cargador."</string>
     <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"No hay tarjeta SIM."</string>
     <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"No hay tarjeta SIM en el teléfono."</string>
@@ -530,7 +478,7 @@
     <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string>
     <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string>
     <string name="battery_low_subtitle" msgid="7388781709819722764">"Hay poca batería:"</string>
-    <string name="battery_low_percent_format" msgid="696154104579022959">"Restan <xliff:g id="NUMBER">%d%%</xliff:g> o menos."</string>
+    <string name="battery_low_percent_format" msgid="6564958083485073855">"menos de <xliff:g id="NUMBER">%d%%</xliff:g> restante."</string>
     <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
     <string name="factorytest_failed" msgid="5410270329114212041">"Error en la prueba de fábrica"</string>
     <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST se admite solamente en paquetes instalados en /system/app."</string>
@@ -540,7 +488,6 @@
     <string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string>
     <string name="js_dialog_before_unload" msgid="1901675448179653089">"¿Deseas salir de esta página?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Selecciona Aceptar para continuar o Cancelar para permanecer en la página actual."</string>
     <string name="save_password_label" msgid="6860261758665825069">"Confirmar"</string>
-    <string name="double_tap_toast" msgid="1068216937244567247">"Sugerencia: presiona dos veces para acercar y alejar"</string>
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"leer historial y marcadores del navegador"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite a la aplicación leer todas las URL que ha visitado el navegador y todos los marcadores del navegador."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir historial y marcadores del navegador"</string>
@@ -745,8 +692,10 @@
     <string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de tu tarjeta."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración de USB conectada"</string>
-    <string name="adb_active_notification_message" msgid="8470296818270110396">"Seleccionar para desactivar la depuración de USB."</string>
-    <string name="select_input_method" msgid="6865512749462072765">"Seleccionar método de entrada"</string>
+    <!-- no translation found for adb_active_notification_message (8470296818270110396) -->
+    <skip />
+    <!-- no translation found for select_input_method (6865512749462072765) -->
+    <skip />
     <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>"candidatos"</u></string>
@@ -782,14 +731,11 @@
     <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>
-    <string name="permission_request_notification_with_subtitle" msgid="4325409589686688000">"Permiso solicitado"\n"para la cuenta <xliff:g id="ACCOUNT">%s</xliff:g>"</string>
+    <!-- no translation found for permission_request_notification_with_subtitle (4325409589686688000) -->
+    <skip />
     <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string>
     <string name="sync_binding_label" msgid="3687969138375092423">"Sincronización"</string>
     <string name="accessibility_binding_label" msgid="4148120742096474641">"Accesibilidad"</string>
     <string name="wallpaper_binding_label" msgid="1240087844304687662">"Papel tapiz"</string>
     <string name="chooser_wallpaper" msgid="7873476199295190279">"Cambiar fondo de pantalla"</string>
-    <string name="pptp_vpn_description" msgid="2688045385181439401">"Protocolo de túnel punto a punto"</string>
-    <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>
 </resources>
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index 10e0197..e551d17 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -84,9 +84,9 @@
     virtual status_t observe_node(
             node_id node, const sp<IOMXObserver> &observer) = 0;
 
-    virtual void fill_buffer(node_id node, buffer_id buffer) = 0;
+    virtual status_t fill_buffer(node_id node, buffer_id buffer) = 0;
 
-    virtual void empty_buffer(
+    virtual status_t empty_buffer(
             node_id node,
             buffer_id buffer,
             OMX_U32 range_offset, OMX_U32 range_length,
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/SoftwareRenderer.h b/include/media/stagefright/SoftwareRenderer.h
index 1545493..9eed089 100644
--- a/include/media/stagefright/SoftwareRenderer.h
+++ b/include/media/stagefright/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/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..8acb744 100644
--- a/media/java/android/media/ThumbnailUtil.java
+++ b/media/java/android/media/ThumbnailUtil.java
@@ -33,6 +33,7 @@
 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;
@@ -305,8 +306,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);
+        byte[] thumbData = null;
         Bitmap bitmap = null;
+        MediaFileType fileType = MediaFile.getFileType(filePath);
+        if (fileType != null && fileType.fileType == MediaFile.FILE_TYPE_JPEG) {
+            thumbData = createThumbnailFromEXIF(filePath, targetSize);
+        }
 
         if (thumbData != null) {
             BitmapFactory.Options options = new BitmapFactory.Options();
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
index 0cec7bb..e74f1a9 100644
--- a/media/libmedia/IOMX.cpp
+++ b/media/libmedia/IOMX.cpp
@@ -289,15 +289,17 @@
         return reply.readInt32();
     }
 
-    virtual void fill_buffer(node_id node, buffer_id buffer) {
+    virtual status_t fill_buffer(node_id node, buffer_id buffer) {
         Parcel data, reply;
         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();
     }
 
-    virtual void empty_buffer(
+    virtual status_t empty_buffer(
             node_id node,
             buffer_id buffer,
             OMX_U32 range_offset, OMX_U32 range_length,
@@ -310,7 +312,9 @@
         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();
     }
 
     virtual status_t get_extension_index(
@@ -601,7 +605,7 @@
 
             node_id node = (void*)data.readIntPtr();
             buffer_id buffer = (void*)data.readIntPtr();
-            fill_buffer(node, buffer);
+            reply->writeInt32(fill_buffer(node, buffer));
 
             return NO_ERROR;
         }
@@ -617,9 +621,10 @@
             OMX_U32 flags = data.readInt32();
             OMX_TICKS timestamp = data.readInt64();
 
-            empty_buffer(
-                    node, buffer, range_offset, range_length,
-                    flags, timestamp);
+            reply->writeInt32(
+                    empty_buffer(
+                        node, buffer, range_offset, range_length,
+                        flags, timestamp));
 
             return NO_ERROR;
         }
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index f21eb73..ef41225 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -18,8 +18,9 @@
 
 ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
 
-LOCAL_SRC_FILES +=              \
-    StagefrightPlayer.cpp
+LOCAL_SRC_FILES +=                      \
+    StagefrightPlayer.cpp               \
+    StagefrightMetadataRetriever.cpp
 
 LOCAL_CFLAGS += -DBUILD_WITH_FULL_STAGEFRIGHT=1
 
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index ddd4e24..9a0d692 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -37,6 +37,7 @@
 #include "VorbisMetadataRetriever.h"
 #include "MidiMetadataRetriever.h"
 #include "MetadataRetrieverClient.h"
+#include "StagefrightMetadataRetriever.h"
 
 namespace android {
 
@@ -105,9 +106,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;
     }
diff --git a/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
new file mode 100644
index 0000000..64e9f2f
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightMetadataRetriever.cpp
@@ -0,0 +1,194 @@
+/*
+**
+** 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);
+
+    sp<DataSource> source;
+    if (!strncasecmp("file://", uri, 7)) {
+        sp<MmapSource> mmapSource = new MmapSource(uri + 7);
+        if (mmapSource->InitCheck() != OK) {
+            return ERROR_IO;
+        }
+        source = mmapSource;
+    } else if (!strncasecmp("http://", uri, 7)) {
+        source = new HTTPDataSource(uri);
+        source = new CachingDataSource(source, 64 * 1024, 10);
+    } else {
+        // Assume it's a filename.
+        sp<MmapSource> mmapSource = new MmapSource(uri);
+        if (mmapSource->InitCheck() != OK) {
+            return ERROR_IO;
+        }
+        source = mmapSource;
+    }
+
+    mExtractor = MediaExtractor::Create(source);
+
+    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) {
+        LOGE("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) {
+        LOGE("no video track found.");
+        return NULL;
+    }
+
+    sp<MediaSource> source = mExtractor->getTrack(i);
+
+    if (source.get() == NULL) {
+        LOGE("unable to instantiate video track.");
+        return NULL;
+    }
+
+    sp<MetaData> meta = source->getFormat();
+
+    sp<MediaSource> decoder =
+        OMXCodec::Create(
+                mClient.interface(), meta, false, source);
+
+    if (decoder.get() == NULL) {
+        LOGE("unable to instantiate video decoder.");
+
+        return NULL;
+    }
+
+    decoder->start();
+
+    MediaBuffer *buffer;
+    status_t err = decoder->read(&buffer);
+
+    if (err != OK) {
+        CHECK_EQ(buffer, NULL);
+
+        LOGE("decoding frame failed.");
+        decoder->stop();
+
+        return NULL;
+    }
+
+    LOGI("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/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp
index 47d95f9..8277c15 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;
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index c4c6149..7586ada 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -39,6 +39,8 @@
 
 namespace android {
 
+static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
+
 struct CodecInfo {
     const char *mime;
     const char *codec;
@@ -202,7 +204,7 @@
 
         status_t err = omx->allocate_node(componentName, &node);
         if (err == OK) {
-            LOGI("Successfully allocated OMX node '%s'", componentName);
+            LOGV("Successfully allocated OMX node '%s'", componentName);
             break;
         }
     }
@@ -217,11 +219,6 @@
     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;
@@ -324,9 +321,10 @@
             size -= length;
         }
 
-        LOGI("AVC profile = %d (%s), level = %d",
+        LOGV("AVC profile = %d (%s), level = %d",
              (int)profile, AVCProfileToString(profile), (int)level / 10);
 
+#if 0
         if (!strcmp(componentName, "OMX.TI.Video.Decoder")
             && (profile != kAVCProfileBaseline || level > 39)) {
             // This stream exceeds the decoder's capabilities.
@@ -334,6 +332,7 @@
             LOGE("Profile and/or level exceed the decoder's capabilities.");
             return NULL;
         }
+#endif
     }
 
     if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) {
@@ -443,7 +442,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
@@ -476,7 +475,7 @@
         return UNKNOWN_ERROR;
     }
 
-    CODEC_LOGI("found a match.");
+    CODEC_LOGV("found a match.");
     status_t err = mOMX->set_parameter(
             mNode, OMX_IndexParamVideoPortFormat,
             &format, sizeof(format));
@@ -486,7 +485,7 @@
 
 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)) {
@@ -546,7 +545,7 @@
     CHECK_EQ(err, OK);
 
     def.nBufferSize = (width * height * 2); // (width * height * 3) / 2;
-    CODEC_LOGI("Setting nBufferSize = %ld", def.nBufferSize);
+    CODEC_LOGV("Setting nBufferSize = %ld", def.nBufferSize);
 
     CHECK_EQ(def.eDomain, OMX_PortDomainVideo);
 
@@ -562,7 +561,7 @@
 
 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)) {
@@ -1415,10 +1414,11 @@
             memcpy(info->mMem->pointer(), specific->mData, specific->mSize);
         }
 
-        mOMX->empty_buffer(
+        status_t err = mOMX->empty_buffer(
                 mNode, info->mBuffer, 0, size,
                 OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
                 0);
+        CHECK_EQ(err, OK);
 
         info->mOwnedByComponent = true;
 
@@ -1471,16 +1471,21 @@
         }
     }
 
-    mOMX->empty_buffer(
-            mNode, info->mBuffer, 0, srcLength,
-            flags, timestamp);
-
-    info->mOwnedByComponent = true;
-
     if (srcBuffer != NULL) {
         srcBuffer->release();
         srcBuffer = NULL;
     }
+
+    err = mOMX->empty_buffer(
+            mNode, info->mBuffer, 0, srcLength,
+            flags, timestamp);
+
+    if (err != OK) {
+        setState(ERROR);
+        return;
+    }
+
+    info->mOwnedByComponent = true;
 }
 
 void OMXCodec::fillOutputBuffer(BufferInfo *info) {
@@ -1493,7 +1498,8 @@
     }
 
     CODEC_LOGV("Calling fill_buffer on buffer %p", info->mBuffer);
-    mOMX->fill_buffer(mNode, info->mBuffer);
+    status_t err = mOMX->fill_buffer(mNode, info->mBuffer);
+    CHECK_EQ(err, OK);
 
     info->mOwnedByComponent = true;
 }
@@ -2017,8 +2023,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) {
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index 4cadccd..468221e 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -10,6 +10,7 @@
 LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
 
 LOCAL_SRC_FILES:=                 \
+        ColorConverter.cpp        \
 	OMX.cpp                   \
         QComHardwareRenderer.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 8b83dd6..d7f355a 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -283,7 +283,7 @@
             &handle, const_cast<char *>(name), meta, &kCallbacks);
 
     if (err != OMX_ErrorNone) {
-        LOGE("FAILED to allocate omx component '%s'", name);
+        LOGV("FAILED to allocate omx component '%s'", name);
 
         delete meta;
         meta = NULL;
@@ -529,7 +529,7 @@
     return OK;
 }
 
-void OMX::fill_buffer(node_id node, buffer_id buffer) {
+status_t OMX::fill_buffer(node_id node, buffer_id buffer) {
     OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer;
     header->nFilledLen = 0;
     header->nOffset = 0;
@@ -539,10 +539,11 @@
 
     OMX_ERRORTYPE err =
         OMX_FillThisBuffer(node_meta->handle(), header);
-    CHECK_EQ(err, OMX_ErrorNone);
+
+    return (err == OMX_ErrorNone) ? OK : UNKNOWN_ERROR;
 }
 
-void OMX::empty_buffer(
+status_t OMX::empty_buffer(
         node_id node,
         buffer_id buffer,
         OMX_U32 range_offset, OMX_U32 range_length,
@@ -561,7 +562,8 @@
 
     OMX_ERRORTYPE err =
         OMX_EmptyThisBuffer(node_meta->handle(), header);
-    CHECK_EQ(err, OMX_ErrorNone);
+
+    return (err == OMX_ErrorNone) ? OK : UNKNOWN_ERROR;
 }
 
 status_t OMX::get_extension_index(
diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/omx/OMX.h
index 6325f79..4c14dd9 100644
--- a/media/libstagefright/omx/OMX.h
+++ b/media/libstagefright/omx/OMX.h
@@ -70,9 +70,9 @@
     virtual status_t observe_node(
             node_id node, const sp<IOMXObserver> &observer);
 
-    virtual void fill_buffer(node_id node, buffer_id buffer);
+    virtual status_t fill_buffer(node_id node, buffer_id buffer);
 
-    virtual void empty_buffer(
+    virtual status_t empty_buffer(
             node_id node,
             buffer_id buffer,
             OMX_U32 range_offset, OMX_U32 range_length,
diff --git a/media/libstagefright/omx/QComHardwareRenderer.cpp b/media/libstagefright/omx/QComHardwareRenderer.cpp
index e9930be..7dc368f 100644
--- a/media/libstagefright/omx/QComHardwareRenderer.cpp
+++ b/media/libstagefright/omx/QComHardwareRenderer.cpp
@@ -83,6 +83,11 @@
     }
 
     mISurface->postBuffer(offset);
+
+    // Since we cannot tell how long it'll take until surface flinger
+    // has displayed the data onscreen, we'll just have to guess...
+    // We must not return the buffer to the decoder before it's been displayed.
+    usleep(25000);
 }
 
 bool QComHardwareRenderer::getOffset(void *platformPrivate, size_t *offset) {
diff --git a/media/libstagefright/omx/SoftwareRenderer.cpp b/media/libstagefright/omx/SoftwareRenderer.cpp
index 4ed6869..882c401 100644
--- a/media/libstagefright/omx/SoftwareRenderer.cpp
+++ b/media/libstagefright/omx/SoftwareRenderer.cpp
@@ -24,14 +24,13 @@
 
 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 +38,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 +57,19 @@
 
 SoftwareRenderer::~SoftwareRenderer() {
     mISurface->unregisterBuffers();
-
-    delete[] mClip;
-    mClip = NULL;
 }
 
 void SoftwareRenderer::render(
         const void *data, size_t size, void *platformPrivate) {
-    static const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00;
-
-    switch (mColorFormat) {
-        case OMX_COLOR_FormatYUV420Planar:
-            return renderYUV420Planar(data, size);
-
-        case OMX_COLOR_FormatCbYCrY:
-            return renderCbYCrY(data, size);
-
-        case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
-            return renderQCOMYUV420SemiPlanar(data, size);
-
-        default:
-        {
-            LOGW("Cannot render color format %ld", mColorFormat);
-            break;
-        }
-    }
-}
-
-void SoftwareRenderer::renderYUV420Planar(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 3) / 2);
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
-    uint8_t *kAdjustedClip = initClip();
-
     size_t offset = mIndex * mFrameSize;
-
     void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
 
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src_y = (const uint8_t *)data;
-
-    const uint8_t *src_u =
-        (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
-#if !QCOM_YUV
-    const uint8_t *src_v =
-        (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2);
-#endif
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
-            // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
-            // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
-
-            // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
-            // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
-            // R = .................. + 409/256 * (V - 128)
-
-            // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
-            // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
-            // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
-
-            // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
-            // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
-            // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
-
-            // clip range -278 .. 535
-
-            signed y1 = (signed)src_y[x] - 16;
-            signed y2 = (signed)src_y[x + 1] - 16;
-
-#if QCOM_YUV
-            signed u = (signed)src_u[x & ~1] - 128;
-            signed v = (signed)src_u[(x & ~1) + 1] - 128;
-#else
-            signed u = (signed)src_u[x / 2] - 128;
-            signed v = (signed)src_v[x / 2] - 128;
-#endif
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[r1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[b1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[r2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[b2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src_y += mDecodedWidth;
-
-        if (y & 1) {
-#if QCOM_YUV
-            src_u += mDecodedWidth;
-#else
-            src_u += mDecodedWidth / 2;
-            src_v += mDecodedWidth / 2;
-#endif
-        }
-
-        dst_ptr += mDecodedWidth / 2;
-    }
+    mConverter.convert(
+            mDecodedWidth, mDecodedHeight,
+            data, 0, dst, 2 * mDecodedWidth);
 
     mISurface->postBuffer(offset);
     mIndex = 1 - mIndex;
 }
 
-void SoftwareRenderer::renderCbYCrY(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 2)) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 2));
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 2));
-
-    uint8_t *kAdjustedClip = initClip();
-
-    size_t offset = mIndex * mFrameSize;
-    void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src = (const uint8_t *)data;
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            signed y1 = (signed)src[2 * x + 1] - 16;
-            signed y2 = (signed)src[2 * x + 3] - 16;
-            signed u = (signed)src[2 * x] - 128;
-            signed v = (signed)src[2 * x + 2] - 128;
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[r1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[b1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[r2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[b2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src += mDecodedWidth * 2;
-        dst_ptr += mDecodedWidth / 2;
-    }
-
-    mISurface->postBuffer(offset);
-    mIndex = 1 - mIndex;
-}
-
-void SoftwareRenderer::renderQCOMYUV420SemiPlanar(
-        const void *data, size_t size) {
-    if (size != (mDecodedHeight * mDecodedWidth * 3) / 2) {
-        LOGE("size is %d, expected %d",
-                size, (mDecodedHeight * mDecodedWidth * 3) / 2);
-    }
-    CHECK(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
-
-    uint8_t *kAdjustedClip = initClip();
-
-    size_t offset = mIndex * mFrameSize;
-
-    void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
-
-    uint32_t *dst_ptr = (uint32_t *)dst;
-
-    const uint8_t *src_y = (const uint8_t *)data;
-
-    const uint8_t *src_u =
-        (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
-
-    for (size_t y = 0; y < mDecodedHeight; ++y) {
-        for (size_t x = 0; x < mDecodedWidth; x += 2) {
-            signed y1 = (signed)src_y[x] - 16;
-            signed y2 = (signed)src_y[x + 1] - 16;
-
-            signed u = (signed)src_u[x & ~1] - 128;
-            signed v = (signed)src_u[(x & ~1) + 1] - 128;
-
-            signed u_b = u * 517;
-            signed u_g = -u * 100;
-            signed v_g = -v * 208;
-            signed v_r = v * 409;
-
-            signed tmp1 = y1 * 298;
-            signed b1 = (tmp1 + u_b) / 256;
-            signed g1 = (tmp1 + v_g + u_g) / 256;
-            signed r1 = (tmp1 + v_r) / 256;
-
-            signed tmp2 = y2 * 298;
-            signed b2 = (tmp2 + u_b) / 256;
-            signed g2 = (tmp2 + v_g + u_g) / 256;
-            signed r2 = (tmp2 + v_r) / 256;
-
-            uint32_t rgb1 =
-                ((kAdjustedClip[b1] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[r1] >> 3);
-
-            uint32_t rgb2 =
-                ((kAdjustedClip[b2] >> 3) << 11)
-                | ((kAdjustedClip[g2] >> 2) << 5)
-                | (kAdjustedClip[r2] >> 3);
-
-            dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
-        }
-
-        src_y += mDecodedWidth;
-
-        if (y & 1) {
-            src_u += mDecodedWidth;
-        }
-
-        dst_ptr += mDecodedWidth / 2;
-    }
-
-    mISurface->postBuffer(offset);
-    mIndex = 1 - mIndex;
-}
-
-uint8_t *SoftwareRenderer::initClip() {
-    static const signed kClipMin = -278;
-    static const signed kClipMax = 535;
-
-    if (mClip == NULL) {
-        mClip = new uint8_t[kClipMax - kClipMin + 1];
-
-        for (signed i = kClipMin; i <= kClipMax; ++i) {
-            mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
-        }
-    }
-
-    return &mClip[-kClipMin];
-}
-
 }  // namespace android
diff --git a/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/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/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/src/com/android/unit_tests/vcard/MockCursor.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/MockCursor.java
new file mode 100644
index 0000000..2523b0d
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/MockCursor.java
@@ -0,0 +1,254 @@
+/*
+ * 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.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;
+
+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/src/com/android/unit_tests/vcard/PropertyNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
index 0ee74df..d93a41b 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNode.java
@@ -16,19 +16,21 @@
 package com.android.unit_tests.vcard;
 
 import android.content.ContentValues;
-
-import org.apache.commons.codec.binary.Base64;
+import android.pim.vcard.ContactStruct;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
 
 /**
- * @hide old class just for test
+ * Previously used in main vCard handling code but now exists only for testing.
+ *
+ * Especially useful for testing parser code (VCardParser), since all properties can be
+ * checked via this class unlike {@link ContactStruct}, which only emits the result of
+ * interpretation of the content of each vCard. We cannot know whether vCard parser or
+ * ContactStruct is wrong withouth this class.
  */
 public class PropertyNode {
     public String propName;
@@ -101,6 +103,15 @@
     }
     
     @Override
+    public int hashCode() {
+        // vCard may contain more than one same line in one entry, while HashSet or any other
+        // library which utilize hashCode() does not honor that, so intentionally throw an
+        // Exception.
+        throw new UnsupportedOperationException(
+                "PropertyNode does not provide hashCode() implementation intentionally.");
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof PropertyNode)) {
             return false;
@@ -159,164 +170,4 @@
         builder.append(propValue);
         return builder.toString();
     }
-    
-    /**
-     * Encode this object into a string which can be decoded. 
-     */
-    public String encode() {
-        // PropertyNode#toString() is for reading, not for parsing in the future.
-        // We construct appropriate String here.
-        StringBuilder builder = new StringBuilder();
-        if (propName.length() > 0) {
-            builder.append("propName:[");
-            builder.append(propName);
-            builder.append("],");
-        }
-        int size = propGroupSet.size();
-        if (size > 0) {
-            Set<String> set = propGroupSet;
-            builder.append("propGroup:[");
-            int i = 0;
-            for (String group : set) {
-                // We do not need to double quote groups.
-                // group        = 1*(ALPHA / DIGIT / "-")
-                builder.append(group);
-                if (i < size - 1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-            builder.append("],");
-        }
-
-        if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) {
-            ContentValues values = paramMap;
-            builder.append("paramMap:[");
-            size = paramMap.size(); 
-            int i = 0;
-            for (Entry<String, Object> entry : values.valueSet()) {
-                // Assuming param-key does not contain NON-ASCII nor symbols.
-                //
-                // According to vCard 3.0:
-                // param-name   = iana-token / x-name
-                builder.append(entry.getKey());
-
-                // param-value may contain any value including NON-ASCIIs.
-                // We use the following replacing rule.
-                // \ -> \\
-                // , -> \,
-                // In String#replaceAll(), "\\\\" means a single backslash.
-                builder.append("=");
-                builder.append(entry.getValue().toString()
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size -1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-
-            Set<String> set = paramMap_TYPE;
-            size = paramMap_TYPE.size();
-            if (i > 0 && size > 0) {
-                builder.append(",");
-            }
-            i = 0;
-            for (String type : set) {
-                builder.append("TYPE=");
-                builder.append(type
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size - 1) {
-                    builder.append(",");
-                }
-                i++;
-            }
-            builder.append("],");
-        }
-
-        size = propValue_vector.size();
-        if (size > 0) {
-            builder.append("propValue:[");
-            List<String> list = propValue_vector;
-            for (int i = 0; i < size; i++) {
-                builder.append(list.get(i)
-                        .replaceAll("\\\\", "\\\\\\\\")
-                        .replaceAll(",", "\\\\,"));
-                if (i < size -1) {
-                    builder.append(",");
-                }
-            }
-            builder.append("],");
-        }
-
-        return builder.toString();
-    }
-
-    public static PropertyNode decode(String encodedString) {
-        PropertyNode propertyNode = new PropertyNode();
-        String trimed = encodedString.trim();
-        if (trimed.length() == 0) {
-            return propertyNode;
-        }
-        String[] elems = trimed.split("],");
-        
-        for (String elem : elems) {
-            int index = elem.indexOf('[');
-            String name = elem.substring(0, index - 1);
-            Pattern pattern = Pattern.compile("(?<!\\\\),");
-            String[] values = pattern.split(elem.substring(index + 1), -1);
-            if (name.equals("propName")) {
-                propertyNode.propName = values[0];
-            } else if (name.equals("propGroupSet")) {
-                for (String value : values) {
-                    propertyNode.propGroupSet.add(value);
-                }
-            } else if (name.equals("paramMap")) {
-                ContentValues paramMap = propertyNode.paramMap;
-                Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE;
-                for (String value : values) {
-                    String[] tmp = value.split("=", 2);
-                    String mapKey = tmp[0];
-                    // \, -> ,
-                    // \\ -> \
-                    // In String#replaceAll(), "\\\\" means a single backslash.
-                    String mapValue =
-                        tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
-                    if (mapKey.equalsIgnoreCase("TYPE")) {
-                        paramMap_TYPE.add(mapValue);
-                    } else {
-                        paramMap.put(mapKey, mapValue);
-                    }
-                }
-            } else if (name.equals("propValue")) {
-                StringBuilder builder = new StringBuilder();
-                List<String> list = propertyNode.propValue_vector;
-                int length = values.length;
-                for (int i = 0; i < length; i++) {
-                    String normValue = values[i]
-                                              .replaceAll("\\\\,", ",")
-                                              .replaceAll("\\\\\\\\", "\\\\");
-                    list.add(normValue);
-                    builder.append(normValue);
-                    if (i < length - 1) {
-                        builder.append(";");
-                    }
-                }
-                propertyNode.propValue = builder.toString();
-            }
-        }
-        
-        // At this time, QUOTED-PRINTABLE is already decoded to Java String.
-        // We just need to decode BASE64 String to binary.
-        String encoding = propertyNode.paramMap.getAsString("ENCODING");
-        if (encoding != null &&
-                (encoding.equalsIgnoreCase("BASE64") ||
-                        encoding.equalsIgnoreCase("B"))) {
-            propertyNode.propValue_bytes =
-                Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes());
-        }
-        
-        return propertyNode;
-    }
 }
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
new file mode 100644
index 0000000..3acd6c1
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/PropertyNodesVerifier.java
@@ -0,0 +1,235 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * 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.
+ */
+public class PropertyNodesVerifier {
+    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 PropertyNodesVerifier(TestCase testCase) {
+        mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
+        mUnorderedNodeList = new ArrayList<PropertyNode>();
+        mTestCase = testCase;
+    }
+
+    // WithOrder
+
+    public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue) {
+        return addNodeWithOrder(propName, propValue, null, null, null, null, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList) {
+        return addNodeWithOrder(propName, propValue, propValueList, null, null, null, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue,
+            TypeSet paramMap_TYPE) {
+        return addNodeWithOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        return addNodeWithOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithOrder(String propName, String propValue,
+            List<String> propValueList, byte[] propValue_bytes,
+            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+        PropertyNode propertyNode = new PropertyNode(propName,
+                propValue, propValueList, propValue_bytes,
+                paramMap, paramMap_TYPE, propGroupSet);
+        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+        if (expectedNodeList == null) {
+            expectedNodeList = new ArrayList<PropertyNode>();
+            mOrderedNodeMap.put(propName, expectedNodeList);
+        }
+        expectedNodeList.add(propertyNode);
+        return this;
+    }
+
+    // WithoutOrder
+
+    public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue) {
+        return addNodeWithoutOrder(propName, propValue, null, null, null, null, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList) {
+        return addNodeWithoutOrder(propName, propValue, propValueList, null, null, null, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue,
+            TypeSet paramMap_TYPE) {
+        return addNodeWithoutOrder(propName, propValue, null, null, null, paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList, TypeSet paramMap_TYPE) {
+        return addNodeWithoutOrder(propName, propValue, propValueList, null, null,
+                paramMap_TYPE, null);
+    }
+
+    public PropertyNodesVerifier addNodeWithoutOrder(String propName, String propValue,
+            List<String> propValueList, byte[] propValue_bytes,
+            ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+        mUnorderedNodeList.add(new PropertyNode(propName, propValue,
+                propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
+        return this;
+    }
+
+    public void verify(VNode vnode) {
+        for (PropertyNode actualNode : vnode.propList) {
+            verifyNode(actualNode.propName, actualNode);
+        }
+        if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
+            List<String> expectedProps = new ArrayList<String>();
+            for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
+                for (PropertyNode node : nodes) {
+                    if (!expectedProps.contains(node.propName)) {
+                        expectedProps.add(node.propName);
+                    }
+                }
+            }
+            for (PropertyNode node : mUnorderedNodeList) {
+                if (!expectedProps.contains(node.propName)) {
+                    expectedProps.add(node.propName);
+                }
+            }
+            mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+                    + " was not found.");
+        }
+    }
+
+    private void verifyNode(final String propName, final PropertyNode actualNode) {
+        List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+        final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
+        if (size > 0) {
+            for (int i = 0; i < size; i++) {
+                PropertyNode expectedNode = expectedNodeList.get(i);
+                List<PropertyNode> expectedButDifferentValueList =
+                    new ArrayList<PropertyNode>();
+                if (expectedNode.propName.equals(propName)) {
+                    if (expectedNode.equals(actualNode)) {
+                        expectedNodeList.remove(i);
+                        if (expectedNodeList.size() == 0) {
+                            mOrderedNodeMap.remove(propName);
+                        }
+                        return;
+                    } else {
+                        expectedButDifferentValueList.add(expectedNode);
+                    }
+                }
+
+                // "actualNode" is not in ordered expected list.
+                // Try looking over unordered expected list.
+                if (tryFoundExpectedNodeFromUnorderedList(actualNode,
+                        expectedButDifferentValueList)) {
+                    return;
+                }
+
+                if (!expectedButDifferentValueList.isEmpty()) {
+                    // Same propName exists but with different value(s).
+                    failWithExpectedNodeList(propName, actualNode,
+                            expectedButDifferentValueList);
+                } else {
+                    // There's no expected node with same propName.
+                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+                }
+            }
+        } else {
+            List<PropertyNode> expectedButDifferentValueList =
+                new ArrayList<PropertyNode>();
+            if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
+                return;
+            } else {
+                if (!expectedButDifferentValueList.isEmpty()) {
+                    // Same propName exists but with different value(s).
+                    failWithExpectedNodeList(propName, actualNode,
+                            expectedButDifferentValueList);
+                } else {
+                    // There's no expected node with same propName.
+                    mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+                }
+            }
+        }
+    }
+
+    private 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..3f10605
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -0,0 +1,409 @@
+/*
+ * 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.vcard.PropertyNodesVerifier.TypeSet;
+
+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.VCardComposer;
+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.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+import android.util.Log;
+
+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.Iterator;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * 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");
+    }
+}
+
+/**
+ * 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 AndroidTestCase {
+    /* package */ static final byte[] sPhotoByteArray =
+        VCardImporterTests.sPhotoByteArrayForComplicatedCase;
+
+    public class ExportTestResolver extends MockContentResolver {
+        ExportTestProvider mProvider = new ExportTestProvider();
+        public ExportTestResolver() {
+            addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+            addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+        }
+
+        public ContentValues buildData(String mimeType) {
+            return mProvider.buildData(mimeType);
+        }
+    }
+
+    public static class MockEntityIterator implements EntityIterator {
+        Collection<Entity> mEntityCollection;
+        Iterator<Entity> mIterator;
+
+        // TODO: Support multiple vCard entries.
+        public MockEntityIterator(Collection<ContentValues> contentValuesCollection) {
+            mEntityCollection = new ArrayList<Entity>();
+            Entity entity = new Entity(new ContentValues());
+            for (ContentValues contentValues : contentValuesCollection) {
+                entity.addSubValue(Data.CONTENT_URI, contentValues);
+            }
+            mEntityCollection.add(entity);
+            mIterator = mEntityCollection.iterator();
+        }
+
+        public boolean hasNext() {
+            return mIterator.hasNext();
+        }
+
+        public Entity next() {
+            return mIterator.next();
+        }
+
+        public void reset() {
+            mIterator = mEntityCollection.iterator();
+        }
+
+        public void close() {
+        }
+    }
+
+    public class ExportTestProvider extends MockContentProvider {
+        List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+        public ContentValues buildData(String mimeType) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Data.MIMETYPE, mimeType);
+            mContentValuesList.add(contentValues);
+            return contentValues;
+        }
+
+        @Override
+        public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
+                String sortOrder) {
+            assert(uri != null);
+            assert(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+            final String authority = uri.getAuthority();
+            assert(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+
+            return new MockEntityIterator(mContentValuesList);
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            assert(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+            // Support multiple rows.
+            return new MockCursor() {
+                int mCurrentPosition = -1;
+
+                @Override
+                public int getCount() {
+                    return 1;
+                }
+
+                @Override
+                public boolean moveToFirst() {
+                    mCurrentPosition = 0;
+                    return true;
+                }
+
+                @Override
+                public boolean moveToNext() {
+                    if (mCurrentPosition == 0 || mCurrentPosition == -1) {
+                        mCurrentPosition++;
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }
+
+                @Override
+                public boolean isBeforeFirst() {
+                    return mCurrentPosition < 0;
+                }
+
+                @Override
+                public boolean isAfterLast() {
+                    return mCurrentPosition > 0;
+                }
+
+                @Override
+                public int getColumnIndex(String columnName) {
+                    assertEquals(Contacts._ID, columnName);
+                    return 0;
+                }
+
+                @Override
+                public int getInt(int columnIndex) {
+                    assertEquals(0, columnIndex);
+                    return 0;
+                }
+
+                @Override
+                public String getString(int columnIndex) {
+                    return String.valueOf(getInt(columnIndex));
+                }
+
+                @Override
+                public void close() {
+                }
+            };
+        }
+    }
+
+    public static class VCardVerificationHandler implements VCardComposer.OneEntryHandler {
+        final private TestCase mTestCase;
+        final private List<PropertyNodesVerifier> mPropertyNodesVerifierList;
+        final private boolean mIsV30;
+        int mCount;
+
+        public VCardVerificationHandler(TestCase testCase, boolean isV30) {
+            mTestCase = testCase;
+            mPropertyNodesVerifierList = new ArrayList<PropertyNodesVerifier>();
+            mIsV30 = isV30;
+            mCount = 1;
+        }
+
+        public PropertyNodesVerifier addNewPropertyNodesVerifier() {
+            PropertyNodesVerifier propertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
+            mPropertyNodesVerifierList.add(propertyNodesVerifier);
+            return propertyNodesVerifier;
+        }
+
+        public boolean onInit(Context context) {
+            return true;
+        }
+
+        public boolean onEntryCreated(String vcard) {
+            if (mPropertyNodesVerifierList.size() == 0) {
+                mTestCase.fail("Too many vCard entries seems to be inserted(No."
+                        + mCount + " of the entries (No.1 is the first entry))");
+            }
+            PropertyNodesVerifier propertyNodesVerifier =
+                mPropertyNodesVerifierList.get(0);
+            mPropertyNodesVerifierList.remove(0);
+            VCardParser parser = (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
+            VNodeBuilder builder = new VNodeBuilder();
+            InputStream is;
+            try {
+                is = new ByteArrayInputStream(vcard.getBytes("UTF-8"));
+                mTestCase.assertEquals(true, parser.parse(is, null, builder));
+                is.close();
+                mTestCase.assertEquals(1, builder.vNodeList.size());
+                propertyNodesVerifier.verify(builder.vNodeList.get(0));
+            } catch (IOException e) {
+                mTestCase.fail("Unexpected IOException: " + e.getMessage());
+            } catch (VCardException e) {
+                mTestCase.fail("Unexpected VCardException: " + e.getMessage());
+            } finally {
+                mCount++;
+            }
+            return true;
+        }
+
+        public void onTerminate() {
+        }
+    }
+
+    private class CustomMockContext extends MockContext {
+        final ContentResolver mResolver;
+        public CustomMockContext(ContentResolver resolver) {
+            mResolver = resolver;
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mResolver;
+        }
+    }
+
+    //// Followings are actual tests ////
+
+    public void testSimple() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "Ando");
+        contentValues.put(StructuredName.GIVEN_NAME, "Roid");
+
+        VCardVerificationHandler handler = new VCardVerificationHandler(this, false);
+        handler.addNewPropertyNodesVerifier()
+            .addNodeWithOrder("VERSION", "2.1")
+            .addNodeWithoutOrder("FN", "Roid Ando")
+            .addNodeWithoutOrder("N", "Ando;Roid;;;", Arrays.asList("Ando", "Roid", "", "", ""));
+
+        VCardComposer composer = new VCardComposer(new CustomMockContext(resolver),
+                VCardConfig.VCARD_TYPE_V21_GENERIC);
+        composer.addHandler(handler);
+        if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+            fail("init failed. Reason: " + composer.getErrorReason());
+        }
+        assertFalse(composer.isAfterLast());
+        assertTrue(composer.createOneEntry());
+        assertTrue(composer.isAfterLast());
+        composer.terminate();
+    }
+
+    private void testPhotoCommon(boolean isV30) {
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContentValues contentValues = resolver.buildData(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "PhotoTest");
+
+        contentValues = resolver.buildData(Photo.CONTENT_ITEM_TYPE);
+        contentValues.put(Photo.PHOTO, sPhotoByteArray);
+
+        ContentValues contentValuesForPhoto = new ContentValues();
+        contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+        VCardVerificationHandler handler = new VCardVerificationHandler(this, isV30);
+        handler.addNewPropertyNodesVerifier()
+            .addNodeWithOrder("VERSION", (isV30 ? "3.0" : "2.1"))
+            .addNodeWithoutOrder("FN", "PhotoTest")
+            .addNodeWithoutOrder("N", "PhotoTest;;;;", Arrays.asList("PhotoTest", "", "", "", ""))
+            .addNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
+                    contentValuesForPhoto, new TypeSet("JPEG"), null);
+
+        int vcardType = (isV30 ? VCardConfig.VCARD_TYPE_V30_GENERIC
+                : VCardConfig.VCARD_TYPE_V21_GENERIC);
+        VCardComposer composer = new VCardComposer(new CustomMockContext(resolver), vcardType);
+        composer.addHandler(handler);
+        if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+            fail("init() failed. Reason: " + composer.getErrorReason());
+        }
+        assertFalse(composer.isAfterLast());
+        assertTrue(composer.createOneEntry());
+        assertTrue(composer.isAfterLast());
+        composer.terminate();
+    }
+
+    public void testPhotoV21() {
+        testPhotoCommon(false);
+    }
+
+    public void testPhotoV30() {
+        testPhotoCommon(true);
+    }
+}
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..c6f827d
--- /dev/null
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardImporterTests.java
@@ -0,0 +1,1297 @@
+/*
+ * 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.R;
+import com.android.unit_tests.vcard.PropertyNodesVerifier.TypeSet;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.pim.vcard.EntryCommitter;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.provider.ContactsContract.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.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
+
+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.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+public class VCardImporterTests extends AndroidTestCase {
+    // 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 class VerificationResolver extends MockContentResolver {
+        VerificationProvider mVerificationProvider = new VerificationProvider();
+        @Override
+        public ContentProviderResult[] applyBatch(String authority,
+                ArrayList<ContentProviderOperation> operations) {
+            equalsString(authority, RawContacts.CONTENT_URI.toString());
+            return mVerificationProvider.applyBatch(operations);
+        }
+
+        public void addExpectedContentValues(ContentValues expectedContentValues) {
+            mVerificationProvider.addExpectedContentValues(expectedContentValues);
+        }
+
+        public void verify() {
+            mVerificationProvider.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));
+
+    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;
+    }
+
+    class VerificationProvider extends MockContentProvider {
+        final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
+
+        public VerificationProvider() {
+            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 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 == null) {
+                        fail("ContentValues for MimeType " + mimeType
+                                + " is not expected at all (" + actualContentValues + ")");
+                    }
+                    boolean checked = false;
+                    for (ContentValues expectedContentValues : contentValuesCollection) {
+                        /* For testing
+                        Log.d("@@@", "expected: "
+                                + convertToEasilyReadableString(expectedContentValues));
+                        Log.d("@@@", "actual  : "
+                                + convertToEasilyReadableString(actualContentValues));
+                         */
+                        if (equalsForContentValues(expectedContentValues,
+                                actualContentValues)) {
+                            assertTrue(contentValuesCollection.remove(expectedContentValues));
+                            checked = true;
+                            break;
+                        }
+                    }
+                    if (!checked) {
+                        final String failMsg =
+                            "Unexpected ContentValues for MimeType " + mimeType
+                            + ": " + actualContentValues;
+                        fail(failMsg);
+                    }
+                } else {
+                    fail("Unexpected Uri has come: " + uri);
+                }
+            }  // for (int i = 0; i < size; i++) {
+            return null;
+        }
+
+        public void verify() {
+            StringBuilder builder = new StringBuilder();
+            for (Collection<ContentValues> contentValuesCollection :
+                    mMimeTypeToExpectedContentValues.values()) {
+                for (ContentValues expectedContentValues: contentValuesCollection) {
+                    builder.append(convertToEasilyReadableString(expectedContentValues));
+                    builder.append("\n");
+                }
+            }
+            if (builder.length() > 0) {
+                final String failMsg = 
+                    "There is(are) remaining expected ContentValues instance(s): \n"
+                        + builder.toString();
+                fail(failMsg);
+            }
+        }
+    }
+
+    /**
+     * 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 String value = entry.getValue().toString();
+            if (Data.MIMETYPE.equals(key)) {
+                mimeTypeValue = value;
+            } else {
+                assertNotNull(key);
+                sortedMap.put(key, (value != null ? value.toString() : ""));
+            }
+        }
+        StringBuilder builder = new StringBuilder();
+        builder.append(Data.MIMETYPE);
+        builder.append('=');
+        builder.append(mimeTypeValue);
+        for (Entry<String, String> entry : sortedMap.entrySet()) {
+            final String key = entry.getKey();
+            final String value = entry.getValue();
+            builder.append(' ');
+            builder.append(key);
+            builder.append('=');
+            builder.append(value);
+        }
+        return builder.toString();
+    }
+    
+    private static boolean equalsString(String a, String b) {
+        if (a == null || a.length() == 0) {
+            return b == null || b.length() == 0;
+        } else {
+            return a.equals(b);
+        }
+    }
+
+    private class ContactStructVerifier {
+        private final int mResourceId;
+        private final int mVCardType;
+        private final VerificationResolver mResolver;
+        // private final String mCharset;
+        public ContactStructVerifier(int resId, int vCardType) {
+            mResourceId = resId;
+            mVCardType = vCardType;
+            mResolver = new VerificationResolver();
+        }
+        
+        public ContentValues createExpected(String mimeType) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(Data.MIMETYPE, mimeType);
+            mResolver.addExpectedContentValues(contentValues);
+            return contentValues;
+        }
+        
+        public void verify() throws IOException, VCardException {
+            InputStream is = getContext().getResources().openRawResource(mResourceId);
+            final VCardParser vCardParser;
+            if (VCardConfig.isV30(mVCardType)) {
+                vCardParser = new VCardParser_V30(true);  // use StrictParsing
+            } else {
+                vCardParser = new VCardParser_V21();
+            }
+            VCardDataBuilder builder =
+                new VCardDataBuilder(null, null, false, mVCardType, null);
+            builder.addEntryHandler(new EntryCommitter(mResolver));
+            try {
+                vCardParser.parse(is, builder);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+            mResolver.verify();
+        }
+    }
+
+    public void testV21SimpleCase1_Parsing() throws IOException, VCardException {
+        VCardParser_V21 parser = new VCardParser_V21();
+        VNodeBuilder builder = new VNodeBuilder();
+        InputStream is = getContext().getResources().openRawResource(R.raw.v21_simple_1);
+        assertEquals(true, parser.parse(is,"ISO-8859-1", builder));
+        is.close();
+        assertEquals(1, builder.vNodeList.size());
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this)
+            .addNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
+        verifier.verify(builder.vNodeList.get(0));
+    }
+
+    public void testV21SimpleCase1_Type_Generic() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "Ando");
+        contentValues.put(StructuredName.GIVEN_NAME, "Roid");
+        contentValues.put(StructuredName.DISPLAY_NAME, "Roid Ando");
+        verifier.verify();
+    }
+
+    public void testV21SimpleCase1_Type_Japanese() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_simple_1, VCardConfig.VCARD_TYPE_V21_JAPANESE);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "Ando");
+        contentValues.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"
+        contentValues.put(StructuredName.DISPLAY_NAME, "Roid Ando");
+        verifier.verify();
+    }
+
+    public void testV21SimpleCase2() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_simple_2, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.DISPLAY_NAME, "Ando Roid");
+        verifier.verify();
+    }
+
+    public void testV21SimpleCase3() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_simple_3, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "Ando");
+        contentValues.put(StructuredName.GIVEN_NAME, "Roid");
+        // "FN" field should be prefered since it should contain the original order intended by
+        // the author of the file.
+        contentValues.put(StructuredName.DISPLAY_NAME, "Ando Roid");
+        verifier.verify();
+    }
+
+    /**
+     * Tests ';' is properly handled by VCardParser implementation.
+     */
+    public void testV21BackslashCase_Parsing() 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(this)
+            .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(builder.vNodeList.get(0));
+    }
+
+    /**
+     * Tests ContactStruct correctly ignores redundant fields in "N" property values and
+     * inserts name related data.
+     */
+    public void testV21BackslashCase() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_backslash, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        // FAMILY_NAME is empty and removed in this test...
+        contentValues.put(StructuredName.GIVEN_NAME, "A;B\\");
+        contentValues.put(StructuredName.MIDDLE_NAME, "C\\;");
+        contentValues.put(StructuredName.PREFIX, "D");
+        contentValues.put(StructuredName.SUFFIX, ":E");
+        contentValues.put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\");
+        verifier.verify();
+    }
+
+    public void testOrgBeforTitle() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_org_before_title, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.DISPLAY_NAME, "Normal Guy");
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Company");
+        contentValues.put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.");
+        contentValues.put(Organization.TITLE, "Excellent Janitor");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify();
+    }
+
+    public void testTitleBeforOrg() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_title_before_org, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.DISPLAY_NAME, "Nice Guy");
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Marverous");
+        contentValues.put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor");
+        contentValues.put(Organization.TITLE, "Cool Title");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify();
+    }
+
+    /**
+     * 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 {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_pref_handling, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.DISPLAY_NAME, "Smith");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.NUMBER, "1");
+        contentValues.put(Phone.TYPE, Phone.TYPE_HOME);
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.NUMBER, "2");
+        contentValues.put(Phone.TYPE, Phone.TYPE_WORK);
+        contentValues.put(Phone.IS_PRIMARY, 1);
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.NUMBER, "3");
+        contentValues.put(Phone.TYPE, Phone.TYPE_ISDN);
+
+        contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE);
+        contentValues.put(Email.DATA, "test@example.com");
+        contentValues.put(Email.TYPE, Email.TYPE_HOME);
+        contentValues.put(Email.IS_PRIMARY, 1);
+
+        contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE);
+        contentValues.put(Email.DATA, "test2@examination.com");
+        contentValues.put(Email.TYPE, Email.TYPE_MOBILE);
+        contentValues.put(Email.IS_PRIMARY, 1);
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Company");
+        contentValues.put(Organization.TITLE, "Engineer");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Mystery");
+        contentValues.put(Organization.TITLE, "Blogger");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Poetry");
+        contentValues.put(Organization.TITLE, "Poet");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+        verifier.verify();
+    }
+
+    /**
+     * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
+     */
+    public void testV21ComplicatedCase_Parsing() 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");
+        PropertyNodesVerifier verifier = new PropertyNodesVerifier(this)
+            .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, contentValuesForQP, 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, contentValuesForQP,
+                    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, contentValuesForQP, null, null)
+            .addNodeWithOrder("PHOTO", null,
+                    null, sPhotoByteArrayForComplicatedCase, contentValuesForPhoto,
+                    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(builder.vNodeList.get(0));
+    }
+
+    /**
+     * Checks ContactStruct correctly inserts values in a complicated vCard
+     * into ContentResolver.
+     */
+    public void testV21ComplicatedCase() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_complicated, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "Gump");
+        contentValues.put(StructuredName.GIVEN_NAME, "Forrest");
+        contentValues.put(StructuredName.MIDDLE_NAME, "Hoge");
+        contentValues.put(StructuredName.PREFIX, "Pos");
+        contentValues.put(StructuredName.SUFFIX, "Tao");
+        contentValues.put(StructuredName.DISPLAY_NAME, "Joe Due");
+        
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+        contentValues.put(Organization.COMPANY, "Gump Shrimp Co.");
+        contentValues.put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper");
+        contentValues.put(Organization.TITLE, "Shrimp Man");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_WORK);
+        // Phone number is expected to be formated with NAMP format in default.
+        contentValues.put(Phone.NUMBER, "111-555-1212");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_HOME);
+        contentValues.put(Phone.NUMBER, "404-555-1212");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_MOBILE);
+        contentValues.put(Phone.NUMBER, "031-111-1111");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM);
+        contentValues.put(Phone.LABEL, "VIDEO");
+        contentValues.put(Phone.NUMBER, "032-222-2222");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM);
+        contentValues.put(Phone.LABEL, "VOICE");
+        contentValues.put(Phone.NUMBER, "033-333-3333");
+
+        contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
+        contentValues.put(StructuredPostal.COUNTRY, "United States of America");
+        contentValues.put(StructuredPostal.POSTCODE, "30314");
+        contentValues.put(StructuredPostal.REGION, "LA");
+        contentValues.put(StructuredPostal.CITY, "Baytown");
+        contentValues.put(StructuredPostal.STREET, "100 Waters Edge");
+        contentValues.put(StructuredPostal.FORMATTED_ADDRESS,
+                "100 Waters Edge Baytown LA 30314 United States of America");
+
+        contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+        contentValues.put(StructuredPostal.COUNTRY, "United States of America");
+        contentValues.put(StructuredPostal.POSTCODE, "30314");
+        contentValues.put(StructuredPostal.REGION, "LA");
+        contentValues.put(StructuredPostal.CITY, "Baytown");
+        contentValues.put(StructuredPostal.STREET, "42 Plantation St.");
+        contentValues.put(StructuredPostal.FORMATTED_ADDRESS,
+                "42 Plantation St. Baytown LA 30314 United States of America");
+
+        contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE);
+        // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET"
+        contentValues.put(Email.TYPE, Email.TYPE_CUSTOM);
+        contentValues.put(Email.LABEL, "INTERNET");
+        contentValues.put(Email.DATA, "forrestgump@walladalla.com");
+        contentValues.put(Email.IS_PRIMARY, 1);
+
+        contentValues = verifier.createExpected(Email.CONTENT_ITEM_TYPE);
+        contentValues.put(Email.TYPE, Email.TYPE_MOBILE);
+        contentValues.put(Email.DATA, "cell@example.com");
+
+        contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE);
+        contentValues.put(Note.NOTE, "The following note is the example from RFC 2045.");
+
+        contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE);
+        contentValues.put(Note.NOTE,
+                "Now's the time for all folk to come to the aid of their country.");
+
+        contentValues = verifier.createExpected(Photo.CONTENT_ITEM_TYPE);
+        // No information about its image format can be inserted.
+        contentValues.put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase);
+
+        contentValues = verifier.createExpected(Event.CONTENT_ITEM_TYPE);
+        contentValues.put(Event.START_DATE, "19800101");
+        contentValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
+
+        contentValues = verifier.createExpected(Website.CONTENT_ITEM_TYPE);
+        contentValues.put(Website.URL, "http://www.example.com/");
+        contentValues.put(Website.TYPE, Website.TYPE_HOMEPAGE);
+        verifier.verify();
+    }
+
+    public void testV30Simple_Parsing() 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(this)
+            .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(builder.vNodeList.get(0));
+    }
+
+    public void testV30Simple() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v30_simple, VCardConfig.VCARD_TYPE_V30_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "And");
+        contentValues.put(StructuredName.GIVEN_NAME, "Roid");
+        contentValues.put(StructuredName.DISPLAY_NAME, "And Roid");
+
+        contentValues = verifier.createExpected(Organization.CONTENT_ITEM_TYPE);
+        contentValues.put(Organization.COMPANY, "Open");
+        contentValues.put(Organization.DEPARTMENT, "Handset  Alliance");
+        contentValues.put(Organization.TYPE, Organization.TYPE_WORK);
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM);
+        contentValues.put(Phone.LABEL, "VOICE");
+        contentValues.put(Phone.NUMBER, "030-000-0000");
+        contentValues.put(Phone.IS_PRIMARY, 1);
+        verifier.verify();
+    }
+
+    public void testV21Japanese1_Parsing() 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(this)
+            .addNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
+            .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+                    Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+                    null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("SOUND", "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+                    null, null, contentValuesForShiftJis,
+                    new TypeSet("X-IRMC-N"), null)
+            .addNodeWithOrder("TEL", "0300000000", null, null, null,
+                    new TypeSet("VOICE", "PREF"), null);
+        verifier.verify(builder.vNodeList.get(0));
+    }
+
+    private void testV21Japanese1Common(ContactStructVerifier verifier, boolean japanese)
+            throws IOException, VCardException {
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9");
+        contentValues.put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9");
+        // While vCard parser does not split "SOUND" property values, ContactStruct care it.
+        contentValues.put(StructuredName.PHONETIC_FAMILY_NAME,
+                "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E");
+
+        contentValues = verifier.createExpected(Phone.CONTENT_ITEM_TYPE);
+        // Phone number formatting is different.
+        if (japanese) {
+            contentValues.put(Phone.NUMBER, "03-0000-0000");
+        } else {
+            contentValues.put(Phone.NUMBER, "030-000-0000");
+        }
+        contentValues.put(Phone.TYPE, Phone.TYPE_CUSTOM);
+        contentValues.put(Phone.LABEL, "VOICE");
+        contentValues.put(Phone.IS_PRIMARY, 1);
+        verifier.verify();
+    }
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with VCARD_TYPE_V21_GENERIC. 
+     */
+    public void testV21Japanese1_Type_Generic() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        testV21Japanese1Common(verifier, false);
+    }
+
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with VCARD_TYPE_V21_JAPANESE.
+     */
+    public void testV21Japanese1_Type_Japanese() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE);
+        testV21Japanese1Common(verifier, true);
+    }
+
+    /**
+     * Verifies vCard with Japanese can be parsed correctly with 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 {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+        testV21Japanese1Common(verifier, true);
+    }
+
+    public void testV21Japanese2_Parsing() 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(this)
+            .addNodeWithOrder("VERSION", "2.1")
+            .addNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+                    Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+                            "", "", ""),
+                    null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+                    null, null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("SOUND",
+                    "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
+                    null, null, contentValuesForShiftJis,
+                    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, contentValuesForQP, new TypeSet("HOME"), null)
+            .addNodeWithOrder("NOTE", "\u30E1\u30E2", null, null, contentValuesForQP, null, null);
+        verifier.verify(builder.vNodeList.get(0));
+    }
+
+    public void testV21Japanese2_Type_Generic() throws IOException, VCardException {
+        ContactStructVerifier verifier = new ContactStructVerifier(
+                R.raw.v21_japanese_2, VCardConfig.VCARD_TYPE_V21_GENERIC);
+        ContentValues contentValues =
+            verifier.createExpected(StructuredName.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4");
+        contentValues.put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031");
+        contentValues.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. 
+        contentValues.put(StructuredName.PHONETIC_FAMILY_NAME,
+                "\uFF71\uFF9D\uFF84\uFF9E\uFF73");
+        contentValues.put(StructuredName.PHONETIC_GIVEN_NAME,
+                "\uFF9B\uFF72\uFF84\uFF9E\u0031");
+
+        contentValues = verifier.createExpected(StructuredPostal.CONTENT_ITEM_TYPE);
+        contentValues.put(StructuredPostal.POSTCODE, "150-8512");
+        contentValues.put(StructuredPostal.NEIGHBORHOOD,
+                "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+                "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+                "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+                "\u0036\u968E");
+        contentValues.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");
+        contentValues.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+        contentValues = verifier.createExpected(Note.CONTENT_ITEM_TYPE);
+        contentValues.put(Note.NOTE, "\u30E1\u30E2");
+        verifier.verify();
+    }
+
+    // Following tests are old ones, though they still work fine.
+    
+    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(this)
+            .addNodeWithOrder("VERSION", "2.1")
+            .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+                    Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+                    null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("SOUND",
+                    "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+                    null, null, contentValuesForShiftJis,
+                    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.verify(builder.vNodeList.get(0));
+        
+        verifier = new PropertyNodesVerifier(this)
+            .addNodeWithOrder("VERSION", "2.1")
+            .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+                    Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+                    null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("SOUND",
+                    "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+                    null, null, contentValuesForShiftJis,
+                    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.verify(builder.vNodeList.get(1));
+        verifier = new PropertyNodesVerifier(this)
+            .addNodeWithOrder("VERSION", "2.1")
+            .addNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+                    Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+                    null, contentValuesForShiftJis, null, null)
+            .addNodeWithOrder("SOUND",
+                    "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+                    null, null, contentValuesForShiftJis,
+                    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(builder.vNodeList.get(2));
+    }
+}
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/VNode.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
index 3eb827b..7587320 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNode.java
@@ -18,7 +18,7 @@
 import java.util.ArrayList;
 
 /**
- * @hide old class. Just for testing
+ * Previously used in main vCard handling code but now exists only for testing.
  */
 public class VNode {
     public String VName;
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
index 6d69223..ce4de03 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VNodeBuilder.java
@@ -36,7 +36,8 @@
  * Maybe several vcard instance, so use vNodeList to store.
  * VNode: standy by a vcard instance.
  * PropertyNode: standy by a property line of a card.
- * @hide old class, just for testing use 
+ *
+ * Previously used in main vCard handling code but now exists only for testing.
  */
 public class VNodeBuilder implements VCardBuilder {
     static private String LOG_TAG = "VDATABuilder"; 
@@ -189,6 +190,7 @@
     
     private String handleOneValue(String value, String targetCharset, String encoding) {
         if (encoding != null) {
+            encoding = encoding.toUpperCase();
             if (encoding.equals("BASE64") || encoding.equals("B")) {
                 // Assume BASE64 is used only when the number of values is 1.
                 mCurrentPropNode.propValue_bytes =
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 2c0d0f1..b7d3a6e 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -90,6 +90,7 @@
      */ 
     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;
 
     /**
      * Interval in milliseconds between polling for connection
@@ -126,6 +127,14 @@
     private static final int RECONNECT_DELAY_MSECS = 2000;
 
     /**
+     * When the supplicant disconnects from an AP it sometimes forgets
+     * to restart scanning.  Wait this delay before asking it to start
+     * scanning (in case it forgot).  15 sec is the standard delay between
+     * scans.
+     */
+    private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000;
+
+    /**
      * The maximum number of times we will retry a connection to an access point
      * for which we have failed in acquiring an IP address from DHCP. A value of
      * N means that we will make N+1 connection attempts in all.
@@ -149,6 +158,14 @@
     private int mNumSupplicantLoopIterations = 0;
 
     /**
+     * The current number of supplicant state changes.  This is used to determine
+     * if we've received any new info since we found out it was DISCONNECTED or
+     * INACTIVE.  If we haven't for X ms, we then request a scan - it should have
+     * done that automatically, but sometimes some firmware does not.
+     */
+    private int mNumSupplicantStateChanges = 0;
+
+    /**
      * True if we received an event that that a password-key may be incorrect.
      * If the next incoming supplicant state change event is DISCONNECT,
      * broadcast a message that we have a possible password error and disable
@@ -831,7 +848,16 @@
                 }
                 break;
 
+            case EVENT_MAYBE_START_SCAN_POST_DISCONNECT:
+                // Only do this if we haven't gotten a new supplicant status since the timer
+                // started
+                if (mNumSupplicantStateChanges == msg.arg1) {
+                    WifiNative.scanCommand(false); // do a passive scan
+                }
+                break;
+
             case EVENT_SUPPLICANT_STATE_CHANGED:
+                mNumSupplicantStateChanges++;
                 SupplicantStateChangeResult supplicantStateResult =
                     (SupplicantStateChangeResult) msg.obj;
                 SupplicantState newState = supplicantStateResult.state;
@@ -850,6 +876,17 @@
                 int networkId = supplicantStateResult.networkId;
 
                 /*
+                 * If we get disconnect or inactive we need to start our
+                 * watchdog timer to start a scan
+                 */
+                if (newState == SupplicantState.DISCONNECTED ||
+                        newState == SupplicantState.INACTIVE) {
+                    sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT,
+                            mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS);
+                }
+
+
+                /*
                  * Did we get to DISCONNECTED state due to an
                  * authentication (password) failure?
                  */