diff options
| author | Daisuke Miyakawa <dmiyakawa@google.com> | 2009-10-08 19:57:36 -0700 |
|---|---|---|
| committer | Daisuke Miyakawa <dmiyakawa@google.com> | 2009-10-08 19:57:36 -0700 |
| commit | 5a016488da5ff6c1ecdff0b1febd836cc1711531 (patch) | |
| tree | c17d5060edea4607b46cee52f641cf4e7ae2be4b /core/java/android/pim | |
| parent | b1fdb1d33600f0b2f199068254900b240572e472 (diff) | |
| parent | 5c3e687965a49e4e54196a049337544a6eed61d9 (diff) | |
| download | frameworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.zip frameworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.tar.gz frameworks_base-5a016488da5ff6c1ecdff0b1febd836cc1711531.tar.bz2 | |
Merge commit 'refs/changes/08/29008/8' of ssh://dmiyakawa@android-git.corp.google.com:29418/platform/frameworks/base into cleanup_import
Conflicts:
core/java/android/pim/vcard/ContactStruct.java
Diffstat (limited to 'core/java/android/pim')
| -rw-r--r-- | core/java/android/pim/vcard/ContactStruct.java | 628 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardConfig.java | 16 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardParser_V21.java | 48 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardParser_V30.java | 23 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardUtils.java | 38 |
5 files changed, 414 insertions, 339 deletions
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/ContactStruct.java index 36e5e23..edd8121 100644 --- a/core/java/android/pim/vcard/ContactStruct.java +++ b/core/java/android/pim/vcard/ContactStruct.java @@ -38,6 +38,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.telephony.PhoneNumberUtils; +import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.util.Log; @@ -46,7 +47,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -55,11 +55,11 @@ import java.util.Map; */ public class ContactStruct { private static final String LOG_TAG = "vcard.ContactStruct"; - + // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ" // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol} private static final Map<String, Integer> sImMap = new HashMap<String, Integer>(); - + static { sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM); sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN); @@ -90,7 +90,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof PhoneData) { + if (!(obj instanceof PhoneData)) { return false; } PhoneData phoneData = (PhoneData)obj; @@ -125,7 +125,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof EmailData) { + if (!(obj instanceof EmailData)) { return false; } EmailData emailData = (EmailData)obj; @@ -202,7 +202,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof PostalData) { + if (!(obj instanceof PostalData)) { return false; } PostalData postalData = (PostalData)obj; @@ -256,35 +256,44 @@ public class ContactStruct { */ static public class OrganizationData { public final int type; - public final String companyName; - // can be changed in some VCard format. - public String positionName; + // non-final is Intended: we may change the values since this info is separated into + // two parts in vCard: "ORG" + "TITLE". + public String companyName; + public String departmentName; + public String titleName; // isPrimary is changable only when there's no appropriate one existing in // the original VCard. public boolean isPrimary; - public OrganizationData(int type, String companyName, String positionName, + public OrganizationData(int type, + String companyName, + String departmentName, + String titleName, boolean isPrimary) { this.type = type; this.companyName = companyName; - this.positionName = positionName; + this.departmentName = departmentName; + this.titleName = titleName; this.isPrimary = isPrimary; } @Override public boolean equals(Object obj) { - if (obj instanceof OrganizationData) { + if (!(obj instanceof OrganizationData)) { return false; } OrganizationData organization = (OrganizationData)obj; - return (type == organization.type && companyName.equals(organization.companyName) && - positionName.equals(organization.positionName) && + return (type == organization.type && + TextUtils.equals(companyName, organization.companyName) && + TextUtils.equals(departmentName, organization.departmentName) && + TextUtils.equals(titleName, organization.titleName) && isPrimary == organization.isPrimary); } - + @Override public String toString() { - return String.format("type: %d, company: %s, position: %s, isPrimary: %s", - type, companyName, positionName, isPrimary); + return String.format( + "type: %d, company: %s, department: %s, title: %s, isPrimary: %s", + type, companyName, departmentName, titleName, isPrimary); } } @@ -304,7 +313,7 @@ public class ContactStruct { @Override public boolean equals(Object obj) { - if (obj instanceof ImData) { + if (!(obj instanceof ImData)) { return false; } ImData imData = (ImData)obj; @@ -327,11 +336,32 @@ public class ContactStruct { public final int type; public final String formatName; // used when type is not defined in ContactsContract. public final byte[] photoBytes; + public final boolean isPrimary; - public PhotoData(int type, String formatName, byte[] photoBytes) { + public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) { this.type = type; this.formatName = formatName; this.photoBytes = photoBytes; + this.isPrimary = isPrimary; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PhotoData)) { + return false; + } + PhotoData photoData = (PhotoData)obj; + return (type == photoData.type && + (formatName == null ? (photoData.formatName == null) : + formatName.equals(photoData.formatName)) && + (Arrays.equals(photoBytes, photoData.photoBytes)) && + (isPrimary == photoData.isPrimary)); + } + + @Override + public String toString() { + return String.format("type: %d, format: %s: size: %d, isPrimary: %s", + type, formatName, photoBytes.length, isPrimary); } } @@ -421,15 +451,6 @@ public class ContactStruct { private final int mVCardType; private final Account mAccount; - // Each Column of four properties has ISPRIMARY field - // (See android.provider.Contacts) - // If false even after the parsing loop, we choose the first entry as a "primary" - // entry. - private boolean mPrefIsSet_Address; - private boolean mPrefIsSet_Phone; - private boolean mPrefIsSet_Email; - private boolean mPrefIsSet_Organization; - public ContactStruct() { this(VCardConfig.VCARD_TYPE_V21_GENERIC); } @@ -454,12 +475,14 @@ public class ContactStruct { String phoneticGivenName, String pheneticFamilyName, String phoneticMiddleName, + List<String> nicknameList, List<byte[]> photoBytesList, - List<String> notes, + List<String> noteList, List<PhoneData> phoneList, List<EmailData> emailList, List<PostalData> postalList, List<OrganizationData> organizationList, + List<ImData> imList, List<PhotoData> photoList, List<String> websiteList) { this(VCardConfig.VCARD_TYPE_DEFAULT); @@ -470,159 +493,16 @@ public class ContactStruct { mPhoneticGivenName = givenName; mPhoneticFamilyName = familyName; mPhoneticMiddleName = middleName; + mNickNameList = nicknameList; + mNoteList = noteList; mEmailList = emailList; mPostalList = postalList; mOrganizationList = organizationList; + mImList = imList; mPhotoList = photoList; mWebsiteList = websiteList; } - // All getter methods should be used carefully, since they may change - // in the future as of 2009-09-24, on which I cannot be sure this structure - // is completely consolidated. - // When we are sure we will no longer change them, we'll be happy to - // make it complete public (withouth @hide tag) - // - // Also note that these getter methods should be used only after - // all properties being pushed into this object. If not, incorrect - // value will "be stored in the local cache and" be returned to you. - - /** - * @hide - */ - public String getFamilyName() { - return mFamilyName; - } - - /** - * @hide - */ - public String getGivenName() { - return mGivenName; - } - - /** - * @hide - */ - public String getMiddleName() { - return mMiddleName; - } - - /** - * @hide - */ - public String getPrefix() { - return mPrefix; - } - - /** - * @hide - */ - public String getSuffix() { - return mSuffix; - } - - /** - * @hide - */ - public String getFullName() { - return mFullName; - } - - /** - * @hide - */ - public String getPhoneticFamilyName() { - return mPhoneticFamilyName; - } - - /** - * @hide - */ - public String getPhoneticGivenName() { - return mPhoneticGivenName; - } - - /** - * @hide - */ - public String getPhoneticMiddleName() { - return mPhoneticMiddleName; - } - - /** - * @hide - */ - public String getPhoneticFullName() { - return mPhoneticFullName; - } - - /** - * @hide - */ - public final List<String> getNickNameList() { - return mNickNameList; - } - - /** - * @hide - */ - public String getDisplayName() { - if (mDisplayName == null) { - constructDisplayName(); - } - return mDisplayName; - } - - /** - * @hide - */ - public String getBirthday() { - return mBirthday; - } - - /** - * @hide - */ - public final List<PhotoData> getPhotoList() { - return mPhotoList; - } - - /** - * @hide - */ - public final List<String> getNotes() { - return mNoteList; - } - - /** - * @hide - */ - public final List<PhoneData> getPhoneList() { - return mPhoneList; - } - - /** - * @hide - */ - public final List<EmailData> getEmailList() { - return mEmailList; - } - - /** - * @hide - */ - public final List<PostalData> getPostalList() { - return mPostalList; - } - - /** - * @hide - */ - public final List<OrganizationData> getOrganizationList() { - return mOrganizationList; - } - /** * Add a phone info to phoneList. * @param data phone number @@ -643,10 +523,24 @@ public class ContactStruct { } } - PhoneData phoneData = new PhoneData(type, - PhoneNumberUtils.formatNumber(builder.toString()), - label, isPrimary); - + final String formattedPhoneNumber; + { + final String rawPhoneNumber = builder.toString(); + if (VCardConfig.isJapaneseDevice(mVCardType)) { + // As of 2009-10-07, there's no formatNumber() which accepts + // the second argument and returns String directly. + final SpannableStringBuilder tmpBuilder = + new SpannableStringBuilder(rawPhoneNumber); + PhoneNumberUtils.formatNumber(tmpBuilder, PhoneNumberUtils.FORMAT_JAPAN); + formattedPhoneNumber = tmpBuilder.toString(); + } else { + // There's no information available on vCard side. Depend on the default + // behavior, which may cause problem in the future when the additional format + // rule is supported (e.g. PhoneNumberUtils.FORMAT_KLINGON) + formattedPhoneNumber = PhoneNumberUtils.formatNumber(rawPhoneNumber); + } + } + PhoneData phoneData = new PhoneData(type, formattedPhoneNumber, label, isPrimary); mPhoneList.add(phoneData); } @@ -666,19 +560,116 @@ public class ContactStruct { private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){ if (mPostalList == null) { - mPostalList = new ArrayList<PostalData>(); + mPostalList = new ArrayList<PostalData>(0); } mPostalList.add(new PostalData(type, propValueList, label, isPrimary)); } - private void addOrganization(int type, final String companyName, - final String positionName, boolean isPrimary) { + /** + * Should be called via {@link #handleOrgValue(int, List, boolean)} or + * {@link #handleTitleValue(String)}. + */ + private void addNewOrganization(int type, final String companyName, + final String departmentName, + final String titleName, boolean isPrimary) { if (mOrganizationList == null) { mOrganizationList = new ArrayList<OrganizationData>(); } - mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary)); + mOrganizationList.add(new OrganizationData(type, companyName, + departmentName, titleName, isPrimary)); } + + private static final List<String> sEmptyList = new ArrayList<String>(0); + /** + * Set "ORG" related values to the appropriate data. If there's more than one + * OrganizationData objects, this input data are attached to the last one which does not + * have valid values (not including empty but only null). If there's no + * OrganizationData object, a new OrganizationData is created, whose title is set to null. + */ + private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) { + if (orgList == null) { + orgList = sEmptyList; + } + final String companyName; + final String departmentName; + final int size = orgList.size(); + switch (size) { + case 0: { + companyName = ""; + departmentName = null; + break; + } + case 1: { + companyName = orgList.get(0); + departmentName = null; + break; + } + default: { // More than 1. + companyName = orgList.get(0); + // We're not sure which is the correct string for department. + // In order to keep all the data, concatinate the rest of elements. + StringBuilder builder = new StringBuilder(); + for (int i = 1; i < size; i++) { + if (i > 1) { + builder.append(' '); + } + builder.append(orgList.get(i)); + } + departmentName = builder.toString(); + } + } + if (mOrganizationList == null) { + // Create new first organization entry, with "null" title which may be + // added via handleTitleValue(). + addNewOrganization(type, companyName, departmentName, null, isPrimary); + return; + } + for (OrganizationData organizationData : mOrganizationList) { + // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty. + // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null. + if (organizationData.companyName == null && + organizationData.departmentName == null) { + // Probably the "TITLE" property comes before the "ORG" property via + // handleTitleLine(). + organizationData.companyName = companyName; + organizationData.departmentName = departmentName; + organizationData.isPrimary = isPrimary; + return; + } + } + // No OrganizatioData is available. Create another one, with "null" title, which may be + // added via handleTitleValue(). + addNewOrganization(type, companyName, departmentName, null, isPrimary); + } + + private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK; + + /** + * Set "title" value to the appropriate data. If there's more than one + * OrganizationData objects, this input is attached to the last one which does not + * have valid title value (not including empty but only null). If there's no + * OrganizationData object, a new OrganizationData is created, whose company name is + * set to null. + */ + private void handleTitleValue(final String title) { + if (mOrganizationList == null) { + // Create new first organization entry, with "null" other info, which may be + // added via handleOrgValue(). + addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); + return; + } + for (OrganizationData organizationData : mOrganizationList) { + if (organizationData.titleName == null) { + organizationData.titleName = title; + return; + } + } + // No Organization is available. Create another one, with "null" other info, which may be + // added via handleOrgValue(). + addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); + } + private void addIm(int type, String data, String label, boolean isPrimary) { if (mImList == null) { mImList = new ArrayList<ImData>(); @@ -693,43 +684,14 @@ public class ContactStruct { mNoteList.add(note); } - private void addPhotoBytes(String formatName, byte[] photoBytes) { + private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) { if (mPhotoList == null) { mPhotoList = new ArrayList<PhotoData>(1); } - final PhotoData photoData = new PhotoData(0, null, photoBytes); + final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary); mPhotoList.add(photoData); } - /** - * Set "position" value to the appropriate data. If there's more than one - * OrganizationData objects, the value is set to the last one. If there's no - * OrganizationData object, a new OrganizationData is created, whose company name is - * empty. - * - * TODO: incomplete logic. fix this: - * - * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not - * know how to handle it in general cases... - * ---- - * TITLE:Software Engineer - * ORG:Google - * ---- - */ - private void setPosition(String positionValue) { - if (mOrganizationList == null) { - mOrganizationList = new ArrayList<OrganizationData>(); - } - int size = mOrganizationList.size(); - if (size == 0) { - addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER, - "", null, false); - size = 1; - } - OrganizationData lastData = mOrganizationList.get(size - 1); - lastData.positionName = positionValue; - } - @SuppressWarnings("fallthrough") private void handleNProperty(List<String> elems) { // Family, Given, Middle, Prefix, Suffix. (1 - 5) @@ -813,7 +775,14 @@ public class ContactStruct { } else if (propName.equals("SOUND")) { Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) { - handlePhoneticNameFromSound(propValueList); + // As of 2009-10-08, Parser side does not split a property value into separated + // values using ';' (in other words, propValueList.size() == 1), + // which is correct behavior from the view of vCard 2.1. + // But we want it to be separated, so do the separation here. + final List<String> phoneticNameList = + VCardUtils.constructListFromValue(propValue, + VCardConfig.isV30(mVCardType)); + handlePhoneticNameFromSound(phoneticNameList); } else { // Ignore this field since Android cannot understand what it is. } @@ -836,9 +805,7 @@ public class ContactStruct { if (typeCollection != null) { for (String typeString : typeCollection) { typeString = typeString.toUpperCase(); - if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) { - // Only first "PREF" is considered. - mPrefIsSet_Address = true; + if (typeString.equals(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { type = StructuredPostal.TYPE_HOME; @@ -879,9 +846,7 @@ public class ContactStruct { if (typeCollection != null) { for (String typeString : typeCollection) { typeString = typeString.toUpperCase(); - if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) { - // Only first "PREF" is considered. - mPrefIsSet_Email = true; + if (typeString.equals(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) { type = Email.TYPE_HOME; @@ -907,48 +872,44 @@ public class ContactStruct { addEmail(type, propValue, label, isPrimary); } else if (propName.equals("ORG")) { // vCard specification does not specify other types. - int type = Organization.TYPE_WORK; + final int type = Organization.TYPE_WORK; boolean isPrimary = false; - Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); if (typeCollection != null) { for (String typeString : typeCollection) { - if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) { - // vCard specification officially does not have PREF in ORG. - // This is just for safety. - mPrefIsSet_Organization = true; + if (typeString.equals(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } } } - - StringBuilder builder = new StringBuilder(); - for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) { - builder.append(iter.next()); - if (iter.hasNext()) { - builder.append(' '); - } - } - addOrganization(type, builder.toString(), "", isPrimary); + handleOrgValue(type, propValueList, isPrimary); } else if (propName.equals("TITLE")) { - setPosition(propValue); + handleTitleValue(propValue); } else if (propName.equals("ROLE")) { - setPosition(propValue); + // This conflicts with TITLE. Ignore for now... + // handleTitleValue(propValue); } else if (propName.equals("PHOTO") || propName.equals("LOGO")) { - String formatName = null; - Collection<String> typeCollection = paramMap.get("TYPE"); - if (typeCollection != null) { - formatName = typeCollection.iterator().next(); - } Collection<String> paramMapValue = paramMap.get("VALUE"); if (paramMapValue != null && paramMapValue.contains("URL")) { // Currently we do not have appropriate example for testing this case. } else { - addPhotoBytes(formatName, propBytes); + final Collection<String> typeCollection = paramMap.get("TYPE"); + String formatName = null; + boolean isPrimary = false; + if (typeCollection != null) { + for (String typeValue : typeCollection) { + if (Constants.ATTR_TYPE_PREF.equals(typeValue)) { + isPrimary = true; + } else if (formatName == null){ + formatName = typeValue; + } + } + } + addPhotoBytes(formatName, propBytes, isPrimary); } } else if (propName.equals("TEL")) { - Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); - Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection); + final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE); + final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection); final int type; final String label; if (typeObject instanceof Integer) { @@ -960,9 +921,7 @@ public class ContactStruct { } final boolean isPrimary; - if (!mPrefIsSet_Phone && typeCollection != null && - typeCollection.contains(Constants.ATTR_TYPE_PREF)) { - mPrefIsSet_Phone = true; + if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } else { isPrimary = false; @@ -975,9 +934,7 @@ public class ContactStruct { int type = Phone.TYPE_OTHER; final String label = null; final boolean isPrimary; - if (!mPrefIsSet_Phone && typeCollection != null && - typeCollection.contains(Constants.ATTR_TYPE_PREF)) { - mPrefIsSet_Phone = true; + if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_PREF)) { isPrimary = true; } else { isPrimary = false; @@ -1048,7 +1005,10 @@ public class ContactStruct { * Construct the display name. The constructed data must not be null. */ private void constructDisplayName() { - if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) { + // FullName (created via "FN" or "NAME" field) is prefered. + if (!TextUtils.isEmpty(mFullName)) { + mDisplayName = mFullName; + } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) { StringBuilder builder = new StringBuilder(); List<String> nameList; switch (VCardConfig.getNameOrderType(mVCardType)) { @@ -1079,8 +1039,6 @@ public class ContactStruct { } } mDisplayName = builder.toString(); - } else if (!TextUtils.isEmpty(mFullName)) { - mDisplayName = mFullName; } else if (!(TextUtils.isEmpty(mPhoneticFamilyName) && TextUtils.isEmpty(mPhoneticGivenName))) { mDisplayName = VCardUtils.constructNameFromElements(mVCardType, @@ -1103,25 +1061,10 @@ public class ContactStruct { */ public void consolidateFields() { constructDisplayName(); - + if (mPhoneticFullName != null) { mPhoneticFullName = mPhoneticFullName.trim(); } - - // If there is no "PREF", we choose the first entries as primary. - if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) { - mPhoneList.get(0).isPrimary = true; - } - - if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) { - mPostalList.get(0).isPrimary = true; - } - if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) { - mEmailList.get(0).isPrimary = true; - } - if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) { - mOrganizationList.get(0).isPrimary = true; - } } // From GoogleSource.java in Contacts app. @@ -1181,22 +1124,16 @@ public class ContactStruct { } if (mNickNameList != null && mNickNameList.size() > 0) { - boolean first = true; for (String nickName : mNickNameList) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE); - builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT); builder.withValue(Nickname.NAME, nickName); - if (first) { - builder.withValue(Data.IS_PRIMARY, 1); - first = false; - } operationList.add(builder.build()); } } - + if (mPhoneList != null) { for (PhoneData phoneData : mPhoneList) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); @@ -1209,30 +1146,34 @@ public class ContactStruct { } builder.withValue(Phone.NUMBER, phoneData.data); if (phoneData.isPrimary) { - builder.withValue(Data.IS_PRIMARY, 1); + builder.withValue(Phone.IS_PRIMARY, 1); } operationList.add(builder.build()); } } - + if (mOrganizationList != null) { - boolean first = true; for (OrganizationData organizationData : mOrganizationList) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE); - - // Currently, we do not use TYPE_CUSTOM. builder.withValue(Organization.TYPE, organizationData.type); - builder.withValue(Organization.COMPANY, organizationData.companyName); - builder.withValue(Organization.TITLE, organizationData.positionName); - if (first) { - builder.withValue(Data.IS_PRIMARY, 1); + if (organizationData.companyName != null) { + builder.withValue(Organization.COMPANY, organizationData.companyName); + } + if (organizationData.departmentName != null) { + builder.withValue(Organization.DEPARTMENT, organizationData.departmentName); + } + if (organizationData.titleName != null) { + builder.withValue(Organization.TITLE, organizationData.titleName); + } + if (organizationData.isPrimary) { + builder.withValue(Organization.IS_PRIMARY, 1); } operationList.add(builder.build()); } } - + if (mEmailList != null) { for (EmailData emailData : mEmailList) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); @@ -1265,7 +1206,6 @@ public class ContactStruct { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Im.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); - builder.withValue(Im.TYPE, imData.type); if (imData.type == Im.TYPE_CUSTOM) { builder.withValue(Im.LABEL, imData.label); @@ -1282,22 +1222,19 @@ public class ContactStruct { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Note.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE); - builder.withValue(Note.NOTE, note); operationList.add(builder.build()); } } - + if (mPhotoList != null) { - boolean first = true; for (PhotoData photoData : mPhotoList) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); builder.withValue(Photo.PHOTO, photoData.photoBytes); - if (first) { - builder.withValue(Data.IS_PRIMARY, 1); - first = false; + if (photoData.isPrimary) { + builder.withValue(Photo.IS_PRIMARY, 1); } operationList.add(builder.build()); } @@ -1310,12 +1247,12 @@ public class ContactStruct { builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE); builder.withValue(Website.URL, website); // There's no information about the type of URL in vCard. - // We use TYPE_HOME for safety. - builder.withValue(Website.TYPE, Website.TYPE_HOME); + // We use TYPE_HOMEPAGE for safety. + builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE); operationList.add(builder.build()); } } - + if (!TextUtils.isEmpty(mBirthday)) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Event.RAW_CONTACT_ID, 0); @@ -1364,4 +1301,99 @@ public class ContactStruct { return ""; } } + + // All getter methods should be used carefully, since they may change + // in the future as of 2009-10-05, on which I cannot be sure this structure + // is completely consolidated. + // + // Also note that these getter methods should be used only after + // all properties being pushed into this object. If not, incorrect + // value will "be stored in the local cache and" be returned to you. + + public String getFamilyName() { + return mFamilyName; + } + + public String getGivenName() { + return mGivenName; + } + + public String getMiddleName() { + return mMiddleName; + } + + public String getPrefix() { + return mPrefix; + } + + public String getSuffix() { + return mSuffix; + } + + public String getFullName() { + return mFullName; + } + + public String getPhoneticFamilyName() { + return mPhoneticFamilyName; + } + + public String getPhoneticGivenName() { + return mPhoneticGivenName; + } + + public String getPhoneticMiddleName() { + return mPhoneticMiddleName; + } + + public String getPhoneticFullName() { + return mPhoneticFullName; + } + + public final List<String> getNickNameList() { + return mNickNameList; + } + + public String getBirthday() { + return mBirthday; + } + + public final List<String> getNotes() { + return mNoteList; + } + + public final List<PhoneData> getPhoneList() { + return mPhoneList; + } + + public final List<EmailData> getEmailList() { + return mEmailList; + } + + public final List<PostalData> getPostalList() { + return mPostalList; + } + + public final List<OrganizationData> getOrganizationList() { + return mOrganizationList; + } + + public final List<ImData> getImList() { + return mImList; + } + + public final List<PhotoData> getPhotoList() { + return mPhotoList; + } + + public final List<String> getWebsiteList() { + return mWebsiteList; + } + + public String getDisplayName() { + if (mDisplayName == null) { + constructDisplayName(); + } + return mDisplayName; + } } diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java index 68cd0df..e606440 100644 --- a/core/java/android/pim/vcard/VCardConfig.java +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -41,8 +41,8 @@ public class VCardConfig { // TODO: make the other codes use this flag public static final boolean IGNORE_CASE_EXCEPT_VALUE = true; - private static final int FLAG_V21 = 0; - private static final int FLAG_V30 = 1; + public static final int FLAG_V21 = 0; + public static final int FLAG_V30 = 1; // 0x2 is reserved for the future use ... @@ -105,8 +105,16 @@ public class VCardConfig { * behavior around this flag in the future. Do not use this flag without any reason. */ public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000; - - // VCard types + + // Note: if you really want to add additional flag(s) incompatible with the main source tree, + // please use flags from 0x0001000 to 0x00080000. + // + // If we notice we cannot manage flags in just one integer, we'll change this interface and + // create/use a complete class, not just an integer, with similar API (but imcompatible). + // + // Again, please be aware that this API is intentionally hidden ~= unstable! + + //// The followings are VCard types available from importer/exporter. //// /** * General vCard format with the version 2.1. Uses UTF-8 for the charset. diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java index 974fca8..a72b96f 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -144,10 +144,14 @@ public class VCardParser_V21 extends VCardParser { } } - protected String getVersion() { + protected int getVersion() { + return VCardConfig.FLAG_V21; + } + + protected String getVersionString() { return "2.1"; } - + /** * @return true when the propertyName is a valid property name. */ @@ -356,7 +360,7 @@ public class VCardParser_V21 extends VCardParser { * / [groups "."] "ADR" [params] ":" addressparts CRLF * / [groups "."] "ORG" [params] ":" orgparts CRLF * / [groups "."] "N" [params] ":" nameparts CRLF - * / [groups "."] "AGENT" [params] ":" vcard CRLF + * / [groups "."] "AGENT" [params] ":" vcard CRLF */ protected boolean parseItem() throws IOException, VCardException { mEncoding = sDefaultEncoding; @@ -392,9 +396,10 @@ 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(getVersionString())) { throw new VCardVersionException("Incompatible version: " + - propertyValue + " != " + getVersion()); + propertyValue + " != " + getVersionString()); } start = System.currentTimeMillis(); handlePropertyValue(propertyName, propertyValue); @@ -761,32 +766,11 @@ public class VCardParser_V21 extends VCardParser { } if (mBuilder != null) { - StringBuilder builder = new StringBuilder(); - ArrayList<String> list = new ArrayList<String>(); - int length = propertyValue.length(); - for (int i = 0; i < length; i++) { - char ch = propertyValue.charAt(i); - if (ch == '\\' && i < length - 1) { - char nextCh = propertyValue.charAt(i + 1); - String unescapedString = maybeUnescapeCharacter(nextCh); - if (unescapedString != null) { - builder.append(unescapedString); - i++; - } else { - builder.append(ch); - } - } else if (ch == ';') { - list.add(builder.toString()); - builder = new StringBuilder(); - } else { - builder.append(ch); - } - } - list.add(builder.toString()); - mBuilder.propertyValues(list); + mBuilder.propertyValues(VCardUtils.constructListFromValue( + propertyValue, (getVersion() == VCardConfig.FLAG_V30))); } } - + /** * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all. * @@ -819,12 +803,16 @@ public class VCardParser_V21 extends VCardParser { protected String maybeUnescapeText(String text) { return text; } - + /** * Returns unescaped String if the character should be unescaped. Return null otherwise. * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be. */ protected String maybeUnescapeCharacter(char ch) { + return unescapeCharacter(ch); + } + + public static String unescapeCharacter(char ch) { // Original vCard 2.1 specification does not allow transformation // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of // this class allowed them, so keep it as is. diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java index 384649a..8267ff5 100644 --- a/core/java/android/pim/vcard/VCardParser_V30.java +++ b/core/java/android/pim/vcard/VCardParser_V30.java @@ -48,12 +48,17 @@ public class VCardParser_V30 extends VCardParser_V21 { private String mPreviousLine; private boolean mEmittedAgentWarning = false; - + @Override - protected String getVersion() { + protected int getVersion() { + return VCardConfig.FLAG_V30; + } + + @Override + protected String getVersionString() { return Constants.VERSION_V30; } - + @Override protected boolean isValidPropertyName(String propertyName) { if (!(sAcceptablePropsWithParam.contains(propertyName) || @@ -284,6 +289,10 @@ public class VCardParser_V30 extends VCardParser_V21 { */ @Override protected String maybeUnescapeText(String text) { + return unescapeText(text); + } + + public static String unescapeText(String text) { StringBuilder builder = new StringBuilder(); int length = text.length(); for (int i = 0; i < length; i++) { @@ -299,15 +308,19 @@ public class VCardParser_V30 extends VCardParser_V21 { builder.append(ch); } } - return builder.toString(); + return builder.toString(); } @Override protected String maybeUnescapeCharacter(char ch) { + return unescapeCharacter(ch); + } + + public static String unescapeCharacter(char ch) { if (ch == 'n' || ch == 'N') { return "\n"; } else { return String.valueOf(ch); - } + } } } diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java index 4f50103..bfd4a4e 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -22,9 +22,11 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.text.TextUtils; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -187,7 +189,10 @@ public class VCardUtils { } builder.withValue(StructuredPostal.POBOX, postalData.pobox); - // Extended address is dropped since there's no relevant entry in ContactsContract. + // TODO: Japanese phone seems to use this field for expressing all the address including + // region, city, etc. Not sure we're ok to store them into NEIGHBORHOOD, while it would be + // better than dropping them all. + builder.withValue(StructuredPostal.NEIGHBORHOOD, postalData.extendedAddress); builder.withValue(StructuredPostal.STREET, postalData.street); builder.withValue(StructuredPostal.CITY, postalData.localty); builder.withValue(StructuredPostal.REGION, postalData.region); @@ -282,7 +287,36 @@ public class VCardUtils { } return builder.toString(); } - + + public static List<String> constructListFromValue(final String value, + final boolean isV30) { + final List<String> list = new ArrayList<String>(); + StringBuilder builder = new StringBuilder(); + int length = value.length(); + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if (ch == '\\' && i < length - 1) { + char nextCh = value.charAt(i + 1); + final String unescapedString = + (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) : + VCardParser_V21.unescapeCharacter(nextCh)); + if (unescapedString != null) { + builder.append(unescapedString); + i++; + } else { + builder.append(ch); + } + } else if (ch == ';') { + list.add(builder.toString()); + builder = new StringBuilder(); + } else { + builder.append(ch); + } + } + list.add(builder.toString()); + return list; + } + public static boolean containsOnlyPrintableAscii(String str) { if (TextUtils.isEmpty(str)) { return true; |
