summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorAndroid (Google) Code Review <android-gerrit@google.com>2009-08-25 22:08:38 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2009-08-25 22:08:38 -0700
commit97f870956a37d441f82e21135a5e68d3ecdd0bf5 (patch)
tree0803c76ea83196fbbd9651639ee10cad0683004a /core/java/android
parentba176d6c8ad581e65b46bd6835c0737e74ef453d (diff)
parentf4ddea769098e24a7316b9ee895d323005433c2c (diff)
downloadframeworks_base-97f870956a37d441f82e21135a5e68d3ecdd0bf5.zip
frameworks_base-97f870956a37d441f82e21135a5e68d3ecdd0bf5.tar.gz
frameworks_base-97f870956a37d441f82e21135a5e68d3ecdd0bf5.tar.bz2
Merge change 22399 into eclair
* changes: Refactor VCard handling code, phase 2, 3, 4, 5
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/pim/vcard/Constants.java94
-rw-r--r--core/java/android/pim/vcard/ContactStruct.java1438
-rw-r--r--core/java/android/pim/vcard/EntryCommitter.java54
-rw-r--r--core/java/android/pim/vcard/EntryHandler.java13
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java1433
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java268
-rw-r--r--core/java/android/pim/vcard/VCardDataBuilder.java33
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java142
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java10
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java764
-rw-r--r--core/java/android/syncml/pim/vcard/VCardDataBuilder.java6
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser.java1
12 files changed, 3349 insertions, 907 deletions
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
new file mode 100644
index 0000000..ca41ce5
--- /dev/null
+++ b/core/java/android/pim/vcard/Constants.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+/**
+ * Constants used in both composer and parser.
+ */
+/* package */ class Constants {
+
+ public static final String ATTR_TYPE = "TYPE";
+
+ public static final String VERSION_V21 = "2.1";
+ public static final String VERSION_V30 = "3.0";
+
+ // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
+ // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ public static final String PROPERTY_X_AIM = "X-AIM";
+ public static final String PROPERTY_X_MSN = "X-MSN";
+ public static final String PROPERTY_X_YAHOO = "X-YAHOO";
+ public static final String PROPERTY_X_ICQ = "X-ICQ";
+ public static final String PROPERTY_X_JABBER = "X-JABBER";
+ public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
+ public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+ // Phone number for Skype, available as usual phone.
+ public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
+ // Some device emits this "X-" attribute, which is specifically invalid but should be
+ // always properly accepted, and emitted in some special case (for that device/application).
+ public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+
+ // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
+ //
+ // e.g.
+ // 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."
+ // 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."
+ // 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."
+ //
+ // 2) has been the default of VCard exporter/importer in Android, but we can see the other
+ // formats in vCard data emitted by the other softwares/devices.
+ //
+ // So we are currently not sure which type is the best; probably we will have to change which
+ // type should be emitted depending on the device.
+ public static final String ATTR_TYPE_HOME = "HOME";
+ public static final String ATTR_TYPE_WORK = "WORK";
+ public static final String ATTR_TYPE_FAX = "FAX";
+ public static final String ATTR_TYPE_CELL = "CELL";
+ public static final String ATTR_TYPE_VOICE = "VOICE";
+ public static final String ATTR_TYPE_INTERNET = "INTERNET";
+
+ public static final String ATTR_TYPE_PREF = "PREF";
+
+ // Phone types valid in vCard and known to ContactsContract, but not so common.
+ public static final String ATTR_TYPE_CAR = "CAR";
+ public static final String ATTR_TYPE_ISDN = "ISDN";
+ public static final String ATTR_TYPE_PAGER = "PAGER";
+
+ // Phone types existing in vCard 2.1 but not known to ContactsContract.
+ // TODO: should make parser make these TYPE_CUSTOM.
+ public static final String ATTR_TYPE_MODEM = "MODEM";
+ public static final String ATTR_TYPE_MSG = "MSG";
+ public static final String ATTR_TYPE_BBS = "BBS";
+ public static final String ATTR_TYPE_VIDEO = "VIDEO";
+
+ // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
+ // These types are encoded to "X-" attributes when composing vCard for now.
+ // Parser passes these even if "X-" is added to the attribute.
+ public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
+ public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
+ // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
+ public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
+ public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
+ public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
+ public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
+ public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
+
+ // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
+ // vCard 3.0.
+ public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
+
+ private Constants() {
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java
index 46725d3..8e8d46a 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/ContactStruct.java
@@ -15,38 +15,58 @@
*/
package android.pim.vcard;
-import android.content.AbstractSyncableContentProvider;
+import android.content.ContentProviderOperation;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.Contacts;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Extensions;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.Photos;
+import android.content.OperationApplicationException;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Miscellaneous;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Map.Entry;
/**
* This class bridges between data structure of Contact app and VCard data.
*/
public class ContactStruct {
- private static final String LOG_TAG = "ContactStruct";
+ private static final String LOG_TAG = "vcard.ContactStruct";
+
+ // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
+ // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
+ private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
+
+ static {
+ sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+ sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+ sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+ sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+ sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+ sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+ sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+ }
/**
* @hide only for testing
@@ -85,11 +105,7 @@ public class ContactStruct {
/**
* @hide only for testing
*/
- static public class ContactMethod {
- // Contacts.KIND_EMAIL, Contacts.KIND_POSTAL
- public final int kind;
- // e.g. Contacts.ContactMethods.TYPE_HOME, Contacts.PhoneColumns.TYPE_HOME
- // If type == Contacts.PhoneColumns.TYPE_CUSTOM, label is used.
+ static public class EmailData {
public final int type;
public final String data;
// Used only when TYPE is TYPE_CUSTOM.
@@ -97,35 +113,143 @@ public class ContactStruct {
// isPrimary is changable only when there's no appropriate one existing in
// the original VCard.
public boolean isPrimary;
- public ContactMethod(int kind, int type, String data, String label,
- boolean isPrimary) {
- this.kind = kind;
+ public EmailData(int type, String data, String label, boolean isPrimary) {
this.type = type;
this.data = data;
- this.label = data;
+ this.label = label;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof EmailData) {
+ return false;
+ }
+ EmailData emailData = (EmailData)obj;
+ return (type == emailData.type && data.equals(emailData.data) &&
+ label.equals(emailData.label) && isPrimary == emailData.isPrimary);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+ type, data, label, isPrimary);
+ }
+ }
+
+ static public class PostalData {
+ // Determined by vCard spec.
+ // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+ public static final int ADDR_MAX_DATA_SIZE = 7;
+ private final String[] dataArray;
+ public final String pobox;
+ public final String extendedAddress;
+ public final String street;
+ public final String localty;
+ public final String region;
+ public final String postalCode;
+ public final String country;
+
+ public final int type;
+
+ // Used only when type variable is TYPE_CUSTOM.
+ public final String label;
+
+ // isPrimary is changable only when there's no appropriate one existing in
+ // the original VCard.
+ public boolean isPrimary;
+ public PostalData(int type, List<String> propValueList,
+ String label, boolean isPrimary) {
+ this.type = type;
+ dataArray = new String[ADDR_MAX_DATA_SIZE];
+
+ int size = propValueList.size();
+ if (size > ADDR_MAX_DATA_SIZE) {
+ size = ADDR_MAX_DATA_SIZE;
+ }
+
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ //
+ // Use Iterator assuming List may be LinkedList, though actually it is
+ // always ArrayList in the current implementation.
+ int i = 0;
+ for (String addressElement : propValueList) {
+ dataArray[i] = addressElement;
+ if (++i >= size) {
+ break;
+ }
+ }
+ while (i < ADDR_MAX_DATA_SIZE) {
+ dataArray[i++] = null;
+ }
+
+ this.pobox = dataArray[0];
+ this.extendedAddress = dataArray[1];
+ this.street = dataArray[2];
+ this.localty = dataArray[3];
+ this.region = dataArray[4];
+ this.postalCode = dataArray[5];
+ this.country = dataArray[6];
+
+ this.label = label;
this.isPrimary = isPrimary;
}
@Override
public boolean equals(Object obj) {
- if (obj instanceof ContactMethod) {
+ if (obj instanceof PostalData) {
return false;
}
- ContactMethod contactMethod = (ContactMethod)obj;
- return (kind == contactMethod.kind && type == contactMethod.type &&
- data.equals(contactMethod.data) && label.equals(contactMethod.label) &&
- isPrimary == contactMethod.isPrimary);
+ PostalData postalData = (PostalData)obj;
+ return (Arrays.equals(dataArray, postalData.dataArray) &&
+ (type == postalData.type &&
+ (type == StructuredPostal.TYPE_CUSTOM ?
+ (label == postalData.label) : true)) &&
+ (isPrimary == postalData.isPrimary));
+ }
+
+ public String getFormattedAddress(int vcardType) {
+ StringBuilder builder = new StringBuilder();
+ boolean empty = true;
+ if (VCardConfig.isJapaneseDevice(vcardType)) {
+ // In Japan, the order is reversed.
+ for (int i = ADDR_MAX_DATA_SIZE - 1; i >= 0; i--) {
+ String addressPart = dataArray[i];
+ if (!TextUtils.isEmpty(addressPart)) {
+ if (!empty) {
+ builder.append(' ');
+ }
+ builder.append(addressPart);
+ empty = false;
+ }
+ }
+ } else {
+ for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
+ String addressPart = dataArray[i];
+ if (!TextUtils.isEmpty(addressPart)) {
+ if (!empty) {
+ builder.append(' ');
+ }
+ builder.append(addressPart);
+ empty = false;
+ }
+ }
+ }
+
+ return builder.toString().trim();
}
@Override
public String toString() {
- return String.format("kind: %d, type: %d, data: %s, label: %s, isPrimary: %s",
- kind, type, data, label, isPrimary);
+ return String.format("type: %d, label: %s, isPrimary: %s",
+ type, label, isPrimary);
}
}
/**
- * @hide only for testing
+ * @hide only for testing.
*/
static public class OrganizationData {
public final int type;
@@ -161,7 +285,54 @@ public class ContactStruct {
}
}
- static class Property {
+ static public class ImData {
+ public final int type;
+ public final String data;
+ public final String label;
+ public final boolean isPrimary;
+
+ // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
+ public ImData(int type, String data, String label, boolean isPrimary) {
+ this.type = type;
+ this.data = data;
+ this.label = label;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ImData) {
+ return false;
+ }
+ ImData imData = (ImData)obj;
+ return (type == imData.type && data.equals(imData.data) &&
+ label.equals(imData.label) && isPrimary == imData.isPrimary);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
+ type, data, label, isPrimary);
+ }
+ }
+
+ /**
+ * @hide only for testing.
+ */
+ static public class PhotoData {
+ public static final String FORMAT_FLASH = "SWF";
+ public final int type;
+ public final String formatName; // used when type is not defined in ContactsContract.
+ public final byte[] photoBytes;
+
+ public PhotoData(int type, String formatName, byte[] photoBytes) {
+ this.type = type;
+ this.formatName = formatName;
+ this.photoBytes = photoBytes;
+ }
+ }
+
+ static /* package */ class Property {
private String mPropertyName;
private Map<String, Collection<String>> mParameterMap =
new HashMap<String, Collection<String>>();
@@ -178,7 +349,7 @@ public class ContactStruct {
public void addParameter(final String paramName, final String paramValue) {
Collection<String> values;
- if (mParameterMap.containsKey(paramName)) {
+ if (!mParameterMap.containsKey(paramName)) {
if (paramName.equals("TYPE")) {
values = new HashSet<String>();
} else {
@@ -188,6 +359,7 @@ public class ContactStruct {
} else {
values = mParameterMap.get(paramName);
}
+ values.add(paramValue);
}
public void addToPropertyValueList(final String propertyValue) {
@@ -213,123 +385,129 @@ public class ContactStruct {
}
}
- private String mName;
- private String mPhoneticName;
- // private String mPhotoType;
- private byte[] mPhotoBytes;
- private List<String> mNotes;
- private List<PhoneData> mPhoneList;
- private List<ContactMethod> mContactMethodList;
- private List<OrganizationData> mOrganizationList;
- private Map<String, List<String>> mExtensionMap;
+ private String mFamilyName;
+ private String mGivenName;
+ private String mMiddleName;
+ private String mPrefix;
+ private String mSuffix;
- private int mNameOrderType;
+ // Used only when no family nor given name is found.
+ private String mFullName;
- /* private variables bellow is for temporary use. */
+ private String mPhoneticFamilyName;
+ private String mPhoneticGivenName;
+ private String mPhoneticMiddleName;
- // For name, there are three fields in vCard: FN, N, NAME.
- // We prefer FN, which is a required field in vCard 3.0 , but not in vCard 2.1.
- // Next, we prefer NAME, which is defined only in vCard 3.0.
- // Finally, we use N, which is a little difficult to parse.
- private String mTmpFullName;
- private String mTmpNameFromNProperty;
+ private String mPhoneticFullName;
+
+ private List<String> mNickNameList;
- // Some vCard has "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", and
- // "X-PHONETIC-LAST-NAME"
- private String mTmpXPhoneticFirstName;
- private String mTmpXPhoneticMiddleName;
- private String mTmpXPhoneticLastName;
+ private String mDisplayName;
+
+ private String mBirthday;
+
+ private List<String> mNoteList;
+ private List<PhoneData> mPhoneList;
+ private List<EmailData> mEmailList;
+ private List<PostalData> mPostalList;
+ private List<OrganizationData> mOrganizationList;
+ private List<ImData> mImList;
+ private List<PhotoData> mPhotoList;
+ private List<String> mWebsiteList;
+
+ private final int mVCardType;
// Each Column of four properties has ISPRIMARY field
// (See android.provider.Contacts)
- // If false even after the following loop, we choose the first
- // entry as a "primary" entry.
+ // 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() {
- mNameOrderType = VCardConfig.NAME_ORDER_TYPE_DEFAULT;
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC);
}
- public ContactStruct(int nameOrderType) {
- mNameOrderType = nameOrderType;
+ public ContactStruct(int vcardType) {
+ mVCardType = vcardType;
}
-
+
/**
- * @hide only for test
+ * @hide only for testing.
*/
- public ContactStruct(String name,
- String phoneticName,
- byte[] photoBytes,
+ 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<ContactMethod> contactMethodList,
+ List<EmailData> emailList,
+ List<PostalData> postalList,
List<OrganizationData> organizationList,
- Map<String, List<String>> extensionMap) {
- mName = name;
- mPhoneticName = phoneticName;
- mPhotoBytes = photoBytes;
- mContactMethodList = contactMethodList;
+ 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;
- mExtensionMap = extensionMap;
+ mPhotoList = photoList;
+ mWebsiteList = websiteList;
}
/**
- * @hide only for test
+ * @hide only for testing.
*/
- public String getName() {
- return mName;
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
}
/**
- * @hide only for test
- */
- public String getPhoneticName() {
- return mPhoneticName;
- }
-
- /**
- * @hide only for test
- */
- public final byte[] getPhotoBytes() {
- return mPhotoBytes;
- }
-
- /**
- * @hide only for test
+ * @hide only for testing.
*/
public final List<String> getNotes() {
- return mNotes;
+ return mNoteList;
}
/**
- * @hide only for test
+ * @hide only for testing.
*/
public final List<PhoneData> getPhoneList() {
return mPhoneList;
}
/**
- * @hide only for test
+ * @hide only for testing.
*/
- public final List<ContactMethod> getContactMethodList() {
- return mContactMethodList;
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
}
/**
- * @hide only for test
+ * @hide only for testing.
*/
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
}
-
+
/**
- * @hide only for test
+ * @hide only for testing.
*/
- public final Map<String, List<String>> getExtensionMap() {
- return mExtensionMap;
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
}
/**
@@ -359,32 +537,55 @@ public class ContactStruct {
mPhoneList.add(phoneData);
}
- /**
- * Add a contactmethod info to contactmethodList.
- * @param kind integer value defined in Contacts.java
- * (e.g. Contacts.KIND_EMAIL)
- * @param type type col of content://contacts/contact_methods
- * @param data contact data
- * @param label extra string used only when kind is Contacts.KIND_CUSTOM.
- */
- private void addContactmethod(int kind, int type, String data,
- String label, boolean isPrimary){
- if (mContactMethodList == null) {
- mContactMethodList = new ArrayList<ContactMethod>();
+ private void addNickName(final String nickName) {
+ if (mNickNameList == null) {
+ mNickNameList = new ArrayList<String>();
}
- mContactMethodList.add(new ContactMethod(kind, type, data, label, isPrimary));
+ mNickNameList.add(nickName);
}
- /**
- * Add a Organization info to organizationList.
- */
- private void addOrganization(int type, String companyName, String positionName,
- boolean isPrimary) {
+ private void addEmail(int type, String data, String label, boolean isPrimary){
+ if (mEmailList == null) {
+ mEmailList = new ArrayList<EmailData>();
+ }
+ mEmailList.add(new EmailData(type, data, label, isPrimary));
+ }
+
+ private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
+ if (mPostalList == null) {
+ mPostalList = new ArrayList<PostalData>();
+ }
+ mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
+ }
+
+ private void addOrganization(int type, final String companyName,
+ final String positionName, boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
}
+
+ private void addIm(int type, String data, String label, boolean isPrimary) {
+ if (mImList == null) {
+ mImList = new ArrayList<ImData>();
+ }
+ mImList.add(new ImData(type, data, label, isPrimary));
+ }
+
+ private void addNote(final String note) {
+ if (mNoteList == null) {
+ mNoteList = new ArrayList<String>(1);
+ }
+ mNoteList.add(note);
+ }
+
+ private void addPhotoBytes(String formatName, byte[] photoBytes) {
+ if (mPhotoList == null) {
+ mPhotoList = new ArrayList<PhotoData>(1);
+ }
+ final PhotoData photoData = new PhotoData(0, null, photoBytes);
+ }
/**
* Set "position" value to the appropriate data. If there's more than one
@@ -407,148 +608,66 @@ public class ContactStruct {
}
int size = mOrganizationList.size();
if (size == 0) {
- addOrganization(Contacts.OrganizationColumns.TYPE_OTHER, "", null, false);
+ addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
+ "", null, false);
size = 1;
}
OrganizationData lastData = mOrganizationList.get(size - 1);
lastData.positionName = positionValue;
}
- private void addExtension(String propName, Map<String, Collection<String>> paramMap,
- List<String> propValueList) {
- if (propValueList.size() == 0) {
+ @SuppressWarnings("fallthrough")
+ private void handleNProperty(List<String> elems) {
+ // Family, Given, Middle, Prefix, Suffix. (1 - 5)
+ int size;
+ if (elems == null || (size = elems.size()) < 1) {
return;
}
- // Now store the string into extensionMap.
- List<String> list;
- if (mExtensionMap == null) {
- mExtensionMap = new HashMap<String, List<String>>();
+ if (size > 5) {
+ size = 5;
}
- if (!mExtensionMap.containsKey(propName)){
- list = new ArrayList<String>();
- mExtensionMap.put(propName, list);
- } else {
- list = mExtensionMap.get(propName);
- }
-
- list.add(encodeProperty(propName, paramMap, propValueList));
- }
- private String encodeProperty(String propName, Map<String, Collection<String>> paramMap,
- List<String> propValueList) {
- // 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("],");
+ switch (size) {
+ // fallthrough
+ case 5:
+ mSuffix = elems.get(4);
+ case 4:
+ mPrefix = elems.get(3);
+ case 3:
+ mMiddleName = elems.get(2);
+ case 2:
+ mGivenName = elems.get(1);
+ default:
+ mFamilyName = elems.get(0);
}
-
- if (paramMap.size() > 0) {
- builder.append("paramMap:[");
- int size = paramMap.size();
- int i = 0;
- for (Map.Entry<String, Collection<String>> entry : paramMap.entrySet()) {
- String key = entry.getKey();
- for (String value : entry.getValue()) {
- // Assuming param-key does not contain NON-ASCII nor symbols.
- // TODO: check it.
- //
- // According to vCard 3.0:
- // param-name = iana-token / x-name
- builder.append(key);
-
- // param-value may contain any value including NON-ASCIIs.
- // We use the following replacing rule.
- // \ -> \\
- // , -> \,
- // In String#replaceAll(), "\\\\" means a single backslash.
- builder.append("=");
-
- // TODO: fix this.
- builder.append(value.replaceAll("\\\\", "\\\\\\\\").replaceAll(",", "\\\\,"));
- if (i < size -1) {
- builder.append(",");
- }
- i++;
- }
- }
-
- builder.append("],");
+ }
+
+ /**
+ * Some Japanese mobile phones use this field for phonetic name,
+ * since vCard 2.1 does not have "SORT-STRING" type.
+ * Also, in some cases, the field has some ';'s in it.
+ * Assume the ';' means the same meaning in N property
+ */
+ @SuppressWarnings("fallthrough")
+ private void handlePhoneticNameFromSound(List<String> elems) {
+ // Family, Given, Middle. (1-3)
+ // This is not from specification but mere assumption. Some Japanese phones use this order.
+ int size;
+ if (elems == null || (size = elems.size()) < 1) {
+ return;
}
-
- int size = propValueList.size();
- if (size > 0) {
- builder.append("propValue:[");
- List<String> list = propValueList;
- for (int i = 0; i < size; i++) {
- // TODO: fix this.
- builder.append(list.get(i).replaceAll("\\\\", "\\\\\\\\").replaceAll(",", "\\\\,"));
- if (i < size -1) {
- builder.append(",");
- }
- }
- builder.append("],");
+ if (size > 3) {
+ size = 3;
}
- return builder.toString();
- }
-
- private static String getNameFromNProperty(List<String> elems, int nameOrderType) {
- // Family, Given, Middle, Prefix, Suffix. (1 - 5)
- int size = elems.size();
- if (size > 1) {
- StringBuilder builder = new StringBuilder();
- boolean builderIsEmpty = true;
- // Prefix
- if (size > 3 && elems.get(3).length() > 0) {
- builder.append(elems.get(3));
- builderIsEmpty = false;
- }
- String first, second;
- if (nameOrderType == VCardConfig.NAME_ORDER_TYPE_JAPANESE) {
- first = elems.get(0);
- second = elems.get(1);
- } else {
- first = elems.get(1);
- second = elems.get(0);
- }
- if (first.length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(first);
- builderIsEmpty = false;
- }
- // Middle name
- if (size > 2 && elems.get(2).length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(elems.get(2));
- builderIsEmpty = false;
- }
- if (second.length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(second);
- builderIsEmpty = false;
- }
- // Suffix
- if (size > 4 && elems.get(4).length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(elems.get(4));
- builderIsEmpty = false;
- }
- return builder.toString();
- } else if (size == 1) {
- return elems.get(0);
- } else {
- return "";
+ switch (size) {
+ // fallthrough
+ case 3:
+ mPhoneticMiddleName = elems.get(2);
+ case 2:
+ mPhoneticGivenName = elems.get(1);
+ default:
+ mPhoneticFamilyName = elems.get(0);
}
}
@@ -561,41 +680,27 @@ public class ContactStruct {
if (propValueList.size() == 0) {
return;
}
-
- String propValue = listToString(propValueList);
-
+ final String propValue = listToString(propValueList).trim();
+
if (propName.equals("VERSION")) {
// vCard version. Ignore this.
} else if (propName.equals("FN")) {
- mTmpFullName = propValue;
- } else if (propName.equals("NAME") && mTmpFullName == null) {
- // Only in vCard 3.0. Use this if FN does not exist.
- // Though, note that vCard 3.0 requires FN.
- mTmpFullName = propValue;
+ mFullName = propValue;
+ } else if (propName.equals("NAME") && mFullName == null) {
+ // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
+ // actually exist in the real vCard data, does not exist.
+ mFullName = propValue;
} else if (propName.equals("N")) {
- mTmpNameFromNProperty = getNameFromNProperty(propValueList, mNameOrderType);
+ handleNProperty(propValueList);
} else if (propName.equals("SORT-STRING")) {
- mPhoneticName = propValue;
+ mPhoneticFullName = propValue;
+ } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+ addNickName(propValue);
} else if (propName.equals("SOUND")) {
- if ("X-IRMC-N".equals(paramMap.get("TYPE")) && mPhoneticName == null) {
- // Some Japanese mobile phones use this field for phonetic name,
- // since vCard 2.1 does not have "SORT-STRING" type.
- // Also, in some cases, the field has some ';'s in it.
- // We remove them.
- StringBuilder builder = new StringBuilder();
- String value = propValue;
- int length = value.length();
- for (int i = 0; i < length; i++) {
- char ch = value.charAt(i);
- if (ch != ';') {
- builder.append(ch);
- }
- }
- if (builder.length() > 0) {
- mPhoneticName = builder.toString();
- }
+ if (Constants.ATTR_TYPE_X_IRMC_N.equals(paramMap.get(Constants.ATTR_TYPE))) {
+ handlePhoneticNameFromSound(propValueList);
} else {
- addExtension(propName, paramMap, propValueList);
+ // Ignore this field since Android cannot understand what it is.
}
} else if (propName.equals("ADR")) {
boolean valuesAreAllEmpty = true;
@@ -609,108 +714,103 @@ public class ContactStruct {
return;
}
- int kind = Contacts.KIND_POSTAL;
int type = -1;
String label = "";
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get("TYPE");
+ Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals("PREF") && !mPrefIsSet_Address) {
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
// Only first "PREF" is considered.
mPrefIsSet_Address = true;
isPrimary = true;
- } else if (typeString.equalsIgnoreCase("HOME")) {
- type = Contacts.ContactMethodsColumns.TYPE_HOME;
+ } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ type = StructuredPostal.TYPE_HOME;
label = "";
- } else if (typeString.equalsIgnoreCase("WORK") ||
+ } else if (typeString.equals(Constants.ATTR_TYPE_WORK) ||
typeString.equalsIgnoreCase("COMPANY")) {
// "COMPANY" seems emitted by Windows Mobile, which is not
// specifically supported by vCard 2.1. We assume this is same
// as "WORK".
- type = Contacts.ContactMethodsColumns.TYPE_WORK;
+ type = StructuredPostal.TYPE_WORK;
label = "";
- } else if (typeString.equalsIgnoreCase("POSTAL")) {
- kind = Contacts.KIND_POSTAL;
- } else if (typeString.equalsIgnoreCase("PARCEL") ||
- typeString.equalsIgnoreCase("DOM") ||
- typeString.equalsIgnoreCase("INTL")) {
- // We do not have a kind or type matching these.
- // TODO: fix this. We may need to split entries into two.
- // (e.g. entries for KIND_POSTAL and KIND_PERCEL)
- } else if (typeString.toUpperCase().startsWith("X-") &&
- type < 0) {
- type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
- label = typeString.substring(2);
- } else if (type < 0) {
+ } else if (typeString.equals("PARCEL") ||
+ typeString.equals("DOM") ||
+ typeString.equals("INTL")) {
+ // We do not have any appropriate way to store this information.
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
+ }
// vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
// emit non-standard types. We do not handle their values now.
- type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ type = StructuredPostal.TYPE_CUSTOM;
label = typeString;
}
}
}
// We use "HOME" as default
if (type < 0) {
- type = Contacts.ContactMethodsColumns.TYPE_HOME;
+ type = StructuredPostal.TYPE_HOME;
}
-
- // adr-value = 0*6(text-value ";") text-value
- // ; PO Box, Extended Address, Street, Locality, Region, Postal
- // ; Code, Country Name
- String address;
- int size = propValueList.size();
- if (size > 1) {
- StringBuilder builder = new StringBuilder();
- boolean builderIsEmpty = true;
- if (Locale.getDefault().getCountry().equals(Locale.JAPAN.getCountry())) {
- // In Japan, the order is reversed.
- for (int i = size - 1; i >= 0; i--) {
- String addressPart = propValueList.get(i);
- if (addressPart.length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(addressPart);
- builderIsEmpty = false;
- }
- }
- } else {
- for (int i = 0; i < size; i++) {
- String addressPart = propValueList.get(i);
- if (addressPart.length() > 0) {
- if (!builderIsEmpty) {
- builder.append(' ');
- }
- builder.append(addressPart);
- builderIsEmpty = false;
+
+ addPostal(type, propValueList, label, isPrimary);
+ } else if (propName.equals("EMAIL")) {
+ int type = -1;
+ String label = null;
+ boolean isPrimary = false;
+ Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ if (typeCollection != null) {
+ for (String typeString : typeCollection) {
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
+ // Only first "PREF" is considered.
+ mPrefIsSet_Email = true;
+ isPrimary = true;
+ } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ type = Email.TYPE_HOME;
+ } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
+ type = Email.TYPE_WORK;
+ } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
+ // We do not have TYPE_MOBILE yet.
+ // TODO: modify this code when TYPE_MOBILE is supported.
+ type = Email.TYPE_CUSTOM;
+ label =
+ android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME;
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
}
+ // vCard 3.0 allows iana-token.
+ // We may have INTERNET (specified in vCard spec),
+ // SCHOOL, etc.
+ type = Email.TYPE_CUSTOM;
+ label = typeString;
}
}
- address = builder.toString().trim();
- } else {
- address = propValue;
}
- addContactmethod(kind, type, address, label, isPrimary);
+ if (type < 0) {
+ type = Email.TYPE_OTHER;
+ }
+ addEmail(type, propValue, label, isPrimary);
} else if (propName.equals("ORG")) {
// vCard specification does not specify other types.
- int type = Contacts.OrganizationColumns.TYPE_WORK;
+ int type = Organization.TYPE_WORK;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get("TYPE");
+ Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals("PREF") && !mPrefIsSet_Organization) {
+ if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
// vCard specification officially does not have PREF in ORG.
// This is just for safety.
mPrefIsSet_Organization = true;
isPrimary = true;
}
- // XXX: Should we cope with X- words?
}
}
- int size = propValueList.size();
StringBuilder builder = new StringBuilder();
for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
builder.append(iter.next());
@@ -718,250 +818,190 @@ public class ContactStruct {
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")) && mPhotoBytes == null)) {
- // We prefer PHOTO to LOGO.
+ } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
+ String formatName = null;
+ Collection<String> typeCollection = paramMap.get("TYPE");
+ if (typeCollection != null) {
+ formatName = typeCollection.iterator().next();
+ }
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
- // TODO: do something.
+ // Currently we do not have appropriate example for testing this case.
} else {
- // Assume PHOTO is stored in BASE64. In that case,
- // data is already stored in propValue_bytes in binary form.
- // It should be automatically done by VBuilder (VDataBuilder/VCardDatabuilder)
- mPhotoBytes = propBytes;
- /*
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- if (typeCollection.size() > 1) {
- StringBuilder builder = new StringBuilder();
- int size = typeCollection.size();
- int i = 0;
- for (String type : typeCollection) {
- builder.append(type);
- if (i < size - 1) {
- builder.append(',');
- }
- i++;
- }
- Log.w(LOG_TAG, "There is more than TYPE: " + builder.toString());
- }
- mPhotoType = typeCollection.iterator().next();
- }*/
+ addPhotoBytes(formatName, propBytes);
}
- } else if (propName.equals("EMAIL")) {
- int type = -1;
- String label = null;
- boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- for (String typeString : typeCollection) {
- if (typeString.equals("PREF") && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
- isPrimary = true;
- } else if (typeString.equalsIgnoreCase("HOME")) {
- type = Contacts.ContactMethodsColumns.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase("WORK")) {
- type = Contacts.ContactMethodsColumns.TYPE_WORK;
- } else if (typeString.equalsIgnoreCase("CELL")) {
- // We do not have Contacts.ContactMethodsColumns.TYPE_MOBILE yet.
- type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
- label = Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME;
- } else if (typeString.toUpperCase().startsWith("X-") &&
- type < 0) {
- type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
- label = typeString.substring(2);
- } else if (type < 0) {
- // vCard 3.0 allows iana-token.
- // We may have INTERNET (specified in vCard spec),
- // SCHOOL, etc.
- type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
- label = typeString;
- }
- }
+ } else if (propName.equals("TEL")) {
+ Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ final int type;
+ final String label;
+ if (typeObject instanceof Integer) {
+ type = (Integer)typeObject;
+ label = null;
+ } else {
+ type = Phone.TYPE_CUSTOM;
+ label = typeObject.toString();
}
- if (type < 0) {
- type = Contacts.ContactMethodsColumns.TYPE_OTHER;
+
+ final boolean isPrimary;
+ if (!mPrefIsSet_Phone && typeCollection != null &&
+ typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
+ mPrefIsSet_Phone = true;
+ isPrimary = true;
+ } else {
+ isPrimary = false;
}
- addContactmethod(Contacts.KIND_EMAIL, type, propValue,label, isPrimary);
- } else if (propName.equals("TEL")) {
- int type = -1;
- String label = null;
+ addPhone(type, propValue, label, isPrimary);
+ } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+ // The phone number available via Skype.
+ Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ // XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization)
+ int type = Phone.TYPE_OTHER;
+ final String label = null;
+ final boolean isPrimary;
+ if (!mPrefIsSet_Phone && typeCollection != null &&
+ typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
+ mPrefIsSet_Phone = true;
+ isPrimary = true;
+ } else {
+ isPrimary = false;
+ }
+ addPhone(type, propValue, label, isPrimary);
+ } else if (sImMap.containsKey(propName)){
+ int type = sImMap.get(propName);
boolean isPrimary = false;
- boolean isFax = false;
- Collection<String> typeCollection = paramMap.get("TYPE");
+ final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals("PREF") && !mPrefIsSet_Phone) {
- // Only first "PREF" is considered.
- mPrefIsSet_Phone = true;
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equalsIgnoreCase("HOME")) {
- type = Contacts.PhonesColumns.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase("WORK")) {
- type = Contacts.PhonesColumns.TYPE_WORK;
- } else if (typeString.equalsIgnoreCase("CELL")) {
- type = Contacts.PhonesColumns.TYPE_MOBILE;
- } else if (typeString.equalsIgnoreCase("PAGER")) {
- type = Contacts.PhonesColumns.TYPE_PAGER;
- } else if (typeString.equalsIgnoreCase("FAX")) {
- isFax = true;
- } else if (typeString.equalsIgnoreCase("VOICE") ||
- typeString.equalsIgnoreCase("MSG")) {
- // Defined in vCard 3.0. Ignore these because they
- // conflict with "HOME", "WORK", etc.
- // XXX: do something?
- } else if (typeString.toUpperCase().startsWith("X-") &&
- type < 0) {
- type = Contacts.PhonesColumns.TYPE_CUSTOM;
- label = typeString.substring(2);
- } else if (type < 0){
- // We may have MODEM, CAR, ISDN, etc...
- type = Contacts.PhonesColumns.TYPE_CUSTOM;
- label = typeString;
+ } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
+ type = Phone.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
+ type = Phone.TYPE_WORK;
}
}
}
if (type < 0) {
- type = Contacts.PhonesColumns.TYPE_HOME;
+ type = Phone.TYPE_HOME;
}
- if (isFax) {
- if (type == Contacts.PhonesColumns.TYPE_HOME) {
- type = Contacts.PhonesColumns.TYPE_FAX_HOME;
- } else if (type == Contacts.PhonesColumns.TYPE_WORK) {
- type = Contacts.PhonesColumns.TYPE_FAX_WORK;
- }
- }
-
- addPhone(type, propValue, label, isPrimary);
+ addIm(type, propValue, null, isPrimary);
} else if (propName.equals("NOTE")) {
- if (mNotes == null) {
- mNotes = new ArrayList<String>(1);
+ addNote(propValue);
+ } else if (propName.equals("URL")) {
+ if (mWebsiteList == null) {
+ mWebsiteList = new ArrayList<String>(1);
}
- mNotes.add(propValue);
+ mWebsiteList.add(propValue);
+ } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
+ mPhoneticGivenName = propValue;
+ } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
+ mPhoneticMiddleName = propValue;
+ } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
+ mPhoneticFamilyName = propValue;
} else if (propName.equals("BDAY")) {
- addExtension(propName, paramMap, propValueList);
- } else if (propName.equals("URL")) {
- addExtension(propName, paramMap, propValueList);
- } else if (propName.equals("REV")) {
+ mBirthday = propValue;
+ /*} else if (propName.equals("REV")) {
// Revision of this VCard entry. I think we can ignore this.
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("UID")) {
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("KEY")) {
// Type is X509 or PGP? I don't know how to handle this...
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("MAILER")) {
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("TZ")) {
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("GEO")) {
- addExtension(propName, paramMap, propValueList);
- } else if (propName.equals("NICKNAME")) {
- // vCard 3.0 only.
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("CLASS")) {
// vCard 3.0 only.
// e.g. CLASS:CONFIDENTIAL
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("PROFILE")) {
// VCard 3.0 only. Must be "VCARD". I think we can ignore this.
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("CATEGORIES")) {
// VCard 3.0 only.
// e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("SOURCE")) {
// VCard 3.0 only.
- addExtension(propName, paramMap, propValueList);
} else if (propName.equals("PRODID")) {
// VCard 3.0 only.
// To specify the identifier for the product that created
- // the vCard object.
- addExtension(propName, paramMap, propValueList);
- } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
- mTmpXPhoneticFirstName = propValue;
- } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
- mTmpXPhoneticMiddleName = propValue;
- } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
- mTmpXPhoneticLastName = propValue;
+ // the vCard object.*/
} else {
// Unknown X- words and IANA token.
- addExtension(propName, paramMap, propValueList);
}
}
- public String displayString() {
- if (mName.length() > 0) {
- return mName;
- }
- if (mContactMethodList != null && mContactMethodList.size() > 0) {
- for (ContactMethod contactMethod : mContactMethodList) {
- if (contactMethod.kind == Contacts.KIND_EMAIL && contactMethod.isPrimary) {
- return contactMethod.data;
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
+
+ /**
+ * Construct the display name. The constructed data must not be null.
+ */
+ private void constructDisplayName() {
+ if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ StringBuilder builder = new StringBuilder();
+ List<String> nameList;
+ switch (VCardConfig.getNameOrderType(mVCardType)) {
+ case VCardConfig.NAME_ORDER_JAPANESE:
+ if (VCardUtils.containsOnlyAscii(mFamilyName) &&
+ VCardUtils.containsOnlyAscii(mGivenName)) {
+ nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
+ } else {
+ nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix);
}
+ break;
+ case VCardConfig.NAME_ORDER_EUROPE:
+ nameList = Arrays.asList(mPrefix, mMiddleName, mGivenName, mFamilyName, mSuffix);
+ break;
+ default:
+ nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
+ break;
}
- }
- if (mPhoneList != null && mPhoneList.size() > 0) {
- for (PhoneData phoneData : mPhoneList) {
- if (phoneData.isPrimary) {
- return phoneData.data;
+ boolean first = true;
+ for (String namePart : nameList) {
+ if (!TextUtils.isEmpty(namePart)) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(' ');
+ }
+ builder.append(namePart);
}
}
+ mDisplayName = builder.toString();
+ } else if (!TextUtils.isEmpty(mFullName)) {
+ mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+ mPhoneticFamilyName, mPhoneticMiddleName, mPhoneticGivenName);
+ } else if (mEmailList != null && mEmailList.size() > 0) {
+ mDisplayName = mEmailList.get(0).data;
+ } else if (mPhoneList != null && mPhoneList.size() > 0) {
+ mDisplayName = mPhoneList.get(0).data;
+ } else if (mPostalList != null && mPostalList.size() > 0) {
+ mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
}
- return "";
- }
+ if (mDisplayName == null) {
+ mDisplayName = "";
+ }
+ }
+
/**
* Consolidate several fielsds (like mName) using name candidates,
*/
public void consolidateFields() {
- if (mTmpFullName != null) {
- mName = mTmpFullName;
- } else if(mTmpNameFromNProperty != null) {
- mName = mTmpNameFromNProperty;
- } else {
- mName = "";
- }
-
- if (mPhoneticName == null &&
- (mTmpXPhoneticFirstName != null || mTmpXPhoneticMiddleName != null ||
- mTmpXPhoneticLastName != null)) {
- // Note: In Europe, this order should be "LAST FIRST MIDDLE". See the comment around
- // NAME_ORDER_TYPE_* for more detail.
- String first;
- String second;
- if (mNameOrderType == VCardConfig.NAME_ORDER_TYPE_JAPANESE) {
- first = mTmpXPhoneticLastName;
- second = mTmpXPhoneticFirstName;
- } else {
- first = mTmpXPhoneticFirstName;
- second = mTmpXPhoneticLastName;
- }
- StringBuilder builder = new StringBuilder();
- if (first != null) {
- builder.append(first);
- }
- if (mTmpXPhoneticMiddleName != null) {
- builder.append(mTmpXPhoneticMiddleName);
- }
- if (second != null) {
- builder.append(second);
- }
- mPhoneticName = builder.toString();
- }
+ constructDisplayName();
- // Remove unnecessary white spaces.
- // It is found that some mobile phone emits phonetic name with just one white space
- // when a user does not specify one.
- // This logic is effective toward such kind of weird data.
- if (mPhoneticName != null) {
- mPhoneticName = mPhoneticName.trim();
+ if (mPhoneticFullName != null) {
+ mPhoneticFullName = mPhoneticFullName.trim();
}
// If there is no "PREF", we choose the first entries as primary.
@@ -969,258 +1009,196 @@ public class ContactStruct {
mPhoneList.get(0).isPrimary = true;
}
- if (!mPrefIsSet_Address && mContactMethodList != null) {
- for (ContactMethod contactMethod : mContactMethodList) {
- if (contactMethod.kind == Contacts.KIND_POSTAL) {
- contactMethod.isPrimary = true;
- break;
- }
- }
+ if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
+ mPostalList.get(0).isPrimary = true;
}
- if (!mPrefIsSet_Email && mContactMethodList != null) {
- for (ContactMethod contactMethod : mContactMethodList) {
- if (contactMethod.kind == Contacts.KIND_EMAIL) {
- contactMethod.isPrimary = true;
- break;
- }
- }
+ 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;
}
-
}
- private void pushIntoContentProviderOrResolver(Object contentSomething,
- long myContactsGroupId) {
- ContentResolver resolver = null;
- AbstractSyncableContentProvider provider = null;
- if (contentSomething instanceof ContentResolver) {
- resolver = (ContentResolver)contentSomething;
- } else if (contentSomething instanceof AbstractSyncableContentProvider) {
- provider = (AbstractSyncableContentProvider)contentSomething;
- } else {
- Log.e(LOG_TAG, "Unsupported object came.");
- return;
- }
-
- ContentValues contentValues = new ContentValues();
- contentValues.put(People.NAME, mName);
- contentValues.put(People.PHONETIC_NAME, mPhoneticName);
-
- if (mNotes != null && mNotes.size() > 0) {
- if (mNotes.size() > 1) {
- StringBuilder builder = new StringBuilder();
- for (String note : mNotes) {
- builder.append(note);
- builder.append("\n");
- }
- contentValues.put(People.NOTES, builder.toString());
- } else {
- contentValues.put(People.NOTES, mNotes.get(0));
- }
- }
+ public void pushIntoContentResolver(ContentResolver resolver) {
+ ArrayList<ContentProviderOperation> operationList =
+ new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder builder =
+ ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
+ builder.withValues(new ContentValues());
+ operationList.add(builder.build());
- Uri personUri;
- long personId = 0;
- if (resolver != null) {
- personUri = Contacts.People.createPersonInMyContactsGroup(resolver, contentValues);
- if (personUri != null) {
- personId = ContentUris.parseId(personUri);
- }
- } else {
- personUri = provider.insert(People.CONTENT_URI, contentValues);
- if (personUri != null) {
- personId = ContentUris.parseId(personUri);
- ContentValues values = new ContentValues();
- values.put(GroupMembership.PERSON_ID, personId);
- values.put(GroupMembership.GROUP_ID, myContactsGroupId);
- Uri resultUri = provider.insert(GroupMembership.CONTENT_URI, values);
- if (resultUri == null) {
- Log.e(LOG_TAG, "Faild to insert the person to MyContact.");
- provider.delete(personUri, null, null);
- personUri = null;
- }
- }
- }
+ {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
- if (personUri == null) {
- Log.e(LOG_TAG, "Failed to create the contact.");
- return;
+ builder.withValue(StructuredName.GIVEN_NAME, mGivenName);
+ builder.withValue(StructuredName.FAMILY_NAME, mFamilyName);
+ builder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);
+ builder.withValue(StructuredName.PREFIX, mPrefix);
+ builder.withValue(StructuredName.SUFFIX, mSuffix);
+
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
+ builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
+ builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+
+ builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
+ operationList.add(builder.build());
}
-
- if (mPhotoBytes != null) {
- if (resolver != null) {
- People.setPhotoData(resolver, personUri, mPhotoBytes);
- } else {
- Uri photoUri = Uri.withAppendedPath(personUri, Contacts.Photos.CONTENT_DIRECTORY);
- ContentValues values = new ContentValues();
- values.put(Photos.DATA, mPhotoBytes);
- provider.update(photoUri, values, null, null);
+
+ 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());
}
}
- long primaryPhoneId = -1;
- if (mPhoneList != null && mPhoneList.size() > 0) {
+ if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
- ContentValues values = new ContentValues();
- values.put(Contacts.PhonesColumns.TYPE, phoneData.type);
- if (phoneData.type == Contacts.PhonesColumns.TYPE_CUSTOM) {
- values.put(Contacts.PhonesColumns.LABEL, phoneData.label);
- }
- // Already formatted.
- values.put(Contacts.PhonesColumns.NUMBER, phoneData.data);
-
- // Not sure about Contacts.PhonesColumns.NUMBER_KEY ...
- values.put(Contacts.PhonesColumns.ISPRIMARY, 1);
- values.put(Contacts.Phones.PERSON_ID, personId);
- Uri phoneUri;
- if (resolver != null) {
- phoneUri = resolver.insert(Phones.CONTENT_URI, values);
- } else {
- phoneUri = provider.insert(Phones.CONTENT_URI, values);
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Phone.TYPE, phoneData.type);
+ if (phoneData.type == Phone.TYPE_CUSTOM) {
+ builder.withValue(Phone.LABEL, phoneData.label);
}
+ builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- primaryPhoneId = Long.parseLong(phoneUri.getLastPathSegment());
+ builder.withValue(Data.IS_PRIMARY, 1);
}
+ operationList.add(builder.build());
}
}
- long primaryOrganizationId = -1;
- if (mOrganizationList != null && mOrganizationList.size() > 0) {
+ if (mOrganizationList != null) {
+ boolean first = true;
for (OrganizationData organizationData : mOrganizationList) {
- ContentValues values = new ContentValues();
+ 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.
- values.put(Contacts.OrganizationColumns.TYPE,
- organizationData.type);
- values.put(Contacts.OrganizationColumns.COMPANY,
- organizationData.companyName);
- values.put(Contacts.OrganizationColumns.TITLE,
- organizationData.positionName);
- values.put(Contacts.OrganizationColumns.ISPRIMARY, 1);
- values.put(Contacts.OrganizationColumns.PERSON_ID, personId);
-
- Uri organizationUri;
- if (resolver != null) {
- organizationUri = resolver.insert(Organizations.CONTENT_URI, values);
- } else {
- organizationUri = provider.insert(Organizations.CONTENT_URI, values);
- }
- if (organizationData.isPrimary) {
- primaryOrganizationId = Long.parseLong(organizationUri.getLastPathSegment());
+ builder.withValue(Organization.TYPE, organizationData.type);
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ builder.withValue(Organization.TITLE, organizationData.positionName);
+ if (first) {
+ builder.withValue(Data.IS_PRIMARY, 1);
}
+ operationList.add(builder.build());
}
}
- long primaryEmailId = -1;
- if (mContactMethodList != null && mContactMethodList.size() > 0) {
- for (ContactMethod contactMethod : mContactMethodList) {
- ContentValues values = new ContentValues();
- values.put(Contacts.ContactMethodsColumns.KIND, contactMethod.kind);
- values.put(Contacts.ContactMethodsColumns.TYPE, contactMethod.type);
- if (contactMethod.type == Contacts.ContactMethodsColumns.TYPE_CUSTOM) {
- values.put(Contacts.ContactMethodsColumns.LABEL, contactMethod.label);
+ if (mEmailList != null) {
+ for (EmailData emailData : mEmailList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Email.TYPE, emailData.type);
+ if (emailData.type == Email.TYPE_CUSTOM) {
+ builder.withValue(Email.LABEL, emailData.label);
}
- values.put(Contacts.ContactMethodsColumns.DATA, contactMethod.data);
- values.put(Contacts.ContactMethodsColumns.ISPRIMARY, 1);
- values.put(Contacts.ContactMethods.PERSON_ID, personId);
-
- if (contactMethod.kind == Contacts.KIND_EMAIL) {
- Uri emailUri;
- if (resolver != null) {
- emailUri = resolver.insert(ContactMethods.CONTENT_URI, values);
- } else {
- emailUri = provider.insert(ContactMethods.CONTENT_URI, values);
- }
- if (contactMethod.isPrimary) {
- primaryEmailId = Long.parseLong(emailUri.getLastPathSegment());
- }
- } else { // probably KIND_POSTAL
- if (resolver != null) {
- resolver.insert(ContactMethods.CONTENT_URI, values);
- } else {
- provider.insert(ContactMethods.CONTENT_URI, values);
- }
+ builder.withValue(Email.DATA, emailData.data);
+ if (emailData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
}
+ operationList.add(builder.build());
}
}
- if (mExtensionMap != null && mExtensionMap.size() > 0) {
- ArrayList<ContentValues> contentValuesArray;
- if (resolver != null) {
- contentValuesArray = new ArrayList<ContentValues>();
- } else {
- contentValuesArray = null;
+ if (mPostalList != null) {
+ for (PostalData postalData : mPostalList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ VCardUtils.insertStructuredPostalDataUsingContactsStruct(
+ mVCardType, builder, postalData);
+ operationList.add(builder.build());
}
- for (Entry<String, List<String>> entry : mExtensionMap.entrySet()) {
- String key = entry.getKey();
- List<String> list = entry.getValue();
- for (String value : list) {
- ContentValues values = new ContentValues();
- values.put(Extensions.NAME, key);
- values.put(Extensions.VALUE, value);
- values.put(Extensions.PERSON_ID, personId);
- if (resolver != null) {
- contentValuesArray.add(values);
- } else {
- provider.insert(Extensions.CONTENT_URI, values);
- }
+ }
+
+ if (mImList != null) {
+ for (ImData imData : mImList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Im.TYPE, imData.type);
+ if (imData.type == Im.TYPE_CUSTOM) {
+ builder.withValue(Im.LABEL, imData.label);
+ }
+ builder.withValue(Im.DATA, imData.data);
+ if (imData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
}
- }
- if (resolver != null) {
- resolver.bulkInsert(Extensions.CONTENT_URI,
- contentValuesArray.toArray(new ContentValues[0]));
}
}
- if (primaryPhoneId >= 0 || primaryOrganizationId >= 0 || primaryEmailId >= 0) {
- ContentValues values = new ContentValues();
- if (primaryPhoneId >= 0) {
- values.put(People.PRIMARY_PHONE_ID, primaryPhoneId);
- }
- if (primaryOrganizationId >= 0) {
- values.put(People.PRIMARY_ORGANIZATION_ID, primaryOrganizationId);
+ if (mNoteList != null) {
+ for (String note : mNoteList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
+
+ builder.withValue(Note.NOTE, note);
+ operationList.add(builder.build());
}
- if (primaryEmailId >= 0) {
- values.put(People.PRIMARY_EMAIL_ID, primaryEmailId);
+ }
+
+ if (mPhotoList != null) {
+ boolean first = true;
+ for (PhotoData photoData : mPhotoList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+ builder.withValue(Photo.PHOTO, photoData.photoBytes);
+ if (first) {
+ builder.withValue(Data.IS_PRIMARY, 1);
+ first = false;
+ }
+ operationList.add(builder.build());
}
- if (resolver != null) {
- resolver.update(personUri, values, null, null);
- } else {
- provider.update(personUri, values, null, null);
+ }
+
+ if (mWebsiteList != null) {
+ for (String website : mWebsiteList) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Website.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
+ builder.withValue(Website.URL, website);
+ operationList.add(builder.build());
}
}
- }
-
- /**
- * Push this object into database in the resolver.
- */
- public void pushIntoContentResolver(ContentResolver resolver) {
- pushIntoContentProviderOrResolver(resolver, 0);
- }
-
- /**
- * Push this object into AbstractSyncableContentProvider object.
- * {@link #consolidateFields() must be called before this method is called}
- * @hide
- */
- public void pushIntoAbstractSyncableContentProvider(
- AbstractSyncableContentProvider provider, long myContactsGroupId) {
- boolean successful = false;
- provider.beginBatch();
+
+ if (!TextUtils.isEmpty(mBirthday)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Miscellaneous.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Miscellaneous.CONTENT_ITEM_TYPE);
+ builder.withValue(Miscellaneous.BIRTHDAY, mBirthday);
+ operationList.add(builder.build());
+ }
+
try {
- pushIntoContentProviderOrResolver(provider, myContactsGroupId);
- successful = true;
- } finally {
- provider.endBatch(successful);
+ resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ } catch (OperationApplicationException e) {
+ Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
}
-
+
public boolean isIgnorable() {
- return TextUtils.isEmpty(mName) &&
- TextUtils.isEmpty(mPhoneticName) &&
- (mPhoneList == null || mPhoneList.size() == 0) &&
- (mContactMethodList == null || mContactMethodList.size() == 0);
+ return getDisplayName().length() == 0;
}
private String listToString(List<String> list){
diff --git a/core/java/android/pim/vcard/EntryCommitter.java b/core/java/android/pim/vcard/EntryCommitter.java
index e26fac5..3f1655d 100644
--- a/core/java/android/pim/vcard/EntryCommitter.java
+++ b/core/java/android/pim/vcard/EntryCommitter.java
@@ -15,11 +15,7 @@
*/
package android.pim.vcard;
-import android.content.AbstractSyncableContentProvider;
-import android.content.ContentProvider;
import android.content.ContentResolver;
-import android.content.IContentProvider;
-import android.provider.Contacts;
import android.util.Log;
/**
@@ -27,62 +23,26 @@ import android.util.Log;
*/
public class EntryCommitter implements EntryHandler {
public static String LOG_TAG = "vcard.EntryComitter";
-
+
private ContentResolver mContentResolver;
-
- // Ideally, this should be ContactsProvider but it seems Class loader cannot find it,
- // even when it is subclass of ContactsProvider...
- private AbstractSyncableContentProvider mProvider;
- private long mMyContactsGroupId;
-
private long mTimeToCommit;
public EntryCommitter(ContentResolver resolver) {
mContentResolver = resolver;
-
- tryGetOriginalProvider();
+ }
+
+ public void onParsingStart() {
}
- public void onFinal() {
+ public void onParsingEnd() {
if (VCardConfig.showPerformanceLog()) {
- Log.d(LOG_TAG,
- String.format("time to commit entries: %ld ms", mTimeToCommit));
+ Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
}
}
-
- private void tryGetOriginalProvider() {
- final ContentResolver resolver = mContentResolver;
-
- if ((mMyContactsGroupId = Contacts.People.tryGetMyContactsGroupId(resolver)) == 0) {
- Log.e(LOG_TAG, "Could not get group id of MyContact");
- return;
- }
- IContentProvider iProviderForName = resolver.acquireProvider(Contacts.CONTENT_URI);
- ContentProvider contentProvider =
- ContentProvider.coerceToLocalContentProvider(iProviderForName);
- if (contentProvider == null) {
- Log.e(LOG_TAG, "Fail to get ContentProvider object.");
- return;
- }
-
- if (!(contentProvider instanceof AbstractSyncableContentProvider)) {
- Log.e(LOG_TAG,
- "Acquired ContentProvider object is not AbstractSyncableContentProvider.");
- return;
- }
-
- mProvider = (AbstractSyncableContentProvider)contentProvider;
- }
-
public void onEntryCreated(final ContactStruct contactStruct) {
long start = System.currentTimeMillis();
- if (mProvider != null) {
- contactStruct.pushIntoAbstractSyncableContentProvider(
- mProvider, mMyContactsGroupId);
- } else {
- contactStruct.pushIntoContentResolver(mContentResolver);
- }
+ contactStruct.pushIntoContentResolver(mContentResolver);
mTimeToCommit += System.currentTimeMillis() - start;
}
} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/EntryHandler.java b/core/java/android/pim/vcard/EntryHandler.java
index 4015cb5..7fb8114 100644
--- a/core/java/android/pim/vcard/EntryHandler.java
+++ b/core/java/android/pim/vcard/EntryHandler.java
@@ -16,18 +16,23 @@
package android.pim.vcard;
/**
- * Unlike VCardBuilderBase, this (and VCardDataBuilder) assumes
+ * Unlike {@link VCardBuilder}, this (and {@link VCardDataBuilder}) assumes
* "each VCard entry should be correctly parsed and passed to each EntryHandler object",
*/
public interface EntryHandler {
/**
- * Able to be use this method for showing performance log, etc.
- * TODO: better name?
+ * Called when the parsing started.
*/
- public void onFinal();
+ public void onParsingStart();
/**
* The method called when one VCard entry is successfully created
*/
public void onEntryCreated(final ContactStruct entry);
+
+ /**
+ * Called when the parsing ended.
+ * Able to be use this method for showing performance log, etc.
+ */
+ public void onParsingEnd();
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
new file mode 100644
index 0000000..283d00b
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -0,0 +1,1433 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.pim.vcard;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.Entity.NamedContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.os.RemoteException;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Miscellaneous;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * The class for composing VCard from Contacts information. Note that this is
+ * completely differnt implementation from
+ * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * </p>
+ *
+ * <p>
+ * 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>
+ */
+public class VCardComposer {
+ private static final String LOG_TAG = "vcard.VCardComposer";
+
+ 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.
+ * </p>
+ * <p>
+ * The input OutputStream object is closed() on {{@link #onTerminate()}.
+ * Must not close the stream outside.
+ * </p>
+ */
+ public class HandlerForOutputStream implements OneEntryHandler {
+ @SuppressWarnings("hiding")
+ private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
+
+ private OutputStream mOutputStream; // mWriter will close this.
+ private Writer mWriter;
+
+ private boolean mFinishIsCalled = false;
+
+ /**
+ * Input stream will be closed on the detruction of this object.
+ */
+ public HandlerForOutputStream(OutputStream outputStream) {
+ mOutputStream = outputStream;
+ }
+
+ public boolean onInit(Context context) {
+ try {
+ mWriter = new BufferedWriter(new OutputStreamWriter(
+ mOutputStream, mCharsetString));
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
+ mErrorReason = "Encoding is not supported (usually this does not happen!): "
+ + mCharsetString;
+ return false;
+ }
+
+ if (mIsDoCoMo) {
+ try {
+ // Create one empty entry.
+ mWriter.write(createOneEntryInternal("-1"));
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IOException occurred during exportOneContactData: "
+ + e.getMessage());
+ mErrorReason = "IOException occurred: " + e.getMessage();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean onEntryCreated(String vcard) {
+ try {
+ mWriter.write(vcard);
+ } catch (IOException e) {
+ Log.e(LOG_TAG,
+ "IOException occurred during exportOneContactData: "
+ + e.getMessage());
+ mErrorReason = "IOException occurred: " + e.getMessage();
+ return false;
+ }
+ return true;
+ }
+
+ public void onTerminate() {
+ if (mWriter != null) {
+ try {
+ // Flush and sync the data so that a user is able to pull
+ // the SDCard just after
+ // the export.
+ mWriter.flush();
+ if (mOutputStream != null
+ && mOutputStream instanceof FileOutputStream) {
+ ((FileOutputStream) mOutputStream).getFD().sync();
+ }
+ } catch (IOException e) {
+ Log.d(LOG_TAG,
+ "IOException during closing the output stream: "
+ + e.getMessage());
+ } finally {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ if (!mFinishIsCalled) {
+ onTerminate();
+ }
+ }
+ }
+
+ 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
+ private static final String VCARD_PROPERTY_X_PHONETIC_NAME = "X-PHONETIC-NAME";
+ private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
+ // TODO: add properties like X-LATITUDE
+
+ // 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 = " ";
+
+ // Type strings are now in VCardConstants.java.
+
+ private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
+
+ private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+ private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+
+ private final Context mContext;
+ private final int mVCardType;
+ private final boolean mCareHandlerErrors;
+ private final ContentResolver mContentResolver;
+
+ // Convenient member variables about the restriction of the vCard format.
+ // Used for not calling the same methods returning same results.
+ private final boolean mIsV30;
+ private final boolean mIsJapaneseMobilePhone;
+ private final boolean mOnlyOneNoteFieldIsAvailable;
+ private final boolean mIsDoCoMo;
+ private final boolean mUsesQuotedPrintable;
+ private final boolean mUsesAndroidProperty;
+ private final boolean mUsesDefactProperty;
+ private final boolean mUsesShiftJis;
+
+ private Cursor mCursor;
+ private int mIdColumn;
+
+ private String mCharsetString;
+ private static String mVCardAttributeCharset;
+ private boolean mTerminateIsCalled;
+ private List<OneEntryHandler> mHandlerList;
+
+ private String mErrorReason = "No error";
+
+ private static final Map<Integer, String> sImMap;
+
+ static {
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
+ // Google talk is a special case.
+ }
+
+
+ public VCardComposer(Context context) {
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
+ }
+
+ public VCardComposer(Context context, String vcardTypeStr,
+ boolean careHandlerErrors) {
+ this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
+ careHandlerErrors);
+ }
+
+ public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
+ mContext = context;
+ mVCardType = vcardType;
+ mCareHandlerErrors = careHandlerErrors;
+ mContentResolver = context.getContentResolver();
+
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig
+ .needsToConvertPhoneticString(vcardType);
+ mOnlyOneNoteFieldIsAvailable = VCardConfig
+ .onlyOneNoteFieldIsAvailable(vcardType);
+ mUsesAndroidProperty = VCardConfig
+ .usesAndroidSpecificProperty(vcardType);
+ mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
+ mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
+
+ if (mIsDoCoMo) {
+ mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
+ // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
+ // Android, not shown to the public).
+ mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ } else if (mUsesShiftJis) {
+ mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ } else {
+ mCharsetString = "UTF-8";
+ mVCardAttributeCharset = "CHARSET=UTF-8";
+ }
+ }
+
+ /**
+ * Must call before {{@link #init()}.
+ */
+ public void addHandler(OneEntryHandler handler) {
+ if (mHandlerList == null) {
+ mHandlerList = new ArrayList<OneEntryHandler>();
+ }
+ 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(final String selection, final String[] selectionArgs) {
+ if (mCareHandlerErrors) {
+ List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ mHandlerList.size());
+ for (OneEntryHandler handler : mHandlerList) {
+ if (!handler.onInit(mContext)) {
+ for (OneEntryHandler finished : finishedList) {
+ finished.onTerminate();
+ }
+ return false;
+ }
+ }
+ } else {
+ // Just ignore the false returned from onInit().
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onInit(mContext);
+ }
+ }
+
+ final String[] projection = new String[] {Contacts._ID,};
+
+ // TODO: thorow an appropriate exception!
+ mCursor = mContentResolver.query(RawContacts.CONTENT_URI, projection,
+ selection, selectionArgs, null);
+ if (mCursor == null || !mCursor.moveToFirst()) {
+ if (mCursor != null) {
+ try {
+ mCursor.close();
+ } catch (SQLiteException e) {
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
+ + e.getMessage());
+ }
+ mCursor = null;
+ }
+ mErrorReason = "Getting database information failed.";
+ return false;
+ }
+
+ mIdColumn = mCursor.getColumnIndex(Contacts._ID);
+
+ return true;
+ }
+
+ public boolean createOneEntry() {
+ if (mCursor == null || mCursor.isAfterLast()) {
+ // TODO: ditto
+ mErrorReason = "Not initialized or database has some problem.";
+ return false;
+ }
+ String name = null;
+ String vcard;
+ try {
+ vcard = createOneEntryInternal(mCursor.getString(mIdColumn));
+ } catch (OutOfMemoryError error) {
+ // Maybe some data (e.g. photo) is too big to have in memory. But it
+ // should be rare.
+ Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: "
+ + name);
+ System.gc();
+ // TODO: should tell users what happened?
+ return true;
+ } finally {
+ mCursor.moveToNext();
+ }
+
+ // This function does not care the OutOfMemoryError on the handler side
+ // :-P
+ if (mCareHandlerErrors) {
+ List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ mHandlerList.size());
+ for (OneEntryHandler handler : mHandlerList) {
+ if (!handler.onEntryCreated(vcard)) {
+ return false;
+ }
+ }
+ } else {
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onEntryCreated(vcard);
+ }
+ }
+
+ return true;
+ }
+
+ private String createOneEntryInternal(final String contactId) {
+ 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);
+ }
+
+ final Map<String, List<ContentValues>> contentValuesListMap =
+ new HashMap<String, List<ContentValues>>();
+
+ final String selection = Data.RAW_CONTACT_ID + "=?";
+ final String[] selectionArgs = new String[] {contactId};
+ EntityIterator entityIterator = null;
+ try {
+ entityIterator = mContentResolver.queryEntities(
+ RawContacts.CONTENT_URI, selection, selectionArgs, null);
+ while (entityIterator.hasNext()) {
+ Entity entity = entityIterator.next();
+ for (NamedContentValues namedContentValues : entity
+ .getSubValues()) {
+ ContentValues contentValues = namedContentValues.values;
+ String key = contentValues.getAsString(Data.MIMETYPE);
+ if (key != null) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(key);
+ if (contentValuesList == null) {
+ contentValuesList = new ArrayList<ContentValues>();
+ contentValuesListMap.put(key, contentValuesList);
+ }
+ contentValuesList.add(contentValues);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, String.format("RemoteException at id %s (%s)",
+ contactId, e.getMessage()));
+ return "";
+ } finally {
+ if (entityIterator != null) {
+ entityIterator.close();
+ }
+ }
+
+ // TODO: consolidate order? (low priority)
+ appendStructuredNames(builder, contentValuesListMap);
+ appendNickNames(builder, contentValuesListMap);
+ appendPhones(builder, contentValuesListMap);
+ appendEmails(builder, contentValuesListMap);
+ appendPostals(builder, contentValuesListMap);
+ appendIms(builder, contentValuesListMap);
+ appendWebsites(builder, contentValuesListMap);
+ appendBirthday(builder, contentValuesListMap);
+ appendOrganizations(builder, contentValuesListMap);
+ appendPhotos(builder, contentValuesListMap);
+ appendNotes(builder, contentValuesListMap);
+ // TODO: GroupMembership... What?
+
+ if (mIsDoCoMo) {
+ appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
+ appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
+ appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
+ }
+
+ appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
+
+ return builder.toString();
+ }
+
+ public void terminate() {
+ for (OneEntryHandler handler : mHandlerList) {
+ handler.onTerminate();
+ }
+
+ if (mCursor != null) {
+ try {
+ mCursor.close();
+ } catch (SQLiteException e) {
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
+ + e.getMessage());
+ }
+ mCursor = null;
+ }
+
+ mTerminateIsCalled = true;
+ }
+
+ @Override
+ public void finalize() {
+ if (!mTerminateIsCalled) {
+ terminate();
+ }
+ }
+
+ public int getCount() {
+ if (mCursor == null) {
+ return 0;
+ }
+ return mCursor.getCount();
+ }
+
+ public boolean isAfterLast() {
+ if (mCursor == null) {
+ return false;
+ }
+ return mCursor.isAfterLast();
+ }
+
+ /**
+ * @return Return the error reason if possible.
+ */
+ public String getErrorReason() {
+ return mErrorReason;
+ }
+
+ private void appendStructuredNames(StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(StructuredName.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ appendStructuredNamesInternal(builder, contentValuesList);
+ } else if (mIsDoCoMo) {
+ appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ }
+ }
+
+ private void appendStructuredNamesInternal(final StringBuilder builder,
+ final List<ContentValues> contentValuesList) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String familyName = contentValues
+ .getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues
+ .getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues
+ .getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues
+ .getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues
+ .getAsString(StructuredName.SUFFIX);
+ final String displayName = contentValues
+ .getAsString(StructuredName.DISPLAY_NAME);
+
+ // For now, some primary element is not encoded into Quoted-Printable, which is not
+ // valid in vCard spec strictly. In the future, we may have to have some flag to
+ // enable composer to encode these primary field into Quoted-Printable.
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final String encodedFamily = escapeCharacters(familyName);
+ final String encodedGiven = escapeCharacters(givenName);
+ final String encodedMiddle = escapeCharacters(middleName);
+ final String encodedPrefix = escapeCharacters(prefix);
+ final String encodedSuffix = escapeCharacters(suffix);
+
+ // N property. This order is specified by vCard spec and does not depend on countries.
+ builder.append(VCARD_PROPERTY_NAME);
+ if (!(VCardUtils.containsOnlyAscii(familyName) &&
+ VCardUtils.containsOnlyAscii(givenName) &&
+ VCardUtils.containsOnlyAscii(middleName) &&
+ VCardUtils.containsOnlyAscii(prefix) &&
+ VCardUtils.containsOnlyAscii(suffix))) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedFamily);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedGiven);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedMiddle);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedPrefix);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(encodedSuffix);
+ builder.append(VCARD_COL_SEPARATOR);
+
+ final String encodedFullname = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
+
+ // FN property
+ builder.append(VCARD_PROPERTY_FULL_NAME);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ if (!VCardUtils.containsOnlyAscii(encodedFullname)) {
+ builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_DATA_SEPARATOR);
+ }
+ builder.append(encodedFullname);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ builder.append(VCARD_PROPERTY_NAME);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(escapeCharacters(displayName));
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else if (mIsDoCoMo) {
+ appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
+ }
+
+ String phoneticFamilyName = contentValues
+ .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ String phoneticMiddleName = contentValues
+ .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ String phoneticGivenName = contentValues
+ .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (!(TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName) && TextUtils
+ .isEmpty(phoneticGivenName))) { // if not empty
+ if (mIsJapaneseMobilePhone) {
+ phoneticFamilyName = VCardUtils
+ .toHalfWidthString(phoneticFamilyName);
+ phoneticMiddleName = VCardUtils
+ .toHalfWidthString(phoneticMiddleName);
+ phoneticGivenName = VCardUtils
+ .toHalfWidthString(phoneticGivenName);
+ }
+
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName,
+ phoneticGivenName);
+ builder.append(VCARD_PROPERTY_SORT_STRING);
+
+ if (!VCardUtils.containsOnlyAscii(sortString)) {
+ // Strictly, adding charset information is NOT valid in
+ // VCard 3.0,
+ // but we'll add this info since parser side may be able to
+ // use the charset via
+ // this attribute field.
+ //
+ // e.g. Japanese mobile phones use Shift_Jis while RFC 2426
+ // recommends
+ // UTF-8. By adding this field, parsers may be able to know
+ // this text
+ // is NOT UTF-8 but Shift_Jis.
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(sortString);
+ builder.append(VCARD_COL_SEPARATOR);
+ } else {
+ // Note: There is no appropriate property for expressing
+ // phonetic name in
+ // VCard 2.1, while there is in VCard 3.0 (SORT-STRING).
+ // We chose to use DoCoMo's way since it is supported by a
+ // lot of
+ // Japanese mobile phones.
+ //
+ // TODO: should use Quoted-Pritable?
+ builder.append(VCARD_PROPERTY_SOUND);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ builder.append(VCARD_ATTR_SEPARATOR);
+
+ if (!(VCardUtils.containsOnlyAscii(phoneticFamilyName) &&
+ VCardUtils.containsOnlyAscii(phoneticMiddleName) &&
+ VCardUtils.containsOnlyAscii(phoneticGivenName))) {
+ builder.append(mVCardAttributeCharset);
+ builder.append(VCARD_DATA_SEPARATOR);
+ }
+
+ builder.append(escapeCharacters(phoneticFamilyName));
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(escapeCharacters(phoneticMiddleName));
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(escapeCharacters(phoneticGivenName));
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+
+ if (mUsesAndroidProperty) {
+ final String phoneticName = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName,
+ phoneticGivenName);
+ builder.append(VCARD_PROPERTY_X_PHONETIC_NAME);
+
+ if (!VCardUtils.containsOnlyAscii(phoneticName)) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ // TODO: may need to make the text quoted-printable.
+ builder.append(phoneticName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ }
+ } else if (mIsDoCoMo) {
+ builder.append(VCARD_PROPERTY_SOUND);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(Constants.ATTR_TYPE_X_IRMC_N);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticGivenName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticMiddleName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(phoneticFamilyName);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ }
+ }
+ }
+
+ private void appendNickNames(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Nickname.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ final String propertyNickname;
+ if (mIsV30) {
+ propertyNickname = VCARD_PROPERTY_NICKNAME;
+ } else if (mUsesAndroidProperty) {
+ propertyNickname = VCARD_PROPERTY_X_NICKNAME;
+ } else {
+ // There's no way to add this field.
+ return;
+ }
+
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues
+ .getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
+ builder.append(propertyNickname);
+
+ if (!VCardUtils.containsOnlyAscii(propertyNickname)) {
+ // Strictly, this is not valid in vCard 3.0. See above.
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(escapeCharacters(nickname));
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ }
+ }
+
+ private void appendPhones(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Phone.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ appendVCardTelephoneLine(builder, contentValues
+ .getAsInteger(Phone.TYPE), contentValues
+ .getAsString(Phone.LABEL), contentValues
+ .getAsString(Phone.NUMBER));
+ }
+ } else if (mIsDoCoMo) {
+ appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
+ }
+ }
+
+ private void appendEmails(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Email.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ appendVCardEmailLine(builder, contentValues
+ .getAsInteger(Email.TYPE), contentValues
+ .getAsString(Email.LABEL), contentValues
+ .getAsString(Email.DATA));
+ }
+ } else if (mIsDoCoMo) {
+ appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
+ }
+ }
+
+ private void appendPostals(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(StructuredPostal.CONTENT_ITEM_TYPE);
+
+ if (contentValuesList != null) {
+ if (mIsDoCoMo) {
+ appendPostalsForDoCoMo(builder, contentValuesList);
+ } else {
+ appendPostalsForGeneric(builder, contentValuesList);
+ }
+ } else if (mIsDoCoMo) {
+ builder.append(VCARD_PROPERTY_ADR);
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(Constants.ATTR_TYPE_HOME);
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+ }
+
+ /**
+ * Try to append just one line. If there's no appropriate address
+ * information, append an empty line.
+ */
+ private void appendPostalsForDoCoMo(final StringBuilder builder,
+ final List<ContentValues> contentValuesList) {
+ // TODO: from old, inefficient code. fix this.
+ if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
+ StructuredPostal.TYPE_HOME)) {
+ return;
+ }
+ if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
+ StructuredPostal.TYPE_WORK)) {
+ return;
+ }
+ if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
+ StructuredPostal.TYPE_OTHER)) {
+ return;
+ }
+ if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
+ StructuredPostal.TYPE_CUSTOM)) {
+ return;
+ }
+
+ Log.w(LOG_TAG,
+ "Should not come here. Must have at least one postal data.");
+ }
+
+ private boolean appendPostalsForDoCoMoInternal(final StringBuilder builder,
+ final List<ContentValues> contentValuesList, int preferedType) {
+ for (ContentValues contentValues : contentValuesList) {
+ final int type = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final String label = contentValues
+ .getAsString(StructuredPostal.LABEL);
+ if (type == preferedType) {
+ appendVCardPostalLine(builder, type, label, contentValues);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void appendPostalsForGeneric(final StringBuilder builder,
+ final List<ContentValues> contentValuesList) {
+ for (ContentValues contentValues : contentValuesList) {
+ final int type = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final String label = contentValues
+ .getAsString(StructuredPostal.LABEL);
+ appendVCardPostalLine(builder, type, label, contentValues);
+ }
+ }
+
+ private void appendIms(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Im.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ int type = contentValues.getAsInteger(Im.PROTOCOL);
+ String data = contentValues.getAsString(Im.DATA);
+
+ Log.d("@@@", "Im information. protocol=\"" + type +
+ "\", data=\"" + data + "\", protocol=\"" +
+ contentValues.getAsString(Im.PROTOCOL) + "\", custom_protocol=\"" +
+ contentValues.getAsString(Im.CUSTOM_PROTOCOL) + "\"");
+
+ if (type == Im.PROTOCOL_GOOGLE_TALK) {
+ if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
+ appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
+ }
+ // TODO: add "X-GOOGLE TALK" case...
+ }
+ }
+ }
+ }
+
+ private void appendWebsites(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Website.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String website = contentValues.getAsString(Website.URL);
+ appendVCardLine(builder, VCARD_PROPERTY_URL, website);
+ }
+ }
+ }
+
+ private void appendBirthday(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Website.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null && contentValuesList.size() > 0) {
+ // Theoretically, there must be only one birthday for each vCard data and
+ // we are afraid of some parse error occuring in some devices, so
+ // we emit only one birthday entry for now.
+ final String birthday = contentValuesList.get(0).getAsString(Miscellaneous.BIRTHDAY);
+ appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
+ }
+ }
+
+ private void appendOrganizations(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Organization.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String company = contentValues
+ .getAsString(Organization.COMPANY);
+ final String title = contentValues
+ .getAsString(Organization.TITLE);
+ appendVCardLine(builder, VCARD_PROPERTY_ORG, company, true,
+ mUsesQuotedPrintable);
+ appendVCardLine(builder, VCARD_PROPERTY_TITLE, title, true,
+ mUsesQuotedPrintable);
+ }
+ }
+ }
+
+ private void appendPhotos(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ List<ContentValues> contentValuesList = contentValuesListMap
+ .get(Photo.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+ final String photoType;
+ // Use some heuristics for guessing the format of the image.
+ // TODO: there should be some general API for detecting the file format.
+ if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
+ && data[2] == 'F') {
+ photoType = "GIF";
+ } else if (data.length >= 4 && data[0] == (byte) 0x89
+ && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
+ // Note: vCard 2.1 officially does not support PNG, but we
+ // may have it
+ // and using X- word like "X-PNG" may not let importers know
+ // it is
+ // PNG. So we use the String "PNG" as is...
+ photoType = "PNG";
+ } else if (data.length >= 2 && data[0] == (byte) 0xff
+ && data[1] == (byte) 0xd8) {
+ photoType = "JPEG";
+ } else {
+ Log.d(LOG_TAG, "Unknown photo type. Ignore.");
+ continue;
+ }
+ String photoString = VCardUtils.encodeBase64(data);
+ if (photoString.length() > 0) {
+ appendVCardPhotoLine(builder, photoString, photoType);
+ }
+ }
+ }
+ }
+
+ private void appendNotes(final StringBuilder builder,
+ final Map<String, List<ContentValues>> contentValuesListMap) {
+ final List<ContentValues> contentValuesList =
+ contentValuesListMap.get(Note.CONTENT_ITEM_TYPE);
+ if (contentValuesList != null) {
+ if (mOnlyOneNoteFieldIsAvailable) {
+ StringBuilder noteBuilder = new StringBuilder();
+ boolean first = true;
+ for (ContentValues contentValues : contentValuesList) {
+ final String note = contentValues.getAsString(Note.NOTE);
+ if (note.length() > 0) {
+ if (first) {
+ first = false;
+ } else {
+ noteBuilder.append('\n');
+ }
+ noteBuilder.append(note);
+ }
+ }
+ appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteBuilder.toString(),
+ true, mUsesQuotedPrintable);
+ } else {
+ for (ContentValues contentValues : contentValuesList) {
+ final String note = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(note)) {
+ appendVCardLine(builder, VCARD_PROPERTY_NOTE, note, true,
+ mUsesQuotedPrintable);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Append '\' to the characters which should be escaped. The character set is different
+ * not only between vCard 2.1 and vCard 3.0 but also among each device.
+ *
+ * Note that Quoted-Printable string must not be input here.
+ */
+ @SuppressWarnings("fallthrough")
+ private String escapeCharacters(String unescaped) {
+ if (TextUtils.isEmpty(unescaped)) {
+ return "";
+ }
+
+ StringBuilder builder = new StringBuilder();
+ final int length = unescaped.length();
+ for (int i = 0; i < length; i++) {
+ char ch = unescaped.charAt(i);
+ switch (ch) {
+ case ';':
+ builder.append('\\');
+ builder.append(';');
+ break;
+ case '\r':
+ if (i + 1 < length) {
+ char nextChar = unescaped.charAt(i);
+ if (nextChar == '\n') {
+ continue;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+ case '\n':
+ // In vCard 2.1, there's no specification about this, while
+ // vCard 3.0 explicitly
+ // requires this should be encoded to "\n".
+ builder.append("\\n");
+ break;
+ case '\\':
+ if (mIsV30) {
+ builder.append("\\\\");
+ break;
+ }
+ case '<':
+ case '>':
+ if (mIsDoCoMo) {
+ builder.append('\\');
+ builder.append(ch);
+ }
+ break;
+ case ',':
+ if (mIsV30) {
+ builder.append("\\,");
+ break;
+ }
+ default:
+ builder.append(ch);
+ break;
+ }
+ }
+ return builder.toString();
+ }
+
+ private void appendVCardPhotoLine(StringBuilder builder,
+ String encodedData, String type) {
+ StringBuilder tmpBuilder = new StringBuilder();
+ tmpBuilder.append(VCARD_PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_ATTR_SEPARATOR);
+ if (mIsV30) {
+ tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
+ } else {
+ tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
+ }
+ tmpBuilder.append(VCARD_ATTR_SEPARATOR);
+ tmpBuilder.append("TYPE=");
+ tmpBuilder.append(type);
+ tmpBuilder.append(VCARD_DATA_SEPARATOR);
+ tmpBuilder.append(encodedData);
+
+ String tmpStr = tmpBuilder.toString();
+ tmpBuilder = new StringBuilder();
+ int lineCount = 0;
+ for (int i = 0; i < tmpStr.length(); i++) {
+ tmpBuilder.append(tmpStr.charAt(i));
+ lineCount++;
+ if (lineCount > 72) {
+ tmpBuilder.append(VCARD_COL_SEPARATOR);
+ tmpBuilder.append(VCARD_WS);
+ lineCount = 0;
+ }
+ }
+ builder.append(tmpBuilder.toString());
+ builder.append(VCARD_COL_SEPARATOR);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ private void appendVCardPostalLine(StringBuilder builder, int type,
+ String label, final ContentValues contentValues) {
+ builder.append(VCARD_PROPERTY_ADR);
+ builder.append(VCARD_ATTR_SEPARATOR);
+
+ boolean dataExists = false;
+ String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
+ int length = dataArray.length;
+ final boolean useQuotedPrintable = mUsesQuotedPrintable;
+ for (int i = 0; i < length; i++) {
+ String data = dataArray[i];
+ if (!TextUtils.isEmpty(data)) {
+ dataExists = true;
+ if (useQuotedPrintable) {
+ dataArray[i] = encodeQuotedPrintable(data);
+ } else {
+ dataArray[i] = escapeCharacters(data);
+ }
+ }
+ }
+
+ boolean typeIsAppended = false;
+ switch (type) {
+ case StructuredPostal.TYPE_HOME:
+ builder.append(Constants.ATTR_TYPE_HOME);
+ typeIsAppended = true;
+ break;
+ case StructuredPostal.TYPE_WORK:
+ builder.append(Constants.ATTR_TYPE_WORK);
+ typeIsAppended = true;
+ break;
+ case StructuredPostal.TYPE_CUSTOM:
+ if (mUsesAndroidProperty && VCardUtils.containsOnlyAlphaDigitHyphen(label)){
+ // We're not sure whether the label is valid in the spec ("IANA-token" in the vCard 3.1
+ // is unclear...)
+ // Just for safety, we add "X-" at the beggining of each label.
+ // Also checks the label obeys with vCard 3.0 spec.
+ builder.append("X-");
+ builder.append(label);
+ builder.append(VCARD_DATA_SEPARATOR);
+ }
+ break;
+ case StructuredPostal.TYPE_OTHER:
+ break;
+ default:
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+ break;
+ }
+
+ if (dataExists) {
+ if (typeIsAppended) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ }
+ // Strictly, vCard 3.0 does not allow this, but we add this since
+ // this information
+ // should be useful, Assume no parser does not emit error with this
+ // attribute.
+ builder.append(mVCardAttributeCharset);
+ if (useQuotedPrintable) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(VCARD_ATTR_ENCODING_QP);
+ }
+ }
+ builder.append(VCARD_DATA_SEPARATOR);
+ if (dataExists) {
+ // The elements in dataArray are already encoded to quoted printable
+ // if needed.
+ // See above.
+ //
+ // TODO: in vCard 3.0, one line may become too huge. Fix this.
+ builder.append(dataArray[0]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[1]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[2]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[3]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[4]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[5]);
+ builder.append(VCARD_ITEM_SEPARATOR);
+ builder.append(dataArray[6]);
+ }
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ private void appendVCardEmailLine(StringBuilder builder, int type,
+ String label, String data) {
+ builder.append(VCARD_PROPERTY_EMAIL);
+ builder.append(VCARD_ATTR_SEPARATOR);
+
+ switch (type) {
+ case Email.TYPE_CUSTOM:
+ if (label.equals(
+ android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME)) {
+ builder.append(Constants.ATTR_TYPE_CELL);
+ } else if (mUsesAndroidProperty && VCardUtils.containsOnlyAlphaDigitHyphen(label)){
+ builder.append("X-");
+ builder.append(label);
+ } else {
+ // Default to INTERNET.
+ builder.append(Constants.ATTR_TYPE_INTERNET);
+ }
+ break;
+ case Email.TYPE_HOME:
+ builder.append(Constants.ATTR_TYPE_HOME);
+ break;
+ case Email.TYPE_WORK:
+ builder.append(Constants.ATTR_TYPE_WORK);
+ break;
+ case Email.TYPE_OTHER:
+ builder.append(Constants.ATTR_TYPE_INTERNET);
+ break;
+ default:
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ builder.append(Constants.ATTR_TYPE_INTERNET);
+ break;
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(data);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ private void appendVCardTelephoneLine(StringBuilder builder, int type,
+ String label, String encodedData) {
+ builder.append(VCARD_PROPERTY_TEL);
+ builder.append(VCARD_ATTR_SEPARATOR);
+
+ switch (type) {
+ case Phone.TYPE_HOME:
+ appendTypeAttributes(builder, Arrays.asList(
+ Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
+ break;
+ case Phone.TYPE_WORK:
+ appendTypeAttributes(builder, Arrays.asList(
+ Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
+ break;
+ case Phone.TYPE_FAX_HOME:
+ appendTypeAttributes(builder, Arrays.asList(
+ Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
+ break;
+ case Phone.TYPE_FAX_WORK:
+ appendTypeAttributes(builder, Arrays.asList(
+ Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
+ break;
+ case Phone.TYPE_MOBILE:
+ builder.append(Constants.ATTR_TYPE_CELL);
+ break;
+ case Phone.TYPE_PAGER:
+ if (mIsDoCoMo) {
+ // Not sure about the reason, but previous implementation had
+ // used "VOICE" instead of "PAGER"
+ builder.append(Constants.ATTR_TYPE_VOICE);
+ } else {
+ builder.append(Constants.ATTR_TYPE_PAGER);
+ }
+ break;
+ case Phone.TYPE_OTHER:
+ builder.append(Constants.ATTR_TYPE_VOICE);
+ break;
+ case Phone.TYPE_CUSTOM:
+ if (mUsesAndroidProperty) {
+ VCardUtils.containsOnlyAlphaDigitHyphen(label);
+ builder.append("X-" + label);
+ } else {
+ // Just ignore the custom type.
+ builder.append(Constants.ATTR_TYPE_VOICE);
+ }
+ break;
+ default:
+ appendUncommonPhoneType(builder, type);
+ break;
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedData);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ /**
+ * Appends phone type string which may not be available in some devices.
+ */
+ private void appendUncommonPhoneType(StringBuilder builder, int type) {
+ if (mIsDoCoMo) {
+ // The previous implementation for DoCoMo had been conservative
+ // about
+ // miscellaneous types.
+ builder.append(Constants.ATTR_TYPE_VOICE);
+ } else {
+ String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
+ if (phoneAttribute != null) {
+ builder.append(phoneAttribute);
+ } else {
+ Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+ }
+ }
+ }
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String propertyName, final String rawData) {
+ appendVCardLine(builder, propertyName, rawData, false, false);
+ }
+
+ private void appendVCardLine(final StringBuilder builder,
+ final String field, final String rawData, boolean needCharset,
+ boolean needQuotedPrintable) {
+ builder.append(field);
+ if (needCharset) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(mVCardAttributeCharset);
+ }
+
+ final String encodedData;
+ if (needQuotedPrintable) {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ builder.append(VCARD_ATTR_ENCODING_QP);
+ encodedData = encodeQuotedPrintable(rawData);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard spec, though
+ // several (even well-known) applications do not care this.
+ encodedData = escapeCharacters(rawData);
+ }
+
+ builder.append(VCARD_DATA_SEPARATOR);
+ builder.append(encodedData);
+ builder.append(VCARD_COL_SEPARATOR);
+ }
+
+ private void appendTypeAttributes(final StringBuilder builder,
+ final List<String> types) {
+ // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
+ // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
+ boolean first = true;
+ for (String type : types) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(VCARD_ATTR_SEPARATOR);
+ }
+ if (mIsV30) {
+ builder.append(Constants.ATTR_TYPE);
+ builder.append('=');
+ }
+ builder.append(type);
+ }
+ }
+
+ private String encodeQuotedPrintable(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return "";
+ }
+ {
+ // Replace "\n" and "\r" with "\r\n".
+ StringBuilder tmpBuilder = new StringBuilder();
+ int length = str.length();
+ for (int i = 0; i < length; i++) {
+ char ch = str.charAt(i);
+ if (ch == '\r') {
+ if (i + 1 < length && str.charAt(i + 1) == '\n') {
+ i++;
+ }
+ tmpBuilder.append("\r\n");
+ } else if (ch == '\n') {
+ tmpBuilder.append("\r\n");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ }
+ str = tmpBuilder.toString();
+ }
+
+ StringBuilder builder = new StringBuilder();
+ int index = 0;
+ int lineCount = 0;
+ byte[] strArray = null;
+
+ try {
+ strArray = str.getBytes(mCharsetString);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ + "Try default charset");
+ strArray = str.getBytes();
+ }
+ while (index < strArray.length) {
+ builder.append(String.format("=%02X", strArray[index]));
+ index += 1;
+ lineCount += 3;
+
+ if (lineCount >= 67) {
+ // Specification requires CRLF must be inserted before the
+ // length of the line
+ // becomes more than 76.
+ // Assuming that the next character is a multi-byte character,
+ // it will become
+ // 6 bytes.
+ // 76 - 6 - 3 = 67
+ builder.append("=\r\n");
+ lineCount = 0;
+ }
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index fef9dba..d87b002 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,43 +15,267 @@
*/
package android.pim.vcard;
+import java.util.HashMap;
+import java.util.Map;
+
/**
- * The class representing VCard related configurations
+ * The class representing VCard related configurations. Useful static methods are not in this class
+ * but in VCardUtils.
*/
public class VCardConfig {
- static final int LOG_LEVEL_NONE = 0;
- static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
- static final int LOG_LEVEL_SHOW_WARNING = 0x2;
- static final int LOG_LEVEL_VERBOSE =
+ // TODO: may be better to make the instance of this available and stop using static methods and
+ // one integer.
+
+ /* package */ static final int LOG_LEVEL_NONE = 0;
+ /* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
+ /* package */ static final int LOG_LEVEL_SHOW_WARNING = 0x2;
+ /* package */ static final int LOG_LEVEL_VERBOSE =
LOG_LEVEL_PERFORMANCE_MEASUREMENT | LOG_LEVEL_SHOW_WARNING;
-
+
+ /* package */ static final int LOG_LEVEL = LOG_LEVEL_PERFORMANCE_MEASUREMENT;
+
// Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
// decode the unicode to the original charset. If not, this setting will cause some bug.
public static final String DEFAULT_CHARSET = "iso-8859-1";
- // TODO: use this flag
- public static boolean IGNORE_CASE_EXCEPT_VALUE = true;
+ // 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;
+
+ // 0x2 is reserved for the future use ...
+
+ public static final int NAME_ORDER_DEFAULT = 0;
+ public static final int NAME_ORDER_EUROPE = 0x4;
+ public static final int NAME_ORDER_JAPANESE = 0x8;
+ private static final int NAME_ORDER_MASK = 0xC;
+
+ // 0x10 is reserved for safety
+
+ private static final int FLAG_CHARSET_UTF8 = 0;
+ private static final int FLAG_CHARSET_SHIFT_JIS = 0x20;
+
+ /**
+ * The flag indicating the vCard composer will add some "X-" properties used only in Android
+ * when the formal vCard specification does not have appropriate fields for that data.
+ *
+ * For example, Android accepts nickname information while vCard 2.1 does not.
+ * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
+ * instead of just dropping it.
+ *
+ * vCard parser code automatically parses the field emitted even when this flag is off.
+ *
+ * Note that this flag does not assure all the information must be hold in the emitted vCard.
+ */
+ private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
+
+ /**
+ * The flag indicating the vCard composer will add some "X-" properties seen in the
+ * vCard data emitted by the other softwares/devices when the formal vCard specification
+ * does not have appropriate field(s) for that data.
+ *
+ * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
+ * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
+ * non-Android devices/softwares. We chose to enable the vCard composer to use those
+ * defact properties since they are also useful for Android devices.
+ *
+ * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
+ * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
+ * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+ */
+ private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
+
+ /**
+ * The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese
+ * mobile careers) should be used. This flag does not include any other information like
+ * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
+ * dialect but the name order should be European", but it is not recommended.
+ */
+ private static final int FLAG_DOCOMO = 0x20000000;
+
+
+ // VCard types
+
+
+ /**
+ * General vCard format with the version 2.1. Uses UTF-8 for the charset.
+ * When composing a vCard entry, the US convension will be used.
+ *
+ * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
+ * while in Japan, it should be "Prefix Family Middle Given Suffix".
+ */
+ public static final int VCARD_TYPE_V21_GENERIC =
+ (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+
+ /**
+ * General vCard format with the version 3.0. Uses UTF-8 for the charset.
+ *
+ * Note that this type is not fully implemented, so probably some bugs remain especially
+ * in parsing part.
+ *
+ * TODO: implement this type.
+ */
+ public static final int VCARD_TYPE_V30_GENERIC =
+ (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+
+ /**
+ * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+ * Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+ */
+ public static final int VCARD_TYPE_V21_EUROPE =
+ (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+
+ /**
+ * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+ */
+ public static final int VCARD_TYPE_V30_EUROPE =
+ (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
+
+ /**
+ * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+ * parsing/composing the vCard data.
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
+
+ /**
+ * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
- protected static final int LOG_LEVEL = LOG_LEVEL_PERFORMANCE_MEASUREMENT;
+ /**
+ * vCard format for miscellaneous Japanese devices, using Shift_Jis for
+ * parsing/composing the vCard data.
+ */
+ public static final int VCARD_TYPE_V30_JAPANESE =
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
- // Note: phonetic name probably should be "LAST FIRST MIDDLE" for European languages, and
- // space should be added between each element while it should not be in Japanese.
- // But unfortunately, we currently do not have the data and are not sure whether we should
- // support European version of name ordering.
- //
- // TODO: Implement the logic described above if we really need European version of
- // phonetic name handling. Also, adding the appropriate test case of vCard would be
- // highly appreciated.
- public static final int NAME_ORDER_TYPE_ENGLISH = 0;
- public static final int NAME_ORDER_TYPE_JAPANESE = 1;
+ /**
+ * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ */
+ public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
+ (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
+
+ /**
+ * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+ * No Android-specific property nor defact property is included.
+ */
+ public static final int VCARD_TYPE_DOCOMO =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+
+ private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static final int NAME_ORDER_TYPE_DEFAULT = NAME_ORDER_TYPE_ENGLISH;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+
+ private static final Map<String, Integer> VCARD_TYPES_MAP;
+
+ static {
+ VCARD_TYPES_MAP = new HashMap<String, Integer>();
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+ }
+
+ public static int getVCardTypeFromString(String vcardTypeString) {
+ String loweredKey = vcardTypeString.toLowerCase();
+ if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
+ return VCARD_TYPES_MAP.get(loweredKey);
+ } else {
+ // XXX: should return the value indicating the input is invalid?
+ return VCARD_TYPE_DEFAULT;
+ }
+ }
+
+ public static boolean isV30(int vcardType) {
+ return ((vcardType & FLAG_V30) != 0);
+ }
+
+ public static boolean usesQuotedPrintable(int vcardType) {
+ return !isV30(vcardType);
+ }
+
+ public static boolean isDoCoMo(int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
+ }
+
+ /**
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
+ */
+ public static boolean isJapaneseDevice(int vcardType) {
+ return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
+ (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
+ (vcardType == VCARD_TYPE_V30_JAPANESE) ||
+ (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
+ (vcardType == VCARD_TYPE_DOCOMO));
+ }
+
+ public static boolean usesShiftJis(int vcardType) {
+ return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
+ }
/**
- * @hide temporal. may be deleted
+ * @return true when Japanese phonetic string must be converted to a string
+ * containing only half-width katakana. This method exists since Japanese mobile
+ * phones usually use only half-width katakana for expressing phonetic names and
+ * some devices are not ready for parsing other phonetic strings like hiragana and
+ * full-width katakana.
*/
+ public static boolean needsToConvertPhoneticString(int vcardType) {
+ return (vcardType == VCARD_TYPE_DOCOMO);
+ }
+
+ public static int getNameOrderType(int vcardType) {
+ return vcardType & NAME_ORDER_MASK;
+ }
+
+ public static boolean usesAndroidSpecificProperty(int vcardType) {
+ return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
+ }
+
+ public static boolean usesDefactProperty(int vcardType) {
+ return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
public static boolean showPerformanceLog() {
- return (LOG_LEVEL & LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
+ return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardDataBuilder.java
index 4025f6c..fd165e9 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardDataBuilder.java
@@ -59,7 +59,7 @@ public class VCardDataBuilder implements VCardBuilder {
private String mTargetCharset;
private boolean mStrictLineBreakParsing;
- private int mNameOrderType;
+ private int mVCardType;
// Just for testing.
private long mTimePushIntoContentResolver;
@@ -67,23 +67,21 @@ public class VCardDataBuilder implements VCardBuilder {
private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
public VCardDataBuilder() {
- this(null, null, false, VCardConfig.NAME_ORDER_TYPE_DEFAULT);
+ this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC);
}
/**
* @hide
*/
- public VCardDataBuilder(int nameOrderType) {
- this(null, null, false, nameOrderType);
+ public VCardDataBuilder(int vcardType) {
+ this(null, null, false, vcardType);
}
-
+
/**
* @hide
*/
- public VCardDataBuilder(String charset,
- boolean strictLineBreakParsing,
- int nameOrderType) {
- this(null, charset, strictLineBreakParsing, nameOrderType);
+ public VCardDataBuilder(String charset, boolean strictLineBreakParsing, int vcardType) {
+ this(null, charset, strictLineBreakParsing, vcardType);
}
/**
@@ -92,7 +90,7 @@ public class VCardDataBuilder implements VCardBuilder {
public VCardDataBuilder(String sourceCharset,
String targetCharset,
boolean strictLineBreakParsing,
- int nameOrderType) {
+ int vcardType) {
if (sourceCharset != null) {
mSourceCharset = sourceCharset;
} else {
@@ -104,7 +102,7 @@ public class VCardDataBuilder implements VCardBuilder {
mTargetCharset = TARGET_CHARSET;
}
mStrictLineBreakParsing = strictLineBreakParsing;
- mNameOrderType = nameOrderType;
+ mVCardType = vcardType;
}
public void addEntryHandler(EntryHandler entryHandler) {
@@ -112,11 +110,14 @@ public class VCardDataBuilder implements VCardBuilder {
}
public void start() {
+ for (EntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onParsingStart();
+ }
}
public void end() {
for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onFinal();
+ entryHandler.onParsingEnd();
}
}
@@ -135,7 +136,7 @@ public class VCardDataBuilder implements VCardBuilder {
Log.e(LOG_TAG, "This is not VCARD!");
}
- mCurrentContactStruct = new ContactStruct(mNameOrderType);
+ mCurrentContactStruct = new ContactStruct(mVCardType);
}
public void endRecord() {
@@ -164,8 +165,7 @@ public class VCardDataBuilder implements VCardBuilder {
public void propertyParamType(String type) {
if (mParamType != null) {
- Log.e(LOG_TAG,
- "propertyParamType() is called more than once " +
+ Log.e(LOG_TAG, "propertyParamType() is called more than once " +
"before propertyParamValue() is called");
}
mParamType = type;
@@ -173,6 +173,7 @@ public class VCardDataBuilder implements VCardBuilder {
public void propertyParamValue(String value) {
if (mParamType == null) {
+ // From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
mParamType = "TYPE";
}
mCurrentProperty.addParameter(mParamType, value);
@@ -297,7 +298,7 @@ public class VCardDataBuilder implements VCardBuilder {
String charset =
((charsetCollection != null) ? charsetCollection.iterator().next() : null);
String targetCharset = CharsetUtils.nameForDefaultVendor(charset);
-
+
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 17a138f..974fca8 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -34,7 +34,7 @@ import java.util.HashSet;
* This class is used to parse vcard. Please refer to vCard Specification 2.1.
*/
public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "VCardParser_V21";
+ private static final String LOG_TAG = "vcard.VCardParser_V21";
/** Store the known-type */
private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
@@ -58,8 +58,10 @@ public class VCardParser_V21 extends VCardParser {
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
- // Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
- // We allow it for safety...
+ /**
+ * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
+ * We allow it for safety...
+ */
private static final HashSet<String> sAvailableEncodingV21 =
new HashSet<String>(Arrays.asList(
"7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
@@ -70,7 +72,10 @@ public class VCardParser_V21 extends VCardParser {
/** The builder to build parsed data */
protected VCardBuilder mBuilder = null;
- /** The encoding type */
+ /**
+ * The encoding type. "Encoding" in vCard is different from "Charset".
+ * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE.
+ */
protected String mEncoding = null;
protected final String sDefaultEncoding = "8BIT";
@@ -88,17 +93,17 @@ public class VCardParser_V21 extends VCardParser {
// Just for debugging
private long mTimeTotal;
- private long mTimeStartRecord;
- private long mTimeEndRecord;
+ private long mTimeReadStartRecord;
+ private long mTimeReadEndRecord;
private long mTimeStartProperty;
private long mTimeEndProperty;
private long mTimeParseItems;
- private long mTimeParseItem1;
- private long mTimeParseItem2;
- private long mTimeParseItem3;
- private long mTimeHandlePropertyValue1;
- private long mTimeHandlePropertyValue2;
- private long mTimeHandlePropertyValue3;
+ private long mTimeParseLineAndHandleGroup;
+ private long mTimeParsePropertyValues;
+ private long mTimeParseAdrOrgN;
+ private long mTimeHandleMiscPropertyValue;
+ private long mTimeHandleQuotedPrintable;
+ private long mTimeHandleBase64;
/**
* Create a new VCard parser.
@@ -213,7 +218,7 @@ public class VCardParser_V21 extends VCardParser {
if (mBuilder != null) {
start = System.currentTimeMillis();
mBuilder.startRecord("VCARD");
- mTimeStartRecord += System.currentTimeMillis() - start;
+ mTimeReadStartRecord += System.currentTimeMillis() - start;
}
start = System.currentTimeMillis();
parseItems();
@@ -222,7 +227,7 @@ public class VCardParser_V21 extends VCardParser {
if (mBuilder != null) {
start = System.currentTimeMillis();
mBuilder.endRecord();
- mTimeEndRecord += System.currentTimeMillis() - start;
+ mTimeReadEndRecord += System.currentTimeMillis() - start;
}
return true;
}
@@ -250,26 +255,6 @@ public class VCardParser_V21 extends VCardParser {
// Though vCard 2.1/3.0 specification does not allow lower cases,
// some data may have them, so we allow it (Actually, previous code
// had explicitly allowed "BEGIN:vCard" though there's no example).
- //
- // TODO: ignore non vCard entry (e.g. vcalendar).
- // XXX: Not sure, but according to VDataBuilder.java, vcalendar
- // entry
- // may be nested. Just seeking "END:SOMETHING" may not be enough.
- // e.g.
- // BEGIN:VCARD
- // ... (Valid. Must parse this)
- // END:VCARD
- // BEGIN:VSOMETHING
- // ... (Must ignore this)
- // BEGIN:VSOMETHING2
- // ... (Must ignore this)
- // END:VSOMETHING2
- // ... (Must ignore this!)
- // END:VSOMETHING
- // BEGIN:VCARD
- // ... (Valid. Must parse this)
- // END:VCARD
- // INVALID_STRING (VCardException should be thrown)
if (length == 2 &&
strArray[0].trim().equalsIgnoreCase("BEGIN") &&
strArray[1].trim().equalsIgnoreCase("VCARD")) {
@@ -367,11 +352,11 @@ public class VCardParser_V21 extends VCardParser {
}
/**
- * item = [groups "."] name [params] ":" value CRLF
- * / [groups "."] "ADR" [params] ":" addressparts CRLF
- * / [groups "."] "ORG" [params] ":" orgparts CRLF
- * / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * item = [groups "."] name [params] ":" value CRLF
+ * / [groups "."] "ADR" [params] ":" addressparts CRLF
+ * / [groups "."] "ORG" [params] ":" orgparts CRLF
+ * / [groups "."] "N" [params] ":" nameparts CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
@@ -389,14 +374,13 @@ public class VCardParser_V21 extends VCardParser {
String propertyName = propertyNameAndValue[0].toUpperCase();
String propertyValue = propertyNameAndValue[1];
- mTimeParseItem1 += System.currentTimeMillis() - start;
+ mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
- if (propertyName.equals("ADR") ||
- propertyName.equals("ORG") ||
+ if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
propertyName.equals("N")) {
start = System.currentTimeMillis();
handleMultiplePropertyValue(propertyName, propertyValue);
- mTimeParseItem3 += System.currentTimeMillis() - start;
+ mTimeParseAdrOrgN += System.currentTimeMillis() - start;
return false;
} else if (propertyName.equals("AGENT")) {
handleAgent(propertyValue);
@@ -408,14 +392,13 @@ public class VCardParser_V21 extends VCardParser {
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") &&
- !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
throw new VCardVersionException("Incompatible version: " +
propertyValue + " != " + getVersion());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
- mTimeParseItem2 += System.currentTimeMillis() - start;
+ mTimeParsePropertyValues += System.currentTimeMillis() - start;
return false;
}
@@ -542,7 +525,7 @@ public class VCardParser_V21 extends VCardParser {
}
/**
- * ptypeval = knowntype / "X-" word
+ * ptypeval = knowntype / "X-" word
*/
protected void handleType(String ptypeval) {
String upperTypeValue = ptypeval;
@@ -637,8 +620,7 @@ public class VCardParser_V21 extends VCardParser {
}
}
- protected void handlePropertyValue(
- String propertyName, String propertyValue) throws
+ protected void handlePropertyValue(String propertyName, String propertyValue) throws
IOException, VCardException {
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
long start = System.currentTimeMillis();
@@ -648,7 +630,7 @@ public class VCardParser_V21 extends VCardParser {
v.add(result);
mBuilder.propertyValues(v);
}
- mTimeHandlePropertyValue2 += System.currentTimeMillis() - start;
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (mEncoding.equalsIgnoreCase("BASE64") ||
mEncoding.equalsIgnoreCase("B")) {
long start = System.currentTimeMillis();
@@ -667,7 +649,7 @@ public class VCardParser_V21 extends VCardParser {
mBuilder.propertyValues(null);
}
}
- mTimeHandlePropertyValue3 += System.currentTimeMillis() - start;
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
} else {
if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
|| mEncoding.equalsIgnoreCase("8BIT")
@@ -681,7 +663,7 @@ public class VCardParser_V21 extends VCardParser {
v.add(maybeUnescapeText(propertyValue));
mBuilder.propertyValues(v);
}
- mTimeHandlePropertyValue1 += System.currentTimeMillis() - start;
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
}
}
@@ -770,15 +752,15 @@ public class VCardParser_V21 extends VCardParser {
* We are not sure whether we should add "\" CRLF to each value.
* For now, we exclude them.
*/
- protected void handleMultiplePropertyValue(
- String propertyName, String propertyValue) throws IOException, VCardException {
- // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some data have it.
+ protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here,
+ // but some softwares/devices emit such data.
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
propertyValue = getQuotedPrintable(propertyValue);
}
if (mBuilder != null) {
- // TODO: limit should be set in accordance with propertyName?
StringBuilder builder = new StringBuilder();
ArrayList<String> list = new ArrayList<String>();
int length = propertyValue.length();
@@ -786,7 +768,7 @@ public class VCardParser_V21 extends VCardParser {
char ch = propertyValue.charAt(i);
if (ch == '\\' && i < length - 1) {
char nextCh = propertyValue.charAt(i + 1);
- String unescapedString = maybeUnescape(nextCh);
+ String unescapedString = maybeUnescapeCharacter(nextCh);
if (unescapedString != null) {
builder.append(unescapedString);
i++;
@@ -819,7 +801,6 @@ public class VCardParser_V21 extends VCardParser {
throw new VCardNotSupportedException("AGENT Property is not supported now.");
/* This is insufficient support. Also, AGENT Property is very rare.
Ignore it for now.
- TODO: fix this.
String[] strArray = propertyValue.split(":", 2);
if (!(strArray.length == 2 ||
@@ -843,7 +824,7 @@ public class VCardParser_V21 extends VCardParser {
* 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 maybeUnescape(char ch) {
+ protected String maybeUnescapeCharacter(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.
@@ -863,17 +844,11 @@ public class VCardParser_V21 extends VCardParser {
@Override
public boolean parse(InputStream is, String charset, VCardBuilder builder)
throws IOException, VCardException {
- // TODO: make this count error entries instead of just throwing VCardException.
-
- {
- // TODO: If we really need to allow only CRLF as line break,
- // we will have to develop our own BufferedReader().
- final InputStreamReader tmpReader = new InputStreamReader(is, charset);
- if (VCardConfig.showPerformanceLog()) {
- mReader = new CustomBufferedReader(tmpReader);
- } else {
- mReader = new BufferedReader(tmpReader);
- }
+ final InputStreamReader tmpReader = new InputStreamReader(is, charset);
+ if (VCardConfig.showPerformanceLog()) {
+ mReader = new CustomBufferedReader(tmpReader);
+ } else {
+ mReader = new BufferedReader(tmpReader);
}
mBuilder = builder;
@@ -903,21 +878,26 @@ public class VCardParser_V21 extends VCardParser {
}
private void showPerformanceInfo() {
- Log.d(LOG_TAG, "total parsing time: " + mTimeTotal + " ms");
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
if (mReader instanceof CustomBufferedReader) {
- Log.d(LOG_TAG, "total readLine time: " +
+ Log.d(LOG_TAG, "Total readLine time: " +
((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
}
- Log.d(LOG_TAG, "mTimeStartRecord: " + mTimeStartRecord + " ms");
- Log.d(LOG_TAG, "mTimeEndRecord: " + mTimeEndRecord + " ms");
- Log.d(LOG_TAG, "mTimeParseItem1: " + mTimeParseItem1 + " ms");
- Log.d(LOG_TAG, "mTimeParseItem2: " + mTimeParseItem2 + " ms");
- Log.d(LOG_TAG, "mTimeParseItem3: " + mTimeParseItem3 + " ms");
- Log.d(LOG_TAG, "mTimeHandlePropertyValue1: " + mTimeHandlePropertyValue1 + " ms");
- Log.d(LOG_TAG, "mTimeHandlePropertyValue2: " + mTimeHandlePropertyValue2 + " ms");
- Log.d(LOG_TAG, "mTimeHandlePropertyValue3: " + mTimeHandlePropertyValue3 + " ms");
+ Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
+ mTimeReadStartRecord + " ms");
+ Log.d(LOG_TAG, "Time for handling the end of the record: " +
+ mTimeReadEndRecord + " ms");
+ Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
+ mTimeParseLineAndHandleGroup + " ms");
+ Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+ Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+ Log.d(LOG_TAG, "Time for handling normal property values: " +
+ mTimeHandleMiscPropertyValue + " ms");
+ Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
+ mTimeHandleQuotedPrintable + " ms");
+ Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
}
-
+
private boolean isLetter(char ch) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return true;
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 634d9f5..475be4e 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -27,7 +27,7 @@ import java.util.HashSet;
* Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
*/
public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "VCardParser_V30";
+ private static final String LOG_TAG = "vcard.VCardParser_V30";
private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
Arrays.asList(
@@ -49,7 +49,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
@Override
protected String getVersion() {
- return "3.0";
+ return Constants.VERSION_V30;
}
@Override
@@ -284,7 +284,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
if (ch == '\\' && i < length - 1) {
char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
- builder.append("\r\n");
+ builder.append("\n");
} else {
builder.append(next_ch);
}
@@ -296,9 +296,9 @@ public class VCardParser_V30 extends VCardParser_V21 {
}
@Override
- protected String maybeUnescape(char ch) {
+ protected String maybeUnescapeCharacter(char ch) {
if (ch == 'n' || ch == 'N') {
- return "\r\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
new file mode 100644
index 0000000..b7b706f
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.text.TextUtils;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for VCard handling codes.
+ */
+public class VCardUtils {
+ /*
+ * TODO: some of methods in this class should be placed to the more appropriate place...
+ */
+
+ // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
+ // converted to two attribute Strings. These only contain some minor fields valid in both
+ // 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>();
+
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
+ sKnownPhoneTypesMap_StoI.put(
+ Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
+
+ sPhoneTypesSetUnknownToContacts = new HashSet<String>();
+ sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
+ sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
+ sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
+ sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+ }
+
+ public static String getPhoneAttributeString(int type) {
+ return sKnownPhoneTypesMap_ItoS.get(type);
+ }
+
+ /**
+ * Returns Interger when the given types can be parsed as known type. Returns String object
+ * when not, which should be set to label.
+ */
+ public static Object getPhoneTypeFromStrings(Collection<String> types) {
+ int type = -1;
+ String label = null;
+ boolean isFax = false;
+ boolean hasPref = false;
+
+ if (types != null) {
+ for (String typeString : types) {
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ hasPref = true;
+ } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+ isFax = true;
+ } else {
+ if (typeString.startsWith("X-") && type < 0) {
+ typeString = typeString.substring(2);
+ }
+ Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+ if (tmp != null) {
+ type = tmp;
+ } else if (type < 0) {
+ type = Phone.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ }
+ }
+ if (type < 0) {
+ if (hasPref) {
+ type = Phone.TYPE_MAIN;
+ } else {
+ // default to TYPE_HOME
+ type = Phone.TYPE_HOME;
+ }
+ }
+ if (isFax) {
+ if (type == Phone.TYPE_HOME) {
+ type = Phone.TYPE_FAX_HOME;
+ } else if (type == Phone.TYPE_WORK) {
+ type = Phone.TYPE_FAX_WORK;
+ } else if (type == Phone.TYPE_OTHER) {
+ type = Phone.TYPE_OTHER_FAX;
+ }
+ }
+ if (type == Phone.TYPE_CUSTOM) {
+ return label;
+ } else {
+ return type;
+ }
+ }
+
+ public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
+ // TODO: check the following.
+ // - it may violate vCard spec
+ // - it may contain non-ASCII characters
+ //
+ // TODO: use vcardType
+ return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
+ sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
+ }
+
+ public static String[] sortNameElements(int vcardType,
+ String familyName, String middleName, String givenName) {
+ String[] list = new String[3];
+ switch (VCardConfig.getNameOrderType(vcardType)) {
+ case VCardConfig.NAME_ORDER_JAPANESE:
+ // TODO: Should handle Ascii case?
+ list[0] = familyName;
+ list[1] = middleName;
+ list[2] = givenName;
+ break;
+ case VCardConfig.NAME_ORDER_EUROPE:
+ list[0] = middleName;
+ list[1] = givenName;
+ list[2] = familyName;
+ break;
+ default:
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ break;
+ }
+ return list;
+ }
+
+ /**
+ * Inserts postal data into the builder object.
+ *
+ * Note that the data structure of ContactsContract is different from that defined in vCard.
+ * So some conversion may be performed in this method. See also
+ * {{@link #getVCardPostalElements(ContentValues)}
+ */
+ public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
+ final ContentProviderOperation.Builder builder,
+ final ContactStruct.PostalData postalData) {
+ builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
+
+ builder.withValue(StructuredPostal.TYPE, postalData.type);
+ if (postalData.type == StructuredPostal.TYPE_CUSTOM) {
+ builder.withValue(StructuredPostal.LABEL, postalData.label);
+ }
+
+ builder.withValue(StructuredPostal.POBOX, postalData.pobox);
+ // Extended address is dropped since there's no relevant entry in ContactsContract.
+ builder.withValue(StructuredPostal.STREET, postalData.street);
+ builder.withValue(StructuredPostal.CITY, postalData.localty);
+ builder.withValue(StructuredPostal.REGION, postalData.region);
+ builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
+ builder.withValue(StructuredPostal.COUNTRY, postalData.country);
+
+ builder.withValue(StructuredPostal.FORMATTED_ADDRESS,
+ postalData.getFormattedAddress(vcardType));
+ if (postalData.isPrimary) {
+ builder.withValue(Data.IS_PRIMARY, 1);
+ }
+ }
+
+ /**
+ * Returns String[] containing address information based on vCard spec
+ * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
+ * All String objects are non-null ("" is used when the relevant data is empty).
+ *
+ * Note that the data structure of ContactsContract is different from that defined in vCard.
+ * So some conversion may be performed in this method. See also
+ * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
+ * android.content.ContentProviderOperation.Builder,
+ * android.pim.vcard.ContactStruct.PostalData)}
+ */
+ public static String[] getVCardPostalElements(ContentValues contentValues) {
+ String[] dataArray = new String[7];
+ dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
+ if (dataArray[0] == null) {
+ dataArray[0] = "";
+ }
+ // Extended addr. There's no relevant data in ContactsContract.
+ dataArray[1] = "";
+ dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
+ if (dataArray[2] == null) {
+ dataArray[2] = "";
+ }
+ // Assume that localty == city
+ dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
+ if (dataArray[3] == null) {
+ dataArray[3] = "";
+ }
+ String region = contentValues.getAsString(StructuredPostal.REGION);
+ if (!TextUtils.isEmpty(region)) {
+ dataArray[4] = region;
+ } else {
+ dataArray[4] = "";
+ }
+ dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
+ if (dataArray[5] == null) {
+ dataArray[5] = "";
+ }
+ dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
+ if (dataArray[6] == null) {
+ dataArray[6] = "";
+ }
+
+ return dataArray;
+ }
+
+ public static String constructNameFromElements(int nameOrderType,
+ String familyName, String middleName, String givenName) {
+ return constructNameFromElements(nameOrderType, familyName, middleName, givenName,
+ null, null);
+ }
+
+ public static String constructNameFromElements(int nameOrderType,
+ String familyName, String middleName, String givenName,
+ String prefix, String suffix) {
+ StringBuilder builder = new StringBuilder();
+ String[] nameList = sortNameElements(nameOrderType,
+ familyName, middleName, givenName);
+ boolean first = true;
+ if (!TextUtils.isEmpty(prefix)) {
+ first = false;
+ builder.append(prefix);
+ }
+ for (String namePart : nameList) {
+ if (!TextUtils.isEmpty(namePart)) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(' ');
+ }
+ builder.append(namePart);
+ }
+ }
+ if (!TextUtils.isEmpty(suffix)) {
+ if (!first) {
+ builder.append(' ');
+ }
+ builder.append(suffix);
+ }
+ return builder.toString();
+ }
+
+ public static boolean containsOnlyAscii(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return true;
+ }
+
+ final int length = str.length();
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x126;
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int c = str.codePointAt(i);
+ if (c < asciiFirst || asciiLast < c) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This is useful since vCard 3.0 often requires the ("X-") properties and groups
+ * should contain only alphabets, digits, and hyphen.
+ *
+ * Note: It is already known some devices (wrongly) outputs properties with characters
+ * which should not be in the field. One example is "X-GOOGLE TALK". We appreciate
+ * such kind of input but must never output it unless the target is very specific
+ * to the device which is able to parse the malformed input.
+ */
+ public static boolean containsOnlyAlphaDigitHyphen(String str) {
+ if (TextUtils.isEmpty(str)) {
+ return true;
+ }
+
+ final int lowerAlphabetFirst = 0x41; // included ('A')
+ final int lowerAlphabetLast = 0x5b; // not included ('[')
+ final int upperAlphabetFirst = 0x61; // included ('a')
+ final int upperAlphabetLast = 0x7b; // included ('{')
+ final int digitFirst = 0x30; // included ('0')
+ final int digitLast = 0x39; // included ('9')
+ final int hyphen = '-';
+ final int length = str.length();
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int codepoint = str.codePointAt(i);
+ if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetLast) ||
+ (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetLast) ||
+ (digitFirst <= codepoint && codepoint < digitLast) ||
+ (codepoint == hyphen))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // TODO: Replace wth the method in Base64 class.
+ private static char PAD = '=';
+ private static final char[] ENCODE64 = {
+ 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+ 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+ 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+ 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+ };
+
+ static public String encodeBase64(byte[] data) {
+ if (data == null) {
+ return "";
+ }
+
+ char[] charBuffer = new char[(data.length + 2) / 3 * 4];
+ int position = 0;
+ int _3byte = 0;
+ for (int i=0; i<data.length-2; i+=3) {
+ _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
+ charBuffer[position++] = ENCODE64[_3byte >> 18];
+ charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
+ charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
+ charBuffer[position++] = ENCODE64[_3byte & 0x3F];
+ }
+ switch(data.length % 3) {
+ case 1: // [111111][11 0000][0000 00][000000]
+ _3byte = ((data[data.length-1] & 0xFF) << 16);
+ charBuffer[position++] = ENCODE64[_3byte >> 18];
+ charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
+ charBuffer[position++] = PAD;
+ charBuffer[position++] = PAD;
+ break;
+ case 2: // [111111][11 1111][1111 00][000000]
+ _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
+ charBuffer[position++] = ENCODE64[_3byte >> 18];
+ charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
+ charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
+ charBuffer[position++] = PAD;
+ break;
+ }
+
+ return new String(charBuffer);
+ }
+
+ static public String toHalfWidthString(String orgString) {
+ if (TextUtils.isEmpty(orgString)) {
+ return null;
+ }
+ StringBuilder builder = new StringBuilder();
+ int length = orgString.length();
+ for (int i = 0; i < length; i++) {
+ // All Japanese character is able to be expressed by char.
+ // Do not need to use String#codepPointAt().
+ char ch = orgString.charAt(i);
+ CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
+ if (halfWidthText != null) {
+ builder.append(halfWidthText);
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ private VCardUtils() {
+ }
+}
+
+/**
+ * TextUtils especially for Japanese.
+ * TODO: make this in android.text in the future
+ */
+class JapaneseUtils {
+ static private final Map<Character, String> sHalfWidthMap =
+ new HashMap<Character, String>();
+
+ static {
+ // There's no logical mapping rule in Unicode. Sigh.
+ sHalfWidthMap.put('\u3001', "\uFF64");
+ sHalfWidthMap.put('\u3002', "\uFF61");
+ sHalfWidthMap.put('\u300C', "\uFF62");
+ sHalfWidthMap.put('\u300D', "\uFF63");
+ sHalfWidthMap.put('\u301C', "~");
+ sHalfWidthMap.put('\u3041', "\uFF67");
+ sHalfWidthMap.put('\u3042', "\uFF71");
+ sHalfWidthMap.put('\u3043', "\uFF68");
+ sHalfWidthMap.put('\u3044', "\uFF72");
+ sHalfWidthMap.put('\u3045', "\uFF69");
+ sHalfWidthMap.put('\u3046', "\uFF73");
+ sHalfWidthMap.put('\u3047', "\uFF6A");
+ sHalfWidthMap.put('\u3048', "\uFF74");
+ sHalfWidthMap.put('\u3049', "\uFF6B");
+ sHalfWidthMap.put('\u304A', "\uFF75");
+ sHalfWidthMap.put('\u304B', "\uFF76");
+ sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u304D', "\uFF77");
+ sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u304F', "\uFF78");
+ sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u3051', "\uFF79");
+ sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u3053', "\uFF7A");
+ sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u3055', "\uFF7B");
+ sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u3057', "\uFF7C");
+ sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u3059', "\uFF7D");
+ sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u305B', "\uFF7E");
+ sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u305D', "\uFF7F");
+ sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u305F', "\uFF80");
+ sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u3061', "\uFF81");
+ sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u3063', "\uFF6F");
+ sHalfWidthMap.put('\u3064', "\uFF82");
+ sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u3066', "\uFF83");
+ sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u3068', "\uFF84");
+ sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u306A', "\uFF85");
+ sHalfWidthMap.put('\u306B', "\uFF86");
+ sHalfWidthMap.put('\u306C', "\uFF87");
+ sHalfWidthMap.put('\u306D', "\uFF88");
+ sHalfWidthMap.put('\u306E', "\uFF89");
+ sHalfWidthMap.put('\u306F', "\uFF8A");
+ sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u3072', "\uFF8B");
+ sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u3075', "\uFF8C");
+ sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u3078', "\uFF8D");
+ sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u307B', "\uFF8E");
+ sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u307E', "\uFF8F");
+ sHalfWidthMap.put('\u307F', "\uFF90");
+ sHalfWidthMap.put('\u3080', "\uFF91");
+ sHalfWidthMap.put('\u3081', "\uFF92");
+ sHalfWidthMap.put('\u3082', "\uFF93");
+ sHalfWidthMap.put('\u3083', "\uFF6C");
+ sHalfWidthMap.put('\u3084', "\uFF94");
+ sHalfWidthMap.put('\u3085', "\uFF6D");
+ sHalfWidthMap.put('\u3086', "\uFF95");
+ sHalfWidthMap.put('\u3087', "\uFF6E");
+ sHalfWidthMap.put('\u3088', "\uFF96");
+ sHalfWidthMap.put('\u3089', "\uFF97");
+ sHalfWidthMap.put('\u308A', "\uFF98");
+ sHalfWidthMap.put('\u308B', "\uFF99");
+ sHalfWidthMap.put('\u308C', "\uFF9A");
+ sHalfWidthMap.put('\u308D', "\uFF9B");
+ sHalfWidthMap.put('\u308E', "\uFF9C");
+ sHalfWidthMap.put('\u308F', "\uFF9C");
+ sHalfWidthMap.put('\u3090', "\uFF72");
+ sHalfWidthMap.put('\u3091', "\uFF74");
+ sHalfWidthMap.put('\u3092', "\uFF66");
+ sHalfWidthMap.put('\u3093', "\uFF9D");
+ sHalfWidthMap.put('\u309B', "\uFF9E");
+ sHalfWidthMap.put('\u309C', "\uFF9F");
+ sHalfWidthMap.put('\u30A1', "\uFF67");
+ sHalfWidthMap.put('\u30A2', "\uFF71");
+ sHalfWidthMap.put('\u30A3', "\uFF68");
+ sHalfWidthMap.put('\u30A4', "\uFF72");
+ sHalfWidthMap.put('\u30A5', "\uFF69");
+ sHalfWidthMap.put('\u30A6', "\uFF73");
+ sHalfWidthMap.put('\u30A7', "\uFF6A");
+ sHalfWidthMap.put('\u30A8', "\uFF74");
+ sHalfWidthMap.put('\u30A9', "\uFF6B");
+ sHalfWidthMap.put('\u30AA', "\uFF75");
+ sHalfWidthMap.put('\u30AB', "\uFF76");
+ sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u30AD', "\uFF77");
+ sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u30AF', "\uFF78");
+ sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u30B1', "\uFF79");
+ sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u30B3', "\uFF7A");
+ sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u30B5', "\uFF7B");
+ sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u30B7', "\uFF7C");
+ sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u30B9', "\uFF7D");
+ sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u30BB', "\uFF7E");
+ sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u30BD', "\uFF7F");
+ sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u30BF', "\uFF80");
+ sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u30C1', "\uFF81");
+ sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u30C3', "\uFF6F");
+ sHalfWidthMap.put('\u30C4', "\uFF82");
+ sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u30C6', "\uFF83");
+ sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u30C8', "\uFF84");
+ sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u30CA', "\uFF85");
+ sHalfWidthMap.put('\u30CB', "\uFF86");
+ sHalfWidthMap.put('\u30CC', "\uFF87");
+ sHalfWidthMap.put('\u30CD', "\uFF88");
+ sHalfWidthMap.put('\u30CE', "\uFF89");
+ sHalfWidthMap.put('\u30CF', "\uFF8A");
+ sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u30D2', "\uFF8B");
+ sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u30D5', "\uFF8C");
+ sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u30D8', "\uFF8D");
+ sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u30DB', "\uFF8E");
+ sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u30DE', "\uFF8F");
+ sHalfWidthMap.put('\u30DF', "\uFF90");
+ sHalfWidthMap.put('\u30E0', "\uFF91");
+ sHalfWidthMap.put('\u30E1', "\uFF92");
+ sHalfWidthMap.put('\u30E2', "\uFF93");
+ sHalfWidthMap.put('\u30E3', "\uFF6C");
+ sHalfWidthMap.put('\u30E4', "\uFF94");
+ sHalfWidthMap.put('\u30E5', "\uFF6D");
+ sHalfWidthMap.put('\u30E6', "\uFF95");
+ sHalfWidthMap.put('\u30E7', "\uFF6E");
+ sHalfWidthMap.put('\u30E8', "\uFF96");
+ sHalfWidthMap.put('\u30E9', "\uFF97");
+ sHalfWidthMap.put('\u30EA', "\uFF98");
+ sHalfWidthMap.put('\u30EB', "\uFF99");
+ sHalfWidthMap.put('\u30EC', "\uFF9A");
+ sHalfWidthMap.put('\u30ED', "\uFF9B");
+ sHalfWidthMap.put('\u30EE', "\uFF9C");
+ sHalfWidthMap.put('\u30EF', "\uFF9C");
+ sHalfWidthMap.put('\u30F0', "\uFF72");
+ sHalfWidthMap.put('\u30F1', "\uFF74");
+ sHalfWidthMap.put('\u30F2', "\uFF66");
+ sHalfWidthMap.put('\u30F3', "\uFF9D");
+ sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
+ sHalfWidthMap.put('\u30F5', "\uFF76");
+ sHalfWidthMap.put('\u30F6', "\uFF79");
+ sHalfWidthMap.put('\u30FB', "\uFF65");
+ sHalfWidthMap.put('\u30FC', "\uFF70");
+ sHalfWidthMap.put('\uFF01', "!");
+ sHalfWidthMap.put('\uFF02', "\"");
+ sHalfWidthMap.put('\uFF03', "#");
+ sHalfWidthMap.put('\uFF04', "$");
+ sHalfWidthMap.put('\uFF05', "%");
+ sHalfWidthMap.put('\uFF06', "&");
+ sHalfWidthMap.put('\uFF07', "'");
+ sHalfWidthMap.put('\uFF08', "(");
+ sHalfWidthMap.put('\uFF09', ")");
+ sHalfWidthMap.put('\uFF0A', "*");
+ sHalfWidthMap.put('\uFF0B', "+");
+ sHalfWidthMap.put('\uFF0C', ",");
+ sHalfWidthMap.put('\uFF0D', "-");
+ sHalfWidthMap.put('\uFF0E', ".");
+ sHalfWidthMap.put('\uFF0F', "/");
+ sHalfWidthMap.put('\uFF10', "0");
+ sHalfWidthMap.put('\uFF11', "1");
+ sHalfWidthMap.put('\uFF12', "2");
+ sHalfWidthMap.put('\uFF13', "3");
+ sHalfWidthMap.put('\uFF14', "4");
+ sHalfWidthMap.put('\uFF15', "5");
+ sHalfWidthMap.put('\uFF16', "6");
+ sHalfWidthMap.put('\uFF17', "7");
+ sHalfWidthMap.put('\uFF18', "8");
+ sHalfWidthMap.put('\uFF19', "9");
+ sHalfWidthMap.put('\uFF1A', ":");
+ sHalfWidthMap.put('\uFF1B', ";");
+ sHalfWidthMap.put('\uFF1C', "<");
+ sHalfWidthMap.put('\uFF1D', "=");
+ sHalfWidthMap.put('\uFF1E', ">");
+ sHalfWidthMap.put('\uFF1F', "?");
+ sHalfWidthMap.put('\uFF20', "@");
+ sHalfWidthMap.put('\uFF21', "A");
+ sHalfWidthMap.put('\uFF22', "B");
+ sHalfWidthMap.put('\uFF23', "C");
+ sHalfWidthMap.put('\uFF24', "D");
+ sHalfWidthMap.put('\uFF25', "E");
+ sHalfWidthMap.put('\uFF26', "F");
+ sHalfWidthMap.put('\uFF27', "G");
+ sHalfWidthMap.put('\uFF28', "H");
+ sHalfWidthMap.put('\uFF29', "I");
+ sHalfWidthMap.put('\uFF2A', "J");
+ sHalfWidthMap.put('\uFF2B', "K");
+ sHalfWidthMap.put('\uFF2C', "L");
+ sHalfWidthMap.put('\uFF2D', "M");
+ sHalfWidthMap.put('\uFF2E', "N");
+ sHalfWidthMap.put('\uFF2F', "O");
+ sHalfWidthMap.put('\uFF30', "P");
+ sHalfWidthMap.put('\uFF31', "Q");
+ sHalfWidthMap.put('\uFF32', "R");
+ sHalfWidthMap.put('\uFF33', "S");
+ sHalfWidthMap.put('\uFF34', "T");
+ sHalfWidthMap.put('\uFF35', "U");
+ sHalfWidthMap.put('\uFF36', "V");
+ sHalfWidthMap.put('\uFF37', "W");
+ sHalfWidthMap.put('\uFF38', "X");
+ sHalfWidthMap.put('\uFF39', "Y");
+ sHalfWidthMap.put('\uFF3A', "Z");
+ sHalfWidthMap.put('\uFF3B', "[");
+ sHalfWidthMap.put('\uFF3C', "\\");
+ sHalfWidthMap.put('\uFF3D', "]");
+ sHalfWidthMap.put('\uFF3E', "^");
+ sHalfWidthMap.put('\uFF3F', "_");
+ sHalfWidthMap.put('\uFF41', "a");
+ sHalfWidthMap.put('\uFF42', "b");
+ sHalfWidthMap.put('\uFF43', "c");
+ sHalfWidthMap.put('\uFF44', "d");
+ sHalfWidthMap.put('\uFF45', "e");
+ sHalfWidthMap.put('\uFF46', "f");
+ sHalfWidthMap.put('\uFF47', "g");
+ sHalfWidthMap.put('\uFF48', "h");
+ sHalfWidthMap.put('\uFF49', "i");
+ sHalfWidthMap.put('\uFF4A', "j");
+ sHalfWidthMap.put('\uFF4B', "k");
+ sHalfWidthMap.put('\uFF4C', "l");
+ sHalfWidthMap.put('\uFF4D', "m");
+ sHalfWidthMap.put('\uFF4E', "n");
+ sHalfWidthMap.put('\uFF4F', "o");
+ sHalfWidthMap.put('\uFF50', "p");
+ sHalfWidthMap.put('\uFF51', "q");
+ sHalfWidthMap.put('\uFF52', "r");
+ sHalfWidthMap.put('\uFF53', "s");
+ sHalfWidthMap.put('\uFF54', "t");
+ sHalfWidthMap.put('\uFF55', "u");
+ sHalfWidthMap.put('\uFF56', "v");
+ sHalfWidthMap.put('\uFF57', "w");
+ sHalfWidthMap.put('\uFF58', "x");
+ sHalfWidthMap.put('\uFF59', "y");
+ sHalfWidthMap.put('\uFF5A', "z");
+ sHalfWidthMap.put('\uFF5B', "{");
+ sHalfWidthMap.put('\uFF5C', "|");
+ sHalfWidthMap.put('\uFF5D', "}");
+ sHalfWidthMap.put('\uFF5E', "~");
+ sHalfWidthMap.put('\uFF61', "\uFF61");
+ sHalfWidthMap.put('\uFF62', "\uFF62");
+ sHalfWidthMap.put('\uFF63', "\uFF63");
+ sHalfWidthMap.put('\uFF64', "\uFF64");
+ sHalfWidthMap.put('\uFF65', "\uFF65");
+ sHalfWidthMap.put('\uFF66', "\uFF66");
+ sHalfWidthMap.put('\uFF67', "\uFF67");
+ sHalfWidthMap.put('\uFF68', "\uFF68");
+ sHalfWidthMap.put('\uFF69', "\uFF69");
+ sHalfWidthMap.put('\uFF6A', "\uFF6A");
+ sHalfWidthMap.put('\uFF6B', "\uFF6B");
+ sHalfWidthMap.put('\uFF6C', "\uFF6C");
+ sHalfWidthMap.put('\uFF6D', "\uFF6D");
+ sHalfWidthMap.put('\uFF6E', "\uFF6E");
+ sHalfWidthMap.put('\uFF6F', "\uFF6F");
+ sHalfWidthMap.put('\uFF70', "\uFF70");
+ sHalfWidthMap.put('\uFF71', "\uFF71");
+ sHalfWidthMap.put('\uFF72', "\uFF72");
+ sHalfWidthMap.put('\uFF73', "\uFF73");
+ sHalfWidthMap.put('\uFF74', "\uFF74");
+ sHalfWidthMap.put('\uFF75', "\uFF75");
+ sHalfWidthMap.put('\uFF76', "\uFF76");
+ sHalfWidthMap.put('\uFF77', "\uFF77");
+ sHalfWidthMap.put('\uFF78', "\uFF78");
+ sHalfWidthMap.put('\uFF79', "\uFF79");
+ sHalfWidthMap.put('\uFF7A', "\uFF7A");
+ sHalfWidthMap.put('\uFF7B', "\uFF7B");
+ sHalfWidthMap.put('\uFF7C', "\uFF7C");
+ sHalfWidthMap.put('\uFF7D', "\uFF7D");
+ sHalfWidthMap.put('\uFF7E', "\uFF7E");
+ sHalfWidthMap.put('\uFF7F', "\uFF7F");
+ sHalfWidthMap.put('\uFF80', "\uFF80");
+ sHalfWidthMap.put('\uFF81', "\uFF81");
+ sHalfWidthMap.put('\uFF82', "\uFF82");
+ sHalfWidthMap.put('\uFF83', "\uFF83");
+ sHalfWidthMap.put('\uFF84', "\uFF84");
+ sHalfWidthMap.put('\uFF85', "\uFF85");
+ sHalfWidthMap.put('\uFF86', "\uFF86");
+ sHalfWidthMap.put('\uFF87', "\uFF87");
+ sHalfWidthMap.put('\uFF88', "\uFF88");
+ sHalfWidthMap.put('\uFF89', "\uFF89");
+ sHalfWidthMap.put('\uFF8A', "\uFF8A");
+ sHalfWidthMap.put('\uFF8B', "\uFF8B");
+ sHalfWidthMap.put('\uFF8C', "\uFF8C");
+ sHalfWidthMap.put('\uFF8D', "\uFF8D");
+ sHalfWidthMap.put('\uFF8E', "\uFF8E");
+ sHalfWidthMap.put('\uFF8F', "\uFF8F");
+ sHalfWidthMap.put('\uFF90', "\uFF90");
+ sHalfWidthMap.put('\uFF91', "\uFF91");
+ sHalfWidthMap.put('\uFF92', "\uFF92");
+ sHalfWidthMap.put('\uFF93', "\uFF93");
+ sHalfWidthMap.put('\uFF94', "\uFF94");
+ sHalfWidthMap.put('\uFF95', "\uFF95");
+ sHalfWidthMap.put('\uFF96', "\uFF96");
+ sHalfWidthMap.put('\uFF97', "\uFF97");
+ sHalfWidthMap.put('\uFF98', "\uFF98");
+ sHalfWidthMap.put('\uFF99', "\uFF99");
+ sHalfWidthMap.put('\uFF9A', "\uFF9A");
+ sHalfWidthMap.put('\uFF9B', "\uFF9B");
+ sHalfWidthMap.put('\uFF9C', "\uFF9C");
+ sHalfWidthMap.put('\uFF9D', "\uFF9D");
+ sHalfWidthMap.put('\uFF9E', "\uFF9E");
+ sHalfWidthMap.put('\uFF9F', "\uFF9F");
+ sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
+ }
+
+ /**
+ * Return half-width version of that character if possible. Return null if not possible
+ * @param ch input character
+ * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+ */
+ public static CharSequence tryGetHalfWidthText(char ch) {
+ if (sHalfWidthMap.containsKey(ch)) {
+ return sHalfWidthMap.get(ch);
+ } else {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcard/VCardDataBuilder.java b/core/java/android/syncml/pim/vcard/VCardDataBuilder.java
index a0513f1..f2a2733 100644
--- a/core/java/android/syncml/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/syncml/pim/vcard/VCardDataBuilder.java
@@ -28,6 +28,7 @@ import android.syncml.pim.PropertyNode;
import android.syncml.pim.VBuilder;
import android.syncml.pim.VNode;
import android.syncml.pim.VParser;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
@@ -403,7 +404,10 @@ public class VCardDataBuilder implements VBuilder {
String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
String encoding = paramMap.getAsString("ENCODING");
- if (targetCharset == null || targetCharset.length() == 0) {
+ Log.d("@@@", String.format("targetCharset: \"%s\", encoding: \"%s\"",
+ targetCharset, encoding));
+
+ if (TextUtils.isEmpty(targetCharset)) {
targetCharset = mTargetCharset;
}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser.java b/core/java/android/syncml/pim/vcard/VCardParser.java
index 6dad852d..9a590dd 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser.java
@@ -17,7 +17,6 @@
package android.syncml.pim.vcard;
import android.syncml.pim.VDataBuilder;
-import android.syncml.pim.VParser;
import android.util.Config;
import android.util.Log;