Merge change I9195a354 into eclair-mr2

* changes:
  Add partial support of Android-specific properties.
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
index a1c7e10..052f329 100644
--- a/core/java/android/pim/vcard/Constants.java
+++ b/core/java/android/pim/vcard/Constants.java
@@ -72,6 +72,9 @@
     // Phone number for Skype, available as usual phone.
     public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
 
+    // Property for Android-specific fields.
+    public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
+
     // Properties for DoCoMo vCard.
     public static final String PROPERTY_X_CLASS = "X-CLASS";
     public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
@@ -158,6 +161,9 @@
         public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
     }
 
+    // TODO: Should be in ContactsContract?
+    /* package */ static final int MAX_DATA_COLUMN = 15;
+
     private Constants() {
     }
 }
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index eb9c48a..9d40db6 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -442,7 +442,8 @@
     private List<ImData> mImList;
     private List<PhotoData> mPhotoList;
     private List<String> mWebsiteList;
-    
+    private List<List<String>> mAndroidCustomPropertyList;
+
     private final int mVCardType;
     private final Account mAccount;
 
@@ -928,14 +929,19 @@
                 mWebsiteList = new ArrayList<String>(1);
             }
             mWebsiteList.add(propValue);
+        } else if (propName.equals(Constants.PROPERTY_BDAY)) {
+            mBirthday = propValue;
         } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
             mPhoneticGivenName = propValue;
         } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
             mPhoneticMiddleName = propValue;
         } else if (propName.equals(Constants.PROPERTY_X_PHONETIC_LAST_NAME)) {
             mPhoneticFamilyName = propValue;
-        } else if (propName.equals(Constants.PROPERTY_BDAY)) {
-            mBirthday = propValue;
+        } else if (propName.equals(Constants.PROPERTY_X_ANDROID_CUSTOM)) {
+            final List<String> customPropertyList =
+                VCardUtils.constructListFromValue(propValue,
+                        VCardConfig.isV30(mVCardType));
+            handleAndroidCustomProperty(customPropertyList);
         /*} else if (propName.equals("REV")) {                
             // Revision of this VCard entry. I think we can ignore this.
         } else if (propName.equals("UID")) {
@@ -963,6 +969,13 @@
         }
     }
 
+    private void handleAndroidCustomProperty(final List<String> customPropertyList) {
+        if (mAndroidCustomPropertyList == null) {
+            mAndroidCustomPropertyList = new ArrayList<List<String>>();
+        }
+        mAndroidCustomPropertyList.add(customPropertyList);
+    }
+
     /**
      * Construct the display name. The constructed data must not be null.
      */
@@ -1017,7 +1030,7 @@
             mDisplayName = "";
         }
     }
-    
+
     /**
      * Consolidate several fielsds (like mName) using name candidates, 
      */
@@ -1028,7 +1041,7 @@
             mPhoneticFullName = mPhoneticFullName.trim();
         }
     }
-    
+
     // From GoogleSource.java in Contacts app.
     private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
     private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
@@ -1224,6 +1237,36 @@
             operationList.add(builder.build());
         }
 
+        if (mAndroidCustomPropertyList != null) {
+            for (List<String> customPropertyList : mAndroidCustomPropertyList) {
+                int size = customPropertyList.size();
+                if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
+                    continue;
+                } else if (size > Constants.MAX_DATA_COLUMN + 1) {
+                    size = Constants.MAX_DATA_COLUMN + 1;
+                    customPropertyList =
+                        customPropertyList.subList(0, Constants.MAX_DATA_COLUMN + 2);
+                }
+
+                int i = 0;
+                for (final String customPropertyValue : customPropertyList) {
+                    if (i == 0) {
+                        final String mimeType = customPropertyValue;
+                        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                        builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+                        builder.withValue(Data.MIMETYPE, mimeType);
+                    } else {  // 1 <= i && i <= MAX_DATA_COLUMNS  
+                        if (!TextUtils.isEmpty(customPropertyValue)) {
+                            builder.withValue("data" + i, customPropertyValue);
+                        }
+                    }
+
+                    operationList.add(builder.build());
+                    i++;
+                }
+            }
+        }
+
         if (myGroupsId != null) {
             builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
             builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 8eabd4b1..33581c9 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -1019,11 +1019,11 @@
             return;
         }
 
-        final String propertyNickname;
+        final boolean useAndroidProperty;
         if (mIsV30) {
-            propertyNickname = Constants.PROPERTY_NICKNAME;
-        /*} else if (mUsesAndroidProperty) {
-            propertyNickname = VCARD_PROPERTY_X_NICKNAME;*/
+            useAndroidProperty = false;
+        } else if (mUsesAndroidProperty) {
+            useAndroidProperty = true;
         } else {
             // There's no way to add this field.
             return;
@@ -1034,29 +1034,13 @@
             if (TextUtils.isEmpty(nickname)) {
                 continue;
             }
-
-            final String encodedNickname;
-            final boolean reallyUseQuotedPrintable =
-                (mUsesQuotedPrintable &&
-                        !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
-            if (reallyUseQuotedPrintable) {
-                encodedNickname = encodeQuotedPrintable(nickname);
+            if (useAndroidProperty) {
+                appendAndroidSpecificProperty(builder, Nickname.CONTENT_ITEM_TYPE,
+                        contentValues);
             } else {
-                encodedNickname = escapeCharacters(nickname);
+                appendVCardLineWithCharsetAndQPDetection(builder,
+                        Constants.PROPERTY_NICKNAME, nickname);
             }
-
-            builder.append(propertyNickname);
-            if (shouldAppendCharsetAttribute(propertyNickname)) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(mVCardAttributeCharset);
-            }
-            if (reallyUseQuotedPrintable) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-                builder.append(VCARD_ATTR_ENCODING_QP);
-            }
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodedNickname);
-            builder.append(VCARD_COL_SEPARATOR);
         }
     }
 
@@ -1491,6 +1475,33 @@
         }
     }
 
+    private void appendAndroidSpecificProperty(final StringBuilder builder,
+            final String mimeType, ContentValues contentValues) {
+        List<String> rawDataList = new ArrayList<String>();
+        rawDataList.add(mimeType);
+        final List<String> columnNameList;
+        if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            
+        } else {
+            // If you add the other field, please check all the columns are able to be
+            // converted to String.
+            //
+            // e.g. BLOB is not what we can handle here now.
+            return;
+        }
+
+        for (int i = 0; i < Constants.MAX_DATA_COLUMN; i++) {
+            String value = contentValues.getAsString("data" + i);
+            if (value == null) {
+                value = "";
+            }
+            rawDataList.add(value);
+        }
+
+        appendVCardLineWithCharsetAndQPDetection(builder,
+                Constants.PROPERTY_X_ANDROID_CUSTOM, rawDataList);
+    }
+
     /**
      * Append '\' to the characters which should be escaped. The character set is different
      * not only between vCard 2.1 and vCard 3.0 but also among each device.
@@ -1968,6 +1979,8 @@
         }
     }
 
+    // appendVCardLine() variants accepting one String.
+
     private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
             final String propertyName, final String rawData) {
         appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawData);
@@ -2026,6 +2039,87 @@
         builder.append(VCARD_COL_SEPARATOR);
     }
 
+    // appendVCardLine() variants accepting List<String>.
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList) {
+        appendVCardLineWithCharsetAndQPDetection(builder, propertyName, null, rawDataList);
+    }
+
+    private void appendVCardLineWithCharsetAndQPDetection(final StringBuilder builder,
+            final String propertyName,
+            final List<String> attributeList, final List<String> rawDataList) {
+        boolean needCharset = false;
+        boolean reallyUseQuotedPrintable = false;
+        for (String rawData : rawDataList) {
+            if (!needCharset && mUsesQuotedPrintable &&
+                    !VCardUtils.containsOnlyPrintableAscii(rawData)) {
+                needCharset = true;
+            }
+            if (!reallyUseQuotedPrintable &&
+                    !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawData)) {
+                reallyUseQuotedPrintable = true;
+            }
+            if (needCharset && reallyUseQuotedPrintable) {
+                break;
+            }
+        }
+
+        appendVCardLine(builder, propertyName, attributeList,
+                rawDataList, needCharset, reallyUseQuotedPrintable);
+    }
+
+    /*
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList) {
+        appendVCardLine(builder, propertyName, rawDataList, false, false);
+    }
+
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName, final List<String> rawDataList,
+            final boolean needCharset, boolean needQuotedPrintable) {
+        appendVCardLine(builder, propertyName, null, rawDataList, needCharset, needQuotedPrintable);
+    }*/
+
+    private void appendVCardLine(final StringBuilder builder,
+            final String propertyName,
+            final List<String> attributeList,
+            final List<String> rawDataList, final boolean needCharset,
+            boolean needQuotedPrintable) {
+        builder.append(propertyName);
+        if (attributeList != null && attributeList.size() > 0) {
+            builder.append(VCARD_ATTR_SEPARATOR);
+            appendTypeAttributes(builder, attributeList);
+        }
+        if (needCharset) {
+            builder.append(VCARD_ATTR_SEPARATOR);
+            builder.append(mVCardAttributeCharset);
+        }
+
+        builder.append(VCARD_DATA_SEPARATOR);
+        boolean first = true;
+        for (String rawData : rawDataList) {
+            final String encodedData;
+            if (needQuotedPrintable) {
+                builder.append(VCARD_ATTR_SEPARATOR);
+                builder.append(VCARD_ATTR_ENCODING_QP);
+                encodedData = encodeQuotedPrintable(rawData);
+            } else {
+                // TODO: one line may be too huge, which may be invalid in vCard spec, though
+                //       several (even well-known) applications do not care this.
+                encodedData = escapeCharacters(rawData);
+            }
+
+            if (first) {
+                first = false;
+            } else {
+                builder.append(VCARD_ITEM_SEPARATOR);
+            }
+            builder.append(encodedData);
+        }
+        builder.append(VCARD_COL_SEPARATOR);
+    }
+
     /**
      * VCARD_ATTR_SEPARATOR must be appended before this method being called.
      */
diff --git a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
index d0097c4..f1a46d6 100644
--- a/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/vcard/VCardExporterTests.java
@@ -643,9 +643,6 @@
         testStructuredNameUseSuperPrimaryCommon(V30);
     }
 
-    /**
-     * There's no property for nickname in vCard 2.1, so we don't have any requirement on it.
-     */
     public void testNickNameV30() {
         ExportTestResolver resolver = new ExportTestResolver();
         ContentValues contentValues = resolver.buildData(Nickname.CONTENT_ITEM_TYPE);
@@ -1269,4 +1266,23 @@
 
         verifyOneComposition(resolver, handler, version);
     }
+
+    /**
+     * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
+     * We use Android-specific "X-ANDROID-CUSTOM" property.
+     * This test verifies the functionality.
+     */
+    public void testNickNameV21() {
+        ExportTestResolver resolver = new ExportTestResolver();
+        ContentValues contentValues = resolver.buildData(Nickname.CONTENT_ITEM_TYPE);
+        contentValues.put(Nickname.NAME, "Nicky");
+
+        VCardVerificationHandler handler = new VCardVerificationHandler(this, V21);
+        handler.addNewVerifierWithEmptyName()
+            .addNodeWithOrder("X-ANDROID-CUSTOM", Nickname.CONTENT_ITEM_TYPE + ";Nicky");
+
+        // TODO: also test import part.
+
+        verifyOneComposition(resolver, handler, V21);
+    }
 }