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
This commit is contained in:
Daisuke Miyakawa
2009-10-08 19:57:36 -07:00
12 changed files with 1686 additions and 1112 deletions

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Normal Guy
ORG:Company;Organization;Devision;Room;Sheet No.
TITLE:Excellent Janitor
END:VCARD

View File

@@ -0,0 +1,15 @@
BEGIN:VCARD
VERSION:2.1
FN:Smith
TEL;HOME:1
TEL;WORK;PREF:2
TEL;ISDN:3
EMAIL;PREF;HOME:test@example.com
EMAIL;CELL;PREF:test2@examination.com
ORG:Company
TITLE:Engineer
ORG:Mystery
TITLE:Blogger
ORG:Poetry
TITLE:Poet
END:VCARD

View File

@@ -0,0 +1,6 @@
BEGIN:VCARD
VERSION:2.1
FN:Nice Guy
TITLE:Cool Title
ORG:Marverous;Perfect;Great;Good;Bad;Poor
END:VCARD

View File

@@ -16,6 +16,7 @@
package com.android.unit_tests.vcard;
import android.content.ContentValues;
import android.pim.vcard.ContactStruct;
import org.apache.commons.codec.binary.Base64;
@@ -28,7 +29,12 @@ import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* @hide old class just for test
* Previously used in main vCard handling code but now exists only for testing.
*
* Especially useful for testing parser code (VCardParser), since all properties can be
* checked via this class unlike {@link ContactStruct}, which only emits the result of
* interpretation of the content of each vCard. We cannot know whether vCard parser or
* ContactStruct is wrong withouth this class.
*/
public class PropertyNode {
public String propName;

View File

@@ -18,7 +18,7 @@ package com.android.unit_tests.vcard;
import java.util.ArrayList;
/**
* @hide old class. Just for testing
* Previously used in main vCard handling code but now exists only for testing.
*/
public class VNode {
public String VName;

View File

@@ -36,7 +36,8 @@ import java.util.List;
* Maybe several vcard instance, so use vNodeList to store.
* VNode: standy by a vcard instance.
* PropertyNode: standy by a property line of a card.
* @hide old class, just for testing use
*
* Previously used in main vCard handling code but now exists only for testing.
*/
public class VNodeBuilder implements VCardBuilder {
static private String LOG_TAG = "VDATABuilder";