Modify vCard importer/exporter around sip handling

Bug: 2985401
Change-Id: I8216dd80bcb09669be807dd92452e69fe2a11a65
diff --git a/java/com/android/vcard/VCardBuilder.java b/java/com/android/vcard/VCardBuilder.java
index f063a07..2eec998 100644
--- a/java/com/android/vcard/VCardBuilder.java
+++ b/java/com/android/vcard/VCardBuilder.java
@@ -1773,8 +1773,6 @@
      * instead of "IMPP;sip:...".
      *
      * We honor RFC 4770 and don't allow vCard 3.0 to emit X-SIP at all.
-     *
-     * vCard 4.0 is aware of RFC 4770, so just using IMPP would be fine.
      */
     public VCardBuilder appendSipAddresses(final List<ContentValues> contentValuesList) {
         final boolean useXProperty;
@@ -1803,11 +1801,19 @@
                     // No type is available yet.
                     appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_X_SIP, sipAddress);
                 } else {
-                    // IMPP is not just for SIP but the other protcols like XMPP.
                     if (!sipAddress.startsWith("sip:")) {
                         sipAddress = "sip:" + sipAddress;
                     }
-                    appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_IMPP, sipAddress);
+                    final String propertyName;
+                    if (VCardConfig.isVersion40(mVCardType)) {
+                        // We have two ways to emit sip address: TEL and IMPP. Currently (rev.13)
+                        // TEL seems appropriate but may change in the future.
+                        propertyName = VCardConstants.PROPERTY_TEL;
+                    } else {
+                        // RFC 4770 (for vCard 3.0)
+                        propertyName = VCardConstants.PROPERTY_IMPP;
+                    }
+                    appendLineWithCharsetAndQPDetection(propertyName, sipAddress);
                 }
             }
         }
diff --git a/java/com/android/vcard/VCardEntry.java b/java/com/android/vcard/VCardEntry.java
index b5ed10f..66104bf 100644
--- a/java/com/android/vcard/VCardEntry.java
+++ b/java/com/android/vcard/VCardEntry.java
@@ -49,8 +49,10 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * This class bridges between data structure of Contact app and VCard data.
@@ -470,7 +472,7 @@
     private List<ImData> mImList;
     private List<PhotoData> mPhotoList;
     private List<String> mWebsiteList;
-    private List<String> mSipList;
+    private Set<String> mSipSet;
     private List<List<String>> mAndroidCustomPropertyList;
 
     private final int mVCardType;
@@ -977,9 +979,36 @@
                 addPhotoBytes(formatName, propBytes, isPrimary);
             }
         } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+            final String phoneNumber;
+            if (VCardConfig.isVersion40(mVCardType)) {
+                // Given propValue is in URI format, not in phone number format used until
+                // vCard 3.0.
+                if (propValue.startsWith("sip:") ) {
+                    if (propValue.length() > 4) {
+                        if (mSipSet == null) {
+                            mSipSet = new LinkedHashSet<String>();
+                        }
+                        mSipSet.add(propValue.substring(4));
+                    }
+                    return;
+                } else if (propValue.startsWith("tel:")) {
+                    phoneNumber = propValue.substring(4);
+                } else {
+                    // We don't know appropriate way to handle the other schemas. Also,
+                    // we may still have non-URI phone number. To keep given data as much as
+                    // we can, just save original value here.
+                    phoneNumber = propValue;
+                }
+            } else {
+                phoneNumber = propValue;
+            }
+
+            if (propValue.length() == 0) {
+                return;
+            }
             final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
             final Object typeObject =
-                VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
+                    VCardUtils.getPhoneTypeFromStrings(typeCollection, phoneNumber);
             final int type;
             final String label;
             if (typeObject instanceof Integer) {
@@ -991,12 +1020,13 @@
             }
 
             final boolean isPrimary;
-            if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
+            if (typeCollection != null &&
+                    typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
                 isPrimary = true;
             } else {
                 isPrimary = false;
             }
-            addPhone(type, propValue, label, isPrimary);
+            addPhone(type, phoneNumber, label, isPrimary);
         } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
             // The phone number available via Skype.
             Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
@@ -1050,24 +1080,26 @@
         } else if (propName.equals(VCardConstants.PROPERTY_IMPP)) {
             // See also RFC 4770 (for vCard 3.0)
             if (propValue.startsWith("sip:") && propValue.length() > 4) {
-                if (mSipList == null) {
-                    mSipList = new ArrayList<String>();
+                if (mSipSet == null) {
+                    mSipSet = new LinkedHashSet<String>();
                 }
-                mSipList.add(propValue.substring(4));
+                mSipSet.add(propValue.substring(4));
             }
         } else if (propName.equals(VCardConstants.PROPERTY_X_SIP)) {
             if (!TextUtils.isEmpty(propValue)) {
-                if (mSipList == null) {
-                    mSipList = new ArrayList<String>();
+                if (mSipSet == null) {
+                    mSipSet = new LinkedHashSet<String>();
                 }
 
-                final String sipAddress;
-                if (propValue.startsWith("sip:") && propValue.length() > 4) {
-                    sipAddress = propValue.substring(4);
+                if (propValue.startsWith("sip:")) {
+                    if (propValue.length() > 4) {
+                        mSipSet.add(propValue.substring(4));
+                    } else {
+                        // Empty sip value. Ignore.
+                    }
                 } else {
-                    sipAddress = propValue;
+                    mSipSet.add(propValue);
                 }
-                mSipList.add(sipAddress);
             }
         } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
             final List<String> customPropertyList =
@@ -1075,6 +1107,7 @@
             handleAndroidCustomProperty(customPropertyList);
         } else {
         }
+        // Be careful when adding some logic here, as some blocks above may use "return".
     }
 
     private void handleAndroidCustomProperty(final List<String> customPropertyList) {
@@ -1320,8 +1353,8 @@
             operationList.add(builder.build());
         }
 
-        if (mSipList != null && !mSipList.isEmpty()) {
-            for (String sipAddress : mSipList) {
+        if (mSipSet != null && !mSipSet.isEmpty()) {
+            for (String sipAddress : mSipSet) {
                 builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
                 builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
                 builder.withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
diff --git a/tests/res/raw/v40_sip.vcf b/tests/res/raw/v40_sip.vcf
new file mode 100644
index 0000000..7b45ce3
--- /dev/null
+++ b/tests/res/raw/v40_sip.vcf
@@ -0,0 +1,7 @@
+BEGIN:VCARD

+VERSION:4.0

+FN:安藤 ロイド

+N:安藤;ロイド;;;

+TEL;TYPE=HOME:tel:1

+TEL:sip:example@example.com

+END:VCARD

diff --git a/tests/src/com/android/vcard/tests/VCardExporterTests.java b/tests/src/com/android/vcard/tests/VCardExporterTests.java
index 8fb0685..76bd755 100644
--- a/tests/src/com/android/vcard/tests/VCardExporterTests.java
+++ b/tests/src/com/android/vcard/tests/VCardExporterTests.java
@@ -1299,8 +1299,8 @@
                 .put(SipAddress.SIP_ADDRESS, "android@example.com");
         mVerifier.addLineVerifierElem()
                 .addExpected("FN:")
-                .addExpected("IMPP:sip:android@example.com");
+                .addExpected("TEL:sip:android@example.com");
         mVerifier.addPropertyNodesVerifierElemWithEmptyName()
-                .addExpectedNode("IMPP", "sip:android@example.com");
+                .addExpectedNode("TEL", "sip:android@example.com");
     }
 }
diff --git a/tests/src/com/android/vcard/tests/VCardImporterTests.java b/tests/src/com/android/vcard/tests/VCardImporterTests.java
index 36cc085..2900106 100644
--- a/tests/src/com/android/vcard/tests/VCardImporterTests.java
+++ b/tests/src/com/android/vcard/tests/VCardImporterTests.java
@@ -1144,4 +1144,18 @@
         elem.addExpected(SipAddress.CONTENT_ITEM_TYPE)
                 .put(SipAddress.SIP_ADDRESS, "90-180-360");
     }
+
+    public void testSipV40() {
+        mVerifier.initForImportTest(V40, R.raw.v40_sip);
+        final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+        elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+                .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
+                .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9")
+                .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9");
+        elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+                .put(Phone.TYPE, Phone.TYPE_HOME)
+                .put(Phone.NUMBER, "1");
+        elem.addExpected(SipAddress.CONTENT_ITEM_TYPE)
+                .put(SipAddress.SIP_ADDRESS, "example@example.com");
+    }
 }