Clone no longer depends on LineageOS gitlinks; the libraries used by the app live in this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
2694 lines
99 KiB
Java
2694 lines
99 KiB
Java
/*
|
|
* 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 com.android.vcard;
|
|
|
|
import com.android.vcard.VCardUtils.PhoneNumberUtilsPort;
|
|
|
|
import android.accounts.Account;
|
|
import android.content.ContentProviderOperation;
|
|
import android.content.ContentResolver;
|
|
import android.net.Uri;
|
|
import android.provider.ContactsContract;
|
|
import android.provider.ContactsContract.CommonDataKinds.Email;
|
|
import android.provider.ContactsContract.CommonDataKinds.Event;
|
|
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
|
import android.provider.ContactsContract.CommonDataKinds.Im;
|
|
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.SipAddress;
|
|
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
|
|
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
|
|
import android.provider.ContactsContract.CommonDataKinds.Website;
|
|
import android.provider.ContactsContract.Contacts;
|
|
import android.provider.ContactsContract.Data;
|
|
import android.provider.ContactsContract.RawContacts;
|
|
import android.telephony.PhoneNumberUtils;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.util.Pair;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* Represents one vCard entry, which should start with "BEGIN:VCARD" and end
|
|
* with "END:VCARD". This class is for bridging between real vCard data and
|
|
* Android's {@link ContactsContract}, which means some aspects of vCard are
|
|
* dropped before this object being constructed. Raw vCard data should be first
|
|
* supplied with {@link #addProperty(VCardProperty)}. After supplying all data,
|
|
* user should call {@link #consolidateFields()} to prepare some additional
|
|
* information which is constructable from supplied raw data. TODO: preserve raw
|
|
* data using {@link VCardProperty}. If it may just waste memory, this at least
|
|
* should contain them when it cannot convert vCard as a string to Android's
|
|
* Contacts representation. Those raw properties should _not_ be used for
|
|
* {@link #isIgnorable()}.
|
|
*/
|
|
public class VCardEntry {
|
|
private static final String LOG_TAG = VCardConstants.LOG_TAG;
|
|
|
|
private static final int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
|
|
|
|
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
|
|
|
|
static {
|
|
sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
|
|
sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
|
|
sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
|
|
sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
|
|
sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
|
|
sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
|
|
sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
|
|
sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
|
|
Im.PROTOCOL_GOOGLE_TALK);
|
|
}
|
|
|
|
/**
|
|
* Whether to insert this VCardEntry as RawContacts.STARRED
|
|
*/
|
|
private boolean mStarred = false;
|
|
|
|
public void setStarred(boolean val) {
|
|
mStarred = val;
|
|
}
|
|
public boolean getStarred() {
|
|
return mStarred;
|
|
}
|
|
|
|
public enum EntryLabel {
|
|
NAME,
|
|
PHONE,
|
|
EMAIL,
|
|
POSTAL_ADDRESS,
|
|
ORGANIZATION,
|
|
IM,
|
|
PHOTO,
|
|
WEBSITE,
|
|
SIP,
|
|
NICKNAME,
|
|
NOTE,
|
|
BIRTHDAY,
|
|
ANNIVERSARY,
|
|
ANDROID_CUSTOM
|
|
}
|
|
|
|
public static interface EntryElement {
|
|
// Also need to inherit toString(), equals().
|
|
public EntryLabel getEntryLabel();
|
|
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex);
|
|
|
|
public boolean isEmpty();
|
|
}
|
|
|
|
// TODO: vCard 4.0 logically has multiple formatted names and we need to
|
|
// select the most preferable one using PREF parameter.
|
|
//
|
|
// e.g. (based on rev.13)
|
|
// FN;PREF=1:John M. Doe
|
|
// FN;PREF=2:John Doe
|
|
// FN;PREF=3;John
|
|
public static class NameData implements EntryElement {
|
|
private String mFamily;
|
|
private String mGiven;
|
|
private String mMiddle;
|
|
private String mPrefix;
|
|
private String mSuffix;
|
|
|
|
// Used only when no family nor given name is found.
|
|
private String mFormatted;
|
|
|
|
private String mPhoneticFamily;
|
|
private String mPhoneticGiven;
|
|
private String mPhoneticMiddle;
|
|
|
|
// For "SORT-STRING" in vCard 3.0.
|
|
private String mSortString;
|
|
|
|
/**
|
|
* Not in vCard but for {@link StructuredName#DISPLAY_NAME}. This field
|
|
* is constructed by VCardEntry on demand. Consider using
|
|
* {@link VCardEntry#getDisplayName()}.
|
|
*/
|
|
// This field should reflect the other Elem fields like Email,
|
|
// PostalAddress, etc., while
|
|
// This is static class which cannot see other data. Thus we ask
|
|
// VCardEntry to populate it.
|
|
public String displayName;
|
|
|
|
public boolean emptyStructuredName() {
|
|
return TextUtils.isEmpty(mFamily) && TextUtils.isEmpty(mGiven)
|
|
&& TextUtils.isEmpty(mMiddle) && TextUtils.isEmpty(mPrefix)
|
|
&& TextUtils.isEmpty(mSuffix);
|
|
}
|
|
|
|
public boolean emptyPhoneticStructuredName() {
|
|
return TextUtils.isEmpty(mPhoneticFamily) && TextUtils.isEmpty(mPhoneticGiven)
|
|
&& TextUtils.isEmpty(mPhoneticMiddle);
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
|
|
|
|
if (!TextUtils.isEmpty(mGiven)) {
|
|
builder.withValue(StructuredName.GIVEN_NAME, mGiven);
|
|
}
|
|
if (!TextUtils.isEmpty(mFamily)) {
|
|
builder.withValue(StructuredName.FAMILY_NAME, mFamily);
|
|
}
|
|
if (!TextUtils.isEmpty(mMiddle)) {
|
|
builder.withValue(StructuredName.MIDDLE_NAME, mMiddle);
|
|
}
|
|
if (!TextUtils.isEmpty(mPrefix)) {
|
|
builder.withValue(StructuredName.PREFIX, mPrefix);
|
|
}
|
|
if (!TextUtils.isEmpty(mSuffix)) {
|
|
builder.withValue(StructuredName.SUFFIX, mSuffix);
|
|
}
|
|
|
|
boolean phoneticNameSpecified = false;
|
|
|
|
if (!TextUtils.isEmpty(mPhoneticGiven)) {
|
|
builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGiven);
|
|
phoneticNameSpecified = true;
|
|
}
|
|
if (!TextUtils.isEmpty(mPhoneticFamily)) {
|
|
builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamily);
|
|
phoneticNameSpecified = true;
|
|
}
|
|
if (!TextUtils.isEmpty(mPhoneticMiddle)) {
|
|
builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddle);
|
|
phoneticNameSpecified = true;
|
|
}
|
|
|
|
// SORT-STRING is used only when phonetic names aren't specified in
|
|
// the original vCard.
|
|
if (!phoneticNameSpecified) {
|
|
builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mSortString);
|
|
}
|
|
|
|
builder.withValue(StructuredName.DISPLAY_NAME, displayName);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return (TextUtils.isEmpty(mFamily) && TextUtils.isEmpty(mMiddle)
|
|
&& TextUtils.isEmpty(mGiven) && TextUtils.isEmpty(mPrefix)
|
|
&& TextUtils.isEmpty(mSuffix) && TextUtils.isEmpty(mFormatted)
|
|
&& TextUtils.isEmpty(mPhoneticFamily) && TextUtils.isEmpty(mPhoneticMiddle)
|
|
&& TextUtils.isEmpty(mPhoneticGiven) && TextUtils.isEmpty(mSortString));
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof NameData)) {
|
|
return false;
|
|
}
|
|
NameData nameData = (NameData) obj;
|
|
|
|
return (TextUtils.equals(mFamily, nameData.mFamily)
|
|
&& TextUtils.equals(mMiddle, nameData.mMiddle)
|
|
&& TextUtils.equals(mGiven, nameData.mGiven)
|
|
&& TextUtils.equals(mPrefix, nameData.mPrefix)
|
|
&& TextUtils.equals(mSuffix, nameData.mSuffix)
|
|
&& TextUtils.equals(mFormatted, nameData.mFormatted)
|
|
&& TextUtils.equals(mPhoneticFamily, nameData.mPhoneticFamily)
|
|
&& TextUtils.equals(mPhoneticMiddle, nameData.mPhoneticMiddle)
|
|
&& TextUtils.equals(mPhoneticGiven, nameData.mPhoneticGiven)
|
|
&& TextUtils.equals(mSortString, nameData.mSortString));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final String[] hashTargets = new String[] {mFamily, mMiddle, mGiven, mPrefix, mSuffix,
|
|
mFormatted, mPhoneticFamily, mPhoneticMiddle,
|
|
mPhoneticGiven, mSortString};
|
|
int hash = 0;
|
|
for (String hashTarget : hashTargets) {
|
|
hash = hash * 31 + (hashTarget != null ? hashTarget.hashCode() : 0);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("family: %s, given: %s, middle: %s, prefix: %s, suffix: %s",
|
|
mFamily, mGiven, mMiddle, mPrefix, mSuffix);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.NAME;
|
|
}
|
|
|
|
public String getFamily() {
|
|
return mFamily;
|
|
}
|
|
|
|
public String getMiddle() {
|
|
return mMiddle;
|
|
}
|
|
|
|
public String getGiven() {
|
|
return mGiven;
|
|
}
|
|
|
|
public String getPrefix() {
|
|
return mPrefix;
|
|
}
|
|
|
|
public String getSuffix() {
|
|
return mSuffix;
|
|
}
|
|
|
|
public String getFormatted() {
|
|
return mFormatted;
|
|
}
|
|
|
|
public String getSortString() {
|
|
return mSortString;
|
|
}
|
|
|
|
/** @hide Just for testing. */
|
|
public void setFamily(String family) { mFamily = family; }
|
|
/** @hide Just for testing. */
|
|
public void setMiddle(String middle) { mMiddle = middle; }
|
|
/** @hide Just for testing. */
|
|
public void setGiven(String given) { mGiven = given; }
|
|
/** @hide Just for testing. */
|
|
public void setPrefix(String prefix) { mPrefix = prefix; }
|
|
/** @hide Just for testing. */
|
|
public void setSuffix(String suffix) { mSuffix = suffix; }
|
|
}
|
|
|
|
public static class PhoneData implements EntryElement {
|
|
private final String mNumber;
|
|
private final int mType;
|
|
private final String mLabel;
|
|
|
|
// isPrimary is (not final but) changable, only when there's no
|
|
// appropriate one existing
|
|
// in the original VCard.
|
|
private boolean mIsPrimary;
|
|
|
|
public PhoneData(String data, int type, String label, boolean isPrimary) {
|
|
mNumber = data;
|
|
mType = type;
|
|
mLabel = label;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Phone.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
|
|
|
|
builder.withValue(Phone.TYPE, mType);
|
|
if (mType == Phone.TYPE_CUSTOM) {
|
|
builder.withValue(Phone.LABEL, mLabel);
|
|
}
|
|
builder.withValue(Phone.NUMBER, mNumber);
|
|
if (mIsPrimary) {
|
|
builder.withValue(Phone.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mNumber);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof PhoneData)) {
|
|
return false;
|
|
}
|
|
PhoneData phoneData = (PhoneData) obj;
|
|
return (mType == phoneData.mType
|
|
&& TextUtils.equals(mNumber, phoneData.mNumber)
|
|
&& TextUtils.equals(mLabel, phoneData.mLabel)
|
|
&& (mIsPrimary == phoneData.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + (mNumber != null ? mNumber.hashCode() : 0);
|
|
hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("type: %d, data: %s, label: %s, isPrimary: %s", mType, mNumber,
|
|
mLabel, mIsPrimary);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.PHONE;
|
|
}
|
|
|
|
public String getNumber() {
|
|
return mNumber;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public String getLabel() {
|
|
return mLabel;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class EmailData implements EntryElement {
|
|
private final String mAddress;
|
|
private final int mType;
|
|
// Used only when TYPE is TYPE_CUSTOM.
|
|
private final String mLabel;
|
|
private final boolean mIsPrimary;
|
|
|
|
public EmailData(String data, int type, String label, boolean isPrimary) {
|
|
mType = type;
|
|
mAddress = data;
|
|
mLabel = label;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Email.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
|
|
|
|
builder.withValue(Email.TYPE, mType);
|
|
if (mType == Email.TYPE_CUSTOM) {
|
|
builder.withValue(Email.LABEL, mLabel);
|
|
}
|
|
builder.withValue(Email.DATA, mAddress);
|
|
if (mIsPrimary) {
|
|
builder.withValue(Data.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mAddress);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof EmailData)) {
|
|
return false;
|
|
}
|
|
EmailData emailData = (EmailData) obj;
|
|
return (mType == emailData.mType
|
|
&& TextUtils.equals(mAddress, emailData.mAddress)
|
|
&& TextUtils.equals(mLabel, emailData.mLabel)
|
|
&& (mIsPrimary == emailData.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0);
|
|
hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("type: %d, data: %s, label: %s, isPrimary: %s", mType, mAddress,
|
|
mLabel, mIsPrimary);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.EMAIL;
|
|
}
|
|
|
|
public String getAddress() {
|
|
return mAddress;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public String getLabel() {
|
|
return mLabel;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class PostalData implements EntryElement {
|
|
// Determined by vCard specification.
|
|
// - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
|
|
private static final int ADDR_MAX_DATA_SIZE = 7;
|
|
private final String mPobox;
|
|
private final String mExtendedAddress;
|
|
private final String mStreet;
|
|
private final String mLocalty;
|
|
private final String mRegion;
|
|
private final String mPostalCode;
|
|
private final String mCountry;
|
|
private final int mType;
|
|
private final String mLabel;
|
|
private boolean mIsPrimary;
|
|
|
|
/** We keep this for {@link StructuredPostal#FORMATTED_ADDRESS} */
|
|
// TODO: need better way to construct formatted address.
|
|
private int mVCardType;
|
|
|
|
public PostalData(String pobox, String extendedAddress, String street, String localty,
|
|
String region, String postalCode, String country, int type, String label,
|
|
boolean isPrimary, int vcardType) {
|
|
mType = type;
|
|
mPobox = pobox;
|
|
mExtendedAddress = extendedAddress;
|
|
mStreet = street;
|
|
mLocalty = localty;
|
|
mRegion = region;
|
|
mPostalCode = postalCode;
|
|
mCountry = country;
|
|
mLabel = label;
|
|
mIsPrimary = isPrimary;
|
|
mVCardType = vcardType;
|
|
}
|
|
|
|
/**
|
|
* Accepts raw propertyValueList in vCard and constructs PostalData.
|
|
*/
|
|
public static PostalData constructPostalData(final List<String> propValueList,
|
|
final int type, final String label, boolean isPrimary, int vcardType) {
|
|
final String[] 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;
|
|
}
|
|
|
|
return new PostalData(dataArray[0], dataArray[1], dataArray[2], dataArray[3],
|
|
dataArray[4], dataArray[5], dataArray[6], type, label, isPrimary, vcardType);
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
|
|
|
|
builder.withValue(StructuredPostal.TYPE, mType);
|
|
if (mType == StructuredPostal.TYPE_CUSTOM) {
|
|
builder.withValue(StructuredPostal.LABEL, mLabel);
|
|
}
|
|
|
|
final String streetString;
|
|
if (TextUtils.isEmpty(mStreet)) {
|
|
if (TextUtils.isEmpty(mExtendedAddress)) {
|
|
streetString = null;
|
|
} else {
|
|
streetString = mExtendedAddress;
|
|
}
|
|
} else {
|
|
if (TextUtils.isEmpty(mExtendedAddress)) {
|
|
streetString = mStreet;
|
|
} else {
|
|
streetString = mStreet + " " + mExtendedAddress;
|
|
}
|
|
}
|
|
builder.withValue(StructuredPostal.POBOX, mPobox);
|
|
builder.withValue(StructuredPostal.STREET, streetString);
|
|
builder.withValue(StructuredPostal.CITY, mLocalty);
|
|
builder.withValue(StructuredPostal.REGION, mRegion);
|
|
builder.withValue(StructuredPostal.POSTCODE, mPostalCode);
|
|
builder.withValue(StructuredPostal.COUNTRY, mCountry);
|
|
|
|
builder.withValue(StructuredPostal.FORMATTED_ADDRESS, getFormattedAddress(mVCardType));
|
|
if (mIsPrimary) {
|
|
builder.withValue(Data.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
public String getFormattedAddress(final int vcardType) {
|
|
StringBuilder builder = new StringBuilder();
|
|
boolean empty = true;
|
|
final String[] dataArray = new String[] {
|
|
mPobox, mExtendedAddress, mStreet, mLocalty, mRegion, mPostalCode, mCountry
|
|
};
|
|
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(' ');
|
|
} else {
|
|
empty = false;
|
|
}
|
|
builder.append(addressPart);
|
|
}
|
|
}
|
|
} else {
|
|
for (int i = 0; i < ADDR_MAX_DATA_SIZE; i++) {
|
|
String addressPart = dataArray[i];
|
|
if (!TextUtils.isEmpty(addressPart)) {
|
|
if (!empty) {
|
|
builder.append(' ');
|
|
} else {
|
|
empty = false;
|
|
}
|
|
builder.append(addressPart);
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder.toString().trim();
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return (TextUtils.isEmpty(mPobox)
|
|
&& TextUtils.isEmpty(mExtendedAddress)
|
|
&& TextUtils.isEmpty(mStreet)
|
|
&& TextUtils.isEmpty(mLocalty)
|
|
&& TextUtils.isEmpty(mRegion)
|
|
&& TextUtils.isEmpty(mPostalCode)
|
|
&& TextUtils.isEmpty(mCountry));
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof PostalData)) {
|
|
return false;
|
|
}
|
|
final PostalData postalData = (PostalData) obj;
|
|
return (mType == postalData.mType)
|
|
&& (mType == StructuredPostal.TYPE_CUSTOM ? TextUtils.equals(mLabel,
|
|
postalData.mLabel) : true)
|
|
&& (mIsPrimary == postalData.mIsPrimary)
|
|
&& TextUtils.equals(mPobox, postalData.mPobox)
|
|
&& TextUtils.equals(mExtendedAddress, postalData.mExtendedAddress)
|
|
&& TextUtils.equals(mStreet, postalData.mStreet)
|
|
&& TextUtils.equals(mLocalty, postalData.mLocalty)
|
|
&& TextUtils.equals(mRegion, postalData.mRegion)
|
|
&& TextUtils.equals(mPostalCode, postalData.mPostalCode)
|
|
&& TextUtils.equals(mCountry, postalData.mCountry);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
|
|
final String[] hashTargets = new String[] {mPobox, mExtendedAddress, mStreet,
|
|
mLocalty, mRegion, mPostalCode, mCountry};
|
|
for (String hashTarget : hashTargets) {
|
|
hash = hash * 31 + (hashTarget != null ? hashTarget.hashCode() : 0);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("type: %d, label: %s, isPrimary: %s, pobox: %s, "
|
|
+ "extendedAddress: %s, street: %s, localty: %s, region: %s, postalCode %s, "
|
|
+ "country: %s", mType, mLabel, mIsPrimary, mPobox, mExtendedAddress, mStreet,
|
|
mLocalty, mRegion, mPostalCode, mCountry);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.POSTAL_ADDRESS;
|
|
}
|
|
|
|
public String getPobox() {
|
|
return mPobox;
|
|
}
|
|
|
|
public String getExtendedAddress() {
|
|
return mExtendedAddress;
|
|
}
|
|
|
|
public String getStreet() {
|
|
return mStreet;
|
|
}
|
|
|
|
public String getLocalty() {
|
|
return mLocalty;
|
|
}
|
|
|
|
public String getRegion() {
|
|
return mRegion;
|
|
}
|
|
|
|
public String getPostalCode() {
|
|
return mPostalCode;
|
|
}
|
|
|
|
public String getCountry() {
|
|
return mCountry;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public String getLabel() {
|
|
return mLabel;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class OrganizationData implements EntryElement {
|
|
// non-final is Intentional: we may change the values since this info is separated into
|
|
// two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in different
|
|
// timing.
|
|
private String mOrganizationName;
|
|
private String mDepartmentName;
|
|
private String mTitle;
|
|
private final String mPhoneticName; // We won't have this in "TITLE" property.
|
|
private final int mType;
|
|
private boolean mIsPrimary;
|
|
|
|
public OrganizationData(final String organizationName, final String departmentName,
|
|
final String titleName, final String phoneticName, int type,
|
|
final boolean isPrimary) {
|
|
mType = type;
|
|
mOrganizationName = organizationName;
|
|
mDepartmentName = departmentName;
|
|
mTitle = titleName;
|
|
mPhoneticName = phoneticName;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
public String getFormattedString() {
|
|
final StringBuilder builder = new StringBuilder();
|
|
if (!TextUtils.isEmpty(mOrganizationName)) {
|
|
builder.append(mOrganizationName);
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(mDepartmentName)) {
|
|
if (builder.length() > 0) {
|
|
builder.append(", ");
|
|
}
|
|
builder.append(mDepartmentName);
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(mTitle)) {
|
|
if (builder.length() > 0) {
|
|
builder.append(", ");
|
|
}
|
|
builder.append(mTitle);
|
|
}
|
|
|
|
return builder.toString();
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Organization.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Organization.TYPE, mType);
|
|
if (mOrganizationName != null) {
|
|
builder.withValue(Organization.COMPANY, mOrganizationName);
|
|
}
|
|
if (mDepartmentName != null) {
|
|
builder.withValue(Organization.DEPARTMENT, mDepartmentName);
|
|
}
|
|
if (mTitle != null) {
|
|
builder.withValue(Organization.TITLE, mTitle);
|
|
}
|
|
if (mPhoneticName != null) {
|
|
builder.withValue(Organization.PHONETIC_NAME, mPhoneticName);
|
|
}
|
|
if (mIsPrimary) {
|
|
builder.withValue(Organization.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mOrganizationName) && TextUtils.isEmpty(mDepartmentName)
|
|
&& TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mPhoneticName);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof OrganizationData)) {
|
|
return false;
|
|
}
|
|
OrganizationData organization = (OrganizationData) obj;
|
|
return (mType == organization.mType
|
|
&& TextUtils.equals(mOrganizationName, organization.mOrganizationName)
|
|
&& TextUtils.equals(mDepartmentName, organization.mDepartmentName)
|
|
&& TextUtils.equals(mTitle, organization.mTitle)
|
|
&& (mIsPrimary == organization.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + (mOrganizationName != null ? mOrganizationName.hashCode() : 0);
|
|
hash = hash * 31 + (mDepartmentName != null ? mDepartmentName.hashCode() : 0);
|
|
hash = hash * 31 + (mTitle != null ? mTitle.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format(
|
|
"type: %d, organization: %s, department: %s, title: %s, isPrimary: %s", mType,
|
|
mOrganizationName, mDepartmentName, mTitle, mIsPrimary);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.ORGANIZATION;
|
|
}
|
|
|
|
public String getOrganizationName() {
|
|
return mOrganizationName;
|
|
}
|
|
|
|
public String getDepartmentName() {
|
|
return mDepartmentName;
|
|
}
|
|
|
|
public String getTitle() {
|
|
return mTitle;
|
|
}
|
|
|
|
public String getPhoneticName() {
|
|
return mPhoneticName;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class ImData implements EntryElement {
|
|
private final String mAddress;
|
|
private final int mProtocol;
|
|
private final String mCustomProtocol;
|
|
private final int mType;
|
|
private final boolean mIsPrimary;
|
|
|
|
public ImData(final int protocol, final String customProtocol, final String address,
|
|
final int type, final boolean isPrimary) {
|
|
mProtocol = protocol;
|
|
mCustomProtocol = customProtocol;
|
|
mType = type;
|
|
mAddress = address;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Im.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Im.TYPE, mType);
|
|
builder.withValue(Im.PROTOCOL, mProtocol);
|
|
builder.withValue(Im.DATA, mAddress);
|
|
if (mProtocol == Im.PROTOCOL_CUSTOM) {
|
|
builder.withValue(Im.CUSTOM_PROTOCOL, mCustomProtocol);
|
|
}
|
|
if (mIsPrimary) {
|
|
builder.withValue(Data.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mAddress);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof ImData)) {
|
|
return false;
|
|
}
|
|
ImData imData = (ImData) obj;
|
|
return (mType == imData.mType
|
|
&& mProtocol == imData.mProtocol
|
|
&& TextUtils.equals(mCustomProtocol, imData.mCustomProtocol)
|
|
&& TextUtils.equals(mAddress, imData.mAddress)
|
|
&& (mIsPrimary == imData.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + mProtocol;
|
|
hash = hash * 31 + (mCustomProtocol != null ? mCustomProtocol.hashCode() : 0);
|
|
hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format(
|
|
"type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s", mType,
|
|
mProtocol, mCustomProtocol, mAddress, mIsPrimary);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.IM;
|
|
}
|
|
|
|
public String getAddress() {
|
|
return mAddress;
|
|
}
|
|
|
|
/**
|
|
* One of the value available for {@link Im#PROTOCOL}. e.g.
|
|
* {@link Im#PROTOCOL_GOOGLE_TALK}
|
|
*/
|
|
public int getProtocol() {
|
|
return mProtocol;
|
|
}
|
|
|
|
public String getCustomProtocol() {
|
|
return mCustomProtocol;
|
|
}
|
|
|
|
public int getType() {
|
|
return mType;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class PhotoData implements EntryElement {
|
|
// private static final String FORMAT_FLASH = "SWF";
|
|
|
|
// used when type is not defined in ContactsContract.
|
|
private final String mFormat;
|
|
private final boolean mIsPrimary;
|
|
|
|
private final byte[] mBytes;
|
|
|
|
private Integer mHashCode = null;
|
|
|
|
public PhotoData(String format, byte[] photoBytes, boolean isPrimary) {
|
|
mFormat = format;
|
|
mBytes = photoBytes;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Photo.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Photo.PHOTO, mBytes);
|
|
if (mIsPrimary) {
|
|
builder.withValue(Photo.IS_PRIMARY, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return mBytes == null || mBytes.length == 0;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof PhotoData)) {
|
|
return false;
|
|
}
|
|
PhotoData photoData = (PhotoData) obj;
|
|
return (TextUtils.equals(mFormat, photoData.mFormat)
|
|
&& Arrays.equals(mBytes, photoData.mBytes)
|
|
&& (mIsPrimary == photoData.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
if (mHashCode != null) {
|
|
return mHashCode;
|
|
}
|
|
|
|
int hash = mFormat != null ? mFormat.hashCode() : 0;
|
|
hash = hash * 31;
|
|
if (mBytes != null) {
|
|
for (byte b : mBytes) {
|
|
hash += b;
|
|
}
|
|
}
|
|
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
mHashCode = hash;
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("format: %s: size: %d, isPrimary: %s", mFormat, mBytes.length,
|
|
mIsPrimary);
|
|
}
|
|
|
|
@Override
|
|
public final EntryLabel getEntryLabel() {
|
|
return EntryLabel.PHOTO;
|
|
}
|
|
|
|
public String getFormat() {
|
|
return mFormat;
|
|
}
|
|
|
|
public byte[] getBytes() {
|
|
return mBytes;
|
|
}
|
|
|
|
public boolean isPrimary() {
|
|
return mIsPrimary;
|
|
}
|
|
}
|
|
|
|
public static class NicknameData implements EntryElement {
|
|
private final String mNickname;
|
|
|
|
public NicknameData(String nickname) {
|
|
mNickname = nickname;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Nickname.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
|
|
builder.withValue(Nickname.NAME, mNickname);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mNickname);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (!(obj instanceof NicknameData)) {
|
|
return false;
|
|
}
|
|
NicknameData nicknameData = (NicknameData) obj;
|
|
return TextUtils.equals(mNickname, nicknameData.mNickname);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mNickname != null ? mNickname.hashCode() : 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "nickname: " + mNickname;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.NICKNAME;
|
|
}
|
|
|
|
public String getNickname() {
|
|
return mNickname;
|
|
}
|
|
}
|
|
|
|
public static class NoteData implements EntryElement {
|
|
public final String mNote;
|
|
|
|
public NoteData(String note) {
|
|
mNote = note;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Note.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Note.NOTE, mNote);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mNote);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof NoteData)) {
|
|
return false;
|
|
}
|
|
NoteData noteData = (NoteData) obj;
|
|
return TextUtils.equals(mNote, noteData.mNote);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mNote != null ? mNote.hashCode() : 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "note: " + mNote;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.NOTE;
|
|
}
|
|
|
|
public String getNote() {
|
|
return mNote;
|
|
}
|
|
}
|
|
|
|
public static class WebsiteData implements EntryElement {
|
|
private final String mWebsite;
|
|
|
|
public WebsiteData(String website) {
|
|
mWebsite = website;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Website.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Website.URL, mWebsite);
|
|
// There's no information about the type of URL in vCard.
|
|
// We use TYPE_HOMEPAGE for safety.
|
|
builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mWebsite);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof WebsiteData)) {
|
|
return false;
|
|
}
|
|
WebsiteData websiteData = (WebsiteData) obj;
|
|
return TextUtils.equals(mWebsite, websiteData.mWebsite);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mWebsite != null ? mWebsite.hashCode() : 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "website: " + mWebsite;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.WEBSITE;
|
|
}
|
|
|
|
public String getWebsite() {
|
|
return mWebsite;
|
|
}
|
|
}
|
|
|
|
public static class BirthdayData implements EntryElement {
|
|
private final String mBirthday;
|
|
|
|
public BirthdayData(String birthday) {
|
|
mBirthday = birthday;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Event.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Event.START_DATE, mBirthday);
|
|
builder.withValue(Event.TYPE, Event.TYPE_BIRTHDAY);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mBirthday);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof BirthdayData)) {
|
|
return false;
|
|
}
|
|
BirthdayData birthdayData = (BirthdayData) obj;
|
|
return TextUtils.equals(mBirthday, birthdayData.mBirthday);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mBirthday != null ? mBirthday.hashCode() : 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "birthday: " + mBirthday;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.BIRTHDAY;
|
|
}
|
|
|
|
public String getBirthday() {
|
|
return mBirthday;
|
|
}
|
|
}
|
|
|
|
public static class AnniversaryData implements EntryElement {
|
|
private final String mAnniversary;
|
|
|
|
public AnniversaryData(String anniversary) {
|
|
mAnniversary = anniversary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(Event.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
|
|
builder.withValue(Event.START_DATE, mAnniversary);
|
|
builder.withValue(Event.TYPE, Event.TYPE_ANNIVERSARY);
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mAnniversary);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof AnniversaryData)) {
|
|
return false;
|
|
}
|
|
AnniversaryData anniversaryData = (AnniversaryData) obj;
|
|
return TextUtils.equals(mAnniversary, anniversaryData.mAnniversary);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return mAnniversary != null ? mAnniversary.hashCode() : 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "anniversary: " + mAnniversary;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.ANNIVERSARY;
|
|
}
|
|
|
|
public String getAnniversary() { return mAnniversary; }
|
|
}
|
|
|
|
public static class SipData implements EntryElement {
|
|
/**
|
|
* Note that schema part ("sip:") is automatically removed. e.g.
|
|
* "sip:username:password@host:port" becomes
|
|
* "username:password@host:port"
|
|
*/
|
|
private final String mAddress;
|
|
private final int mType;
|
|
private final String mLabel;
|
|
private final boolean mIsPrimary;
|
|
|
|
public SipData(String rawSip, int type, String label, boolean isPrimary) {
|
|
if (rawSip.startsWith("sip:")) {
|
|
mAddress = rawSip.substring(4);
|
|
} else {
|
|
mAddress = rawSip;
|
|
}
|
|
mType = type;
|
|
mLabel = label;
|
|
mIsPrimary = isPrimary;
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(SipAddress.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
|
|
builder.withValue(SipAddress.SIP_ADDRESS, mAddress);
|
|
builder.withValue(SipAddress.TYPE, mType);
|
|
if (mType == SipAddress.TYPE_CUSTOM) {
|
|
builder.withValue(SipAddress.LABEL, mLabel);
|
|
}
|
|
if (mIsPrimary) {
|
|
builder.withValue(SipAddress.IS_PRIMARY, mIsPrimary);
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mAddress);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof SipData)) {
|
|
return false;
|
|
}
|
|
SipData sipData = (SipData) obj;
|
|
return (mType == sipData.mType
|
|
&& TextUtils.equals(mLabel, sipData.mLabel)
|
|
&& TextUtils.equals(mAddress, sipData.mAddress)
|
|
&& (mIsPrimary == sipData.mIsPrimary));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mType;
|
|
hash = hash * 31 + (mLabel != null ? mLabel.hashCode() : 0);
|
|
hash = hash * 31 + (mAddress != null ? mAddress.hashCode() : 0);
|
|
hash = hash * 31 + (mIsPrimary ? 1231 : 1237);
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "sip: " + mAddress;
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.SIP;
|
|
}
|
|
|
|
/**
|
|
* @return Address part of the sip data. The schema ("sip:") isn't contained here.
|
|
*/
|
|
public String getAddress() { return mAddress; }
|
|
public int getType() { return mType; }
|
|
public String getLabel() { return mLabel; }
|
|
}
|
|
|
|
/**
|
|
* Some Contacts data in Android cannot be converted to vCard
|
|
* representation. VCardEntry preserves those data using this class.
|
|
*/
|
|
public static class AndroidCustomData implements EntryElement {
|
|
private final String mMimeType;
|
|
|
|
private final List<String> mDataList; // 1 .. VCardConstants.MAX_DATA_COLUMN
|
|
|
|
public AndroidCustomData(String mimeType, List<String> dataList) {
|
|
mMimeType = mimeType;
|
|
mDataList = dataList;
|
|
}
|
|
|
|
public static AndroidCustomData constructAndroidCustomData(List<String> list) {
|
|
String mimeType;
|
|
List<String> dataList;
|
|
|
|
if (list == null) {
|
|
mimeType = null;
|
|
dataList = null;
|
|
} else if (list.size() < 2) {
|
|
mimeType = list.get(0);
|
|
dataList = null;
|
|
} else {
|
|
final int max = (list.size() < VCardConstants.MAX_DATA_COLUMN + 1) ? list.size()
|
|
: VCardConstants.MAX_DATA_COLUMN + 1;
|
|
mimeType = list.get(0);
|
|
dataList = list.subList(1, max);
|
|
}
|
|
|
|
return new AndroidCustomData(mimeType, dataList);
|
|
}
|
|
|
|
@Override
|
|
public void constructInsertOperation(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(Data.CONTENT_URI);
|
|
builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, backReferenceIndex);
|
|
builder.withValue(Data.MIMETYPE, mMimeType);
|
|
for (int i = 0; i < mDataList.size(); i++) {
|
|
String value = mDataList.get(i);
|
|
if (!TextUtils.isEmpty(value)) {
|
|
// 1-origin
|
|
builder.withValue("data" + (i + 1), value);
|
|
}
|
|
}
|
|
operationList.add(builder.build());
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return TextUtils.isEmpty(mMimeType) || mDataList == null || mDataList.size() == 0;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (!(obj instanceof AndroidCustomData)) {
|
|
return false;
|
|
}
|
|
AndroidCustomData data = (AndroidCustomData) obj;
|
|
if (!TextUtils.equals(mMimeType, data.mMimeType)) {
|
|
return false;
|
|
}
|
|
if (mDataList == null) {
|
|
return data.mDataList == null;
|
|
} else {
|
|
final int size = mDataList.size();
|
|
if (size != data.mDataList.size()) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < size; i++) {
|
|
if (!TextUtils.equals(mDataList.get(i), data.mDataList.get(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
int hash = mMimeType != null ? mMimeType.hashCode() : 0;
|
|
if (mDataList != null) {
|
|
for (String data : mDataList) {
|
|
hash = hash * 31 + (data != null ? data.hashCode() : 0);
|
|
}
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
final StringBuilder builder = new StringBuilder();
|
|
builder.append("android-custom: " + mMimeType + ", data: ");
|
|
builder.append(mDataList == null ? "null" : Arrays.toString(mDataList.toArray()));
|
|
return builder.toString();
|
|
}
|
|
|
|
@Override
|
|
public EntryLabel getEntryLabel() {
|
|
return EntryLabel.ANDROID_CUSTOM;
|
|
}
|
|
|
|
public String getMimeType() { return mMimeType; }
|
|
public List<String> getDataList() { return mDataList; }
|
|
}
|
|
|
|
private final NameData mNameData = new NameData();
|
|
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<WebsiteData> mWebsiteList;
|
|
private List<SipData> mSipList;
|
|
private List<NicknameData> mNicknameList;
|
|
private List<NoteData> mNoteList;
|
|
private List<AndroidCustomData> mAndroidCustomDataList;
|
|
private BirthdayData mBirthday;
|
|
private AnniversaryData mAnniversary;
|
|
private List<Pair<String, String>> mUnknownXData;
|
|
|
|
/**
|
|
* Inner iterator interface.
|
|
*/
|
|
public interface EntryElementIterator {
|
|
public void onIterationStarted();
|
|
|
|
public void onIterationEnded();
|
|
|
|
/**
|
|
* Called when there are one or more {@link EntryElement} instances
|
|
* associated with {@link EntryLabel}.
|
|
*/
|
|
public void onElementGroupStarted(EntryLabel label);
|
|
|
|
/**
|
|
* Called after all {@link EntryElement} instances for
|
|
* {@link EntryLabel} provided on {@link #onElementGroupStarted(EntryLabel)}
|
|
* being processed by {@link #onElement(EntryElement)}
|
|
*/
|
|
public void onElementGroupEnded();
|
|
|
|
/**
|
|
* @return should be true when child wants to continue the operation.
|
|
* False otherwise.
|
|
*/
|
|
public boolean onElement(EntryElement elem);
|
|
}
|
|
|
|
public final void iterateAllData(EntryElementIterator iterator) {
|
|
iterator.onIterationStarted();
|
|
iterator.onElementGroupStarted(mNameData.getEntryLabel());
|
|
iterator.onElement(mNameData);
|
|
iterator.onElementGroupEnded();
|
|
|
|
iterateOneList(mPhoneList, iterator);
|
|
iterateOneList(mEmailList, iterator);
|
|
iterateOneList(mPostalList, iterator);
|
|
iterateOneList(mOrganizationList, iterator);
|
|
iterateOneList(mImList, iterator);
|
|
iterateOneList(mPhotoList, iterator);
|
|
iterateOneList(mWebsiteList, iterator);
|
|
iterateOneList(mSipList, iterator);
|
|
iterateOneList(mNicknameList, iterator);
|
|
iterateOneList(mNoteList, iterator);
|
|
iterateOneList(mAndroidCustomDataList, iterator);
|
|
|
|
if (mBirthday != null) {
|
|
iterator.onElementGroupStarted(mBirthday.getEntryLabel());
|
|
iterator.onElement(mBirthday);
|
|
iterator.onElementGroupEnded();
|
|
}
|
|
if (mAnniversary != null) {
|
|
iterator.onElementGroupStarted(mAnniversary.getEntryLabel());
|
|
iterator.onElement(mAnniversary);
|
|
iterator.onElementGroupEnded();
|
|
}
|
|
iterator.onIterationEnded();
|
|
}
|
|
|
|
private void iterateOneList(List<? extends EntryElement> elemList,
|
|
EntryElementIterator iterator) {
|
|
if (elemList != null && elemList.size() > 0) {
|
|
iterator.onElementGroupStarted(elemList.get(0).getEntryLabel());
|
|
for (EntryElement elem : elemList) {
|
|
iterator.onElement(elem);
|
|
}
|
|
iterator.onElementGroupEnded();
|
|
}
|
|
}
|
|
|
|
private class IsIgnorableIterator implements EntryElementIterator {
|
|
private boolean mEmpty = true;
|
|
|
|
@Override
|
|
public void onIterationStarted() {
|
|
}
|
|
|
|
@Override
|
|
public void onIterationEnded() {
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupStarted(EntryLabel label) {
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupEnded() {
|
|
}
|
|
|
|
@Override
|
|
public boolean onElement(EntryElement elem) {
|
|
if (!elem.isEmpty()) {
|
|
mEmpty = false;
|
|
// exit now
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public boolean getResult() {
|
|
return mEmpty;
|
|
}
|
|
}
|
|
|
|
private class ToStringIterator implements EntryElementIterator {
|
|
private StringBuilder mBuilder;
|
|
|
|
private boolean mFirstElement;
|
|
|
|
@Override
|
|
public void onIterationStarted() {
|
|
mBuilder = new StringBuilder();
|
|
mBuilder.append("[[hash: " + VCardEntry.this.hashCode() + "\n");
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupStarted(EntryLabel label) {
|
|
mBuilder.append(label.toString() + ": ");
|
|
mFirstElement = true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onElement(EntryElement elem) {
|
|
if (!mFirstElement) {
|
|
mBuilder.append(", ");
|
|
mFirstElement = false;
|
|
}
|
|
mBuilder.append("[").append(elem.toString()).append("]");
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupEnded() {
|
|
mBuilder.append("\n");
|
|
}
|
|
|
|
@Override
|
|
public void onIterationEnded() {
|
|
mBuilder.append("]]\n");
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return mBuilder.toString();
|
|
}
|
|
}
|
|
|
|
private class InsertOperationConstrutor implements EntryElementIterator {
|
|
private final List<ContentProviderOperation> mOperationList;
|
|
|
|
private final int mBackReferenceIndex;
|
|
|
|
public InsertOperationConstrutor(List<ContentProviderOperation> operationList,
|
|
int backReferenceIndex) {
|
|
mOperationList = operationList;
|
|
mBackReferenceIndex = backReferenceIndex;
|
|
}
|
|
|
|
@Override
|
|
public void onIterationStarted() {
|
|
}
|
|
|
|
@Override
|
|
public void onIterationEnded() {
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupStarted(EntryLabel label) {
|
|
}
|
|
|
|
@Override
|
|
public void onElementGroupEnded() {
|
|
}
|
|
|
|
@Override
|
|
public boolean onElement(EntryElement elem) {
|
|
if (!elem.isEmpty()) {
|
|
elem.constructInsertOperation(mOperationList, mBackReferenceIndex);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private final int mVCardType;
|
|
private Account mAccount;
|
|
|
|
public Account getAccount() {
|
|
return mAccount;
|
|
}
|
|
|
|
public void setAccount(Account account) {
|
|
mAccount = account;
|
|
}
|
|
|
|
private List<VCardEntry> mChildren;
|
|
|
|
@Override
|
|
public String toString() {
|
|
ToStringIterator iterator = new ToStringIterator();
|
|
iterateAllData(iterator);
|
|
return iterator.toString();
|
|
}
|
|
|
|
public VCardEntry() {
|
|
this(VCardConfig.VCARD_TYPE_V21_GENERIC);
|
|
}
|
|
|
|
public VCardEntry(int vcardType) {
|
|
this(vcardType, null);
|
|
}
|
|
|
|
public VCardEntry(int vcardType, Account account) {
|
|
mVCardType = vcardType;
|
|
mAccount = account;
|
|
}
|
|
|
|
private void addPhone(int type, String data, String label, boolean isPrimary) {
|
|
if (mPhoneList == null) {
|
|
mPhoneList = new ArrayList<PhoneData>();
|
|
}
|
|
final StringBuilder builder = new StringBuilder();
|
|
final String trimmed = data.trim();
|
|
final String formattedNumber;
|
|
if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
|
|
formattedNumber = trimmed;
|
|
} else {
|
|
// TODO: from the view of vCard spec these auto conversions should be removed.
|
|
// Note that some other codes (like the phone number formatter) or modules expect this
|
|
// auto conversion (bug 5178723), so just omitting this code won't be preferable enough
|
|
// (bug 4177894)
|
|
boolean hasPauseOrWait = false;
|
|
final int length = trimmed.length();
|
|
for (int i = 0; i < length; i++) {
|
|
char ch = trimmed.charAt(i);
|
|
// See RFC 3601 and docs for PhoneNumberUtils for more info.
|
|
if (ch == 'p' || ch == 'P') {
|
|
builder.append(PhoneNumberUtils.PAUSE);
|
|
hasPauseOrWait = true;
|
|
} else if (ch == 'w' || ch == 'W') {
|
|
builder.append(PhoneNumberUtils.WAIT);
|
|
hasPauseOrWait = true;
|
|
} else if (PhoneNumberUtils.is12Key(ch) || (i == 0 && ch == '+')) {
|
|
builder.append(ch);
|
|
}
|
|
}
|
|
if (!hasPauseOrWait) {
|
|
final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
|
|
formattedNumber = PhoneNumberUtilsPort.formatNumber(
|
|
builder.toString(), formattingType);
|
|
} else {
|
|
formattedNumber = builder.toString();
|
|
}
|
|
}
|
|
PhoneData phoneData = new PhoneData(formattedNumber, type, label, isPrimary);
|
|
mPhoneList.add(phoneData);
|
|
}
|
|
|
|
private void addSip(String sipData, int type, String label, boolean isPrimary) {
|
|
if (mSipList == null) {
|
|
mSipList = new ArrayList<SipData>();
|
|
}
|
|
mSipList.add(new SipData(sipData, type, label, isPrimary));
|
|
}
|
|
|
|
private void addNickName(final String nickName) {
|
|
if (mNicknameList == null) {
|
|
mNicknameList = new ArrayList<NicknameData>();
|
|
}
|
|
mNicknameList.add(new NicknameData(nickName));
|
|
}
|
|
|
|
private void addEmail(int type, String data, String label, boolean isPrimary) {
|
|
if (mEmailList == null) {
|
|
mEmailList = new ArrayList<EmailData>();
|
|
}
|
|
mEmailList.add(new EmailData(data, type, label, isPrimary));
|
|
}
|
|
|
|
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary) {
|
|
if (mPostalList == null) {
|
|
mPostalList = new ArrayList<PostalData>(0);
|
|
}
|
|
mPostalList.add(PostalData.constructPostalData(propValueList, type, label, isPrimary,
|
|
mVCardType));
|
|
}
|
|
|
|
/**
|
|
* Should be called via {@link #handleOrgValue(int, List, Map, boolean)} or
|
|
* {@link #handleTitleValue(String)}.
|
|
*/
|
|
private void addNewOrganization(final String organizationName, final String departmentName,
|
|
final String titleName, final String phoneticName, int type, final boolean isPrimary) {
|
|
if (mOrganizationList == null) {
|
|
mOrganizationList = new ArrayList<OrganizationData>();
|
|
}
|
|
mOrganizationList.add(new OrganizationData(organizationName, departmentName, titleName,
|
|
phoneticName, type, isPrimary));
|
|
}
|
|
|
|
private static final List<String> sEmptyList = Collections
|
|
.unmodifiableList(new ArrayList<String>(0));
|
|
|
|
private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) {
|
|
final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
|
|
if (sortAsCollection != null && sortAsCollection.size() != 0) {
|
|
if (sortAsCollection.size() > 1) {
|
|
Log.w(LOG_TAG,
|
|
"Incorrect multiple SORT_AS parameters detected: "
|
|
+ Arrays.toString(sortAsCollection.toArray()));
|
|
}
|
|
final List<String> sortNames = VCardUtils.constructListFromValue(sortAsCollection
|
|
.iterator().next(), mVCardType);
|
|
final StringBuilder builder = new StringBuilder();
|
|
for (final String elem : sortNames) {
|
|
builder.append(elem);
|
|
}
|
|
return builder.toString();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set "ORG" related values to the appropriate data. If there's more than
|
|
* one {@link 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 {@link OrganizationData} object, a new
|
|
* {@link OrganizationData} is created, whose title is set to null.
|
|
*/
|
|
private void handleOrgValue(final int type, List<String> orgList,
|
|
Map<String, Collection<String>> paramMap, boolean isPrimary) {
|
|
final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap);
|
|
if (orgList == null) {
|
|
orgList = sEmptyList;
|
|
}
|
|
final String organizationName;
|
|
final String departmentName;
|
|
final int size = orgList.size();
|
|
switch (size) {
|
|
case 0: {
|
|
organizationName = "";
|
|
departmentName = null;
|
|
break;
|
|
}
|
|
case 1: {
|
|
organizationName = orgList.get(0);
|
|
departmentName = null;
|
|
break;
|
|
}
|
|
default: { // More than 1.
|
|
organizationName = 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(organizationName, departmentName, null, phoneticName, type,
|
|
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.mOrganizationName == null
|
|
&& organizationData.mDepartmentName == null) {
|
|
// Probably the "TITLE" property comes before the "ORG" property via
|
|
// handleTitleLine().
|
|
organizationData.mOrganizationName = organizationName;
|
|
organizationData.mDepartmentName = departmentName;
|
|
organizationData.mIsPrimary = isPrimary;
|
|
return;
|
|
}
|
|
}
|
|
// No OrganizatioData is available. Create another one, with "null" title, which may be
|
|
// added via handleTitleValue().
|
|
addNewOrganization(organizationName, departmentName, null, phoneticName, type, isPrimary);
|
|
}
|
|
|
|
/**
|
|
* 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(null, null, title, null, DEFAULT_ORGANIZATION_TYPE, false);
|
|
return;
|
|
}
|
|
for (OrganizationData organizationData : mOrganizationList) {
|
|
if (organizationData.mTitle == null) {
|
|
organizationData.mTitle = title;
|
|
return;
|
|
}
|
|
}
|
|
// No Organization is available. Create another one, with "null" other info, which may be
|
|
// added via handleOrgValue().
|
|
addNewOrganization(null, null, title, null, DEFAULT_ORGANIZATION_TYPE, false);
|
|
}
|
|
|
|
private void addIm(int protocol, String customProtocol, String propValue, int type,
|
|
boolean isPrimary) {
|
|
if (mImList == null) {
|
|
mImList = new ArrayList<ImData>();
|
|
}
|
|
mImList.add(new ImData(protocol, customProtocol, propValue, type, isPrimary));
|
|
}
|
|
|
|
private void addNote(final String note) {
|
|
if (mNoteList == null) {
|
|
mNoteList = new ArrayList<NoteData>(1);
|
|
}
|
|
mNoteList.add(new NoteData(note));
|
|
}
|
|
|
|
private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
|
|
if (mPhotoList == null) {
|
|
mPhotoList = new ArrayList<PhotoData>(1);
|
|
}
|
|
final PhotoData photoData = new PhotoData(formatName, photoBytes, isPrimary);
|
|
mPhotoList.add(photoData);
|
|
}
|
|
|
|
/**
|
|
* Tries to extract paramMap, constructs SORT-AS parameter values, and store
|
|
* them in appropriate phonetic name variables. This method does not care
|
|
* the vCard version. Even when we have SORT-AS parameters in invalid
|
|
* versions (i.e. 2.1 and 3.0), we scilently accept them so that we won't
|
|
* drop meaningful information. If we had this parameter in the N field of
|
|
* vCard 3.0, and the contact data also have SORT-STRING, we will prefer
|
|
* SORT-STRING, since it is regitimate property to be understood.
|
|
*/
|
|
private void tryHandleSortAsName(final Map<String, Collection<String>> paramMap) {
|
|
if (VCardConfig.isVersion30(mVCardType)
|
|
&& !(TextUtils.isEmpty(mNameData.mPhoneticFamily)
|
|
&& TextUtils.isEmpty(mNameData.mPhoneticMiddle) && TextUtils
|
|
.isEmpty(mNameData.mPhoneticGiven))) {
|
|
return;
|
|
}
|
|
|
|
final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
|
|
if (sortAsCollection != null && sortAsCollection.size() != 0) {
|
|
if (sortAsCollection.size() > 1) {
|
|
Log.w(LOG_TAG,
|
|
"Incorrect multiple SORT_AS parameters detected: "
|
|
+ Arrays.toString(sortAsCollection.toArray()));
|
|
}
|
|
final List<String> sortNames = VCardUtils.constructListFromValue(sortAsCollection
|
|
.iterator().next(), mVCardType);
|
|
int size = sortNames.size();
|
|
if (size > 3) {
|
|
size = 3;
|
|
}
|
|
switch (size) {
|
|
case 3:
|
|
mNameData.mPhoneticMiddle = sortNames.get(2); //$FALL-THROUGH$
|
|
case 2:
|
|
mNameData.mPhoneticGiven = sortNames.get(1); //$FALL-THROUGH$
|
|
default:
|
|
mNameData.mPhoneticFamily = sortNames.get(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("fallthrough")
|
|
private void handleNProperty(final List<String> paramValues,
|
|
Map<String, Collection<String>> paramMap) {
|
|
// in vCard 4.0, SORT-AS parameter is available.
|
|
tryHandleSortAsName(paramMap);
|
|
|
|
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
|
|
int size;
|
|
if (paramValues == null || (size = paramValues.size()) < 1) {
|
|
return;
|
|
}
|
|
if (size > 5) {
|
|
size = 5;
|
|
}
|
|
|
|
switch (size) {
|
|
// Fall-through.
|
|
case 5:
|
|
mNameData.mSuffix = paramValues.get(4);
|
|
case 4:
|
|
mNameData.mPrefix = paramValues.get(3);
|
|
case 3:
|
|
mNameData.mMiddle = paramValues.get(2);
|
|
case 2:
|
|
mNameData.mGiven = paramValues.get(1);
|
|
default:
|
|
mNameData.mFamily = paramValues.get(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note: 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) {
|
|
if (!(TextUtils.isEmpty(mNameData.mPhoneticFamily)
|
|
&& TextUtils.isEmpty(mNameData.mPhoneticMiddle) && TextUtils
|
|
.isEmpty(mNameData.mPhoneticGiven))) {
|
|
// This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
|
|
// Ignore "SOUND;X-IRMC-N".
|
|
return;
|
|
}
|
|
|
|
int size;
|
|
if (elems == null || (size = elems.size()) < 1) {
|
|
return;
|
|
}
|
|
|
|
// Assume that the order is "Family, Given, Middle".
|
|
// This is not from specification but mere assumption. Some Japanese
|
|
// phones use this order.
|
|
if (size > 3) {
|
|
size = 3;
|
|
}
|
|
|
|
if (elems.get(0).length() > 0) {
|
|
boolean onlyFirstElemIsNonEmpty = true;
|
|
for (int i = 1; i < size; i++) {
|
|
if (elems.get(i).length() > 0) {
|
|
onlyFirstElemIsNonEmpty = false;
|
|
break;
|
|
}
|
|
}
|
|
if (onlyFirstElemIsNonEmpty) {
|
|
final String[] namesArray = elems.get(0).split(" ");
|
|
final int nameArrayLength = namesArray.length;
|
|
if (nameArrayLength == 3) {
|
|
// Assume the string is "Family Middle Given".
|
|
mNameData.mPhoneticFamily = namesArray[0];
|
|
mNameData.mPhoneticMiddle = namesArray[1];
|
|
mNameData.mPhoneticGiven = namesArray[2];
|
|
} else if (nameArrayLength == 2) {
|
|
// Assume the string is "Family Given" based on the Japanese mobile
|
|
// phones' preference.
|
|
mNameData.mPhoneticFamily = namesArray[0];
|
|
mNameData.mPhoneticGiven = namesArray[1];
|
|
} else {
|
|
mNameData.mPhoneticGiven = elems.get(0);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (size) {
|
|
// fallthrough
|
|
case 3:
|
|
mNameData.mPhoneticMiddle = elems.get(2);
|
|
case 2:
|
|
mNameData.mPhoneticGiven = elems.get(1);
|
|
default:
|
|
mNameData.mPhoneticFamily = elems.get(0);
|
|
}
|
|
}
|
|
|
|
public void addProperty(final VCardProperty property) {
|
|
final String propertyName = property.getName();
|
|
final Map<String, Collection<String>> paramMap = property.getParameterMap();
|
|
final List<String> propertyValueList = property.getValueList();
|
|
byte[] propertyBytes = property.getByteValue();
|
|
|
|
if ((propertyValueList == null || propertyValueList.size() == 0)
|
|
&& propertyBytes == null) {
|
|
return;
|
|
}
|
|
final String propValue = (propertyValueList != null
|
|
? listToString(propertyValueList).trim()
|
|
: null);
|
|
|
|
if (propertyName.equals(VCardConstants.PROPERTY_VERSION)) {
|
|
// vCard version. Ignore this.
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_FN)) {
|
|
mNameData.mFormatted = propValue;
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_NAME)) {
|
|
// Only in vCard 3.0. Use this if FN doesn't exist though it is
|
|
// required in vCard 3.0.
|
|
if (TextUtils.isEmpty(mNameData.mFormatted)) {
|
|
mNameData.mFormatted = propValue;
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_N)) {
|
|
handleNProperty(propertyValueList, paramMap);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
|
|
mNameData.mSortString = propValue;
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_NICKNAME)
|
|
|| propertyName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
|
|
addNickName(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_SOUND)) {
|
|
Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
if (typeCollection != null
|
|
&& typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
|
|
// 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,
|
|
mVCardType);
|
|
handlePhoneticNameFromSound(phoneticNameList);
|
|
} else {
|
|
// Ignore this field since Android cannot understand what it is.
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_ADR)) {
|
|
boolean valuesAreAllEmpty = true;
|
|
for (String value : propertyValueList) {
|
|
if (!TextUtils.isEmpty(value)) {
|
|
valuesAreAllEmpty = false;
|
|
break;
|
|
}
|
|
}
|
|
if (valuesAreAllEmpty) {
|
|
return;
|
|
}
|
|
|
|
int type = -1;
|
|
String label = null;
|
|
boolean isPrimary = false;
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
if (typeCollection != null) {
|
|
for (final String typeStringOrg : typeCollection) {
|
|
final String typeStringUpperCase = typeStringOrg.toUpperCase();
|
|
if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) {
|
|
type = StructuredPostal.TYPE_HOME;
|
|
label = null;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK)
|
|
|| typeStringUpperCase
|
|
.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
|
|
// "COMPANY" seems emitted by Windows Mobile, which is not
|
|
// specifically supported by vCard 2.1. We assume this is same
|
|
// as "WORK".
|
|
type = StructuredPostal.TYPE_WORK;
|
|
label = null;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL)
|
|
|| typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_DOM)
|
|
|| typeStringUpperCase.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
|
|
// We do not have any appropriate way to store this information.
|
|
} else if (type < 0) { // If no other type is specified before.
|
|
type = StructuredPostal.TYPE_CUSTOM;
|
|
if (typeStringUpperCase.startsWith("X-")) { // If X- or x-
|
|
label = typeStringOrg.substring(2);
|
|
} else {
|
|
label = typeStringOrg;
|
|
}
|
|
// {@link ContactsContract} has a {@link StructuredPostal.TYPE_OTHER}, so
|
|
// if the custom type is "other", map it from {@code TYPE_CUSTOM} to
|
|
// {@code TYPE_OTHER}.
|
|
if (VCardConstants.PARAM_ADR_EXTRA_TYPE_OTHER.equals(label.toUpperCase())) {
|
|
type = StructuredPostal.TYPE_OTHER;
|
|
label = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// We use "HOME" as default
|
|
if (type < 0) {
|
|
type = StructuredPostal.TYPE_HOME;
|
|
}
|
|
|
|
addPostal(type, propertyValueList, label, isPrimary);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_EMAIL)) {
|
|
int type = -1;
|
|
String label = null;
|
|
boolean isPrimary = false;
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
if (typeCollection != null) {
|
|
for (final String typeStringOrg : typeCollection) {
|
|
final String typeStringUpperCase = typeStringOrg.toUpperCase();
|
|
if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) {
|
|
type = Email.TYPE_HOME;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK)) {
|
|
type = Email.TYPE_WORK;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_CELL)) {
|
|
type = Email.TYPE_MOBILE;
|
|
} else if (type < 0) { // If no other type is specified before
|
|
if (typeStringUpperCase.startsWith("X-")) { // If X- or x-
|
|
label = typeStringOrg.substring(2);
|
|
} else {
|
|
label = typeStringOrg;
|
|
}
|
|
type = Email.TYPE_CUSTOM;
|
|
}
|
|
}
|
|
}
|
|
if (type < 0) {
|
|
type = Email.TYPE_OTHER;
|
|
}
|
|
addEmail(type, propValue, label, isPrimary);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_ORG)) {
|
|
// vCard specification does not specify other types.
|
|
final int type = Organization.TYPE_WORK;
|
|
boolean isPrimary = false;
|
|
Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
if (typeCollection != null) {
|
|
for (String typeString : typeCollection) {
|
|
if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
}
|
|
}
|
|
}
|
|
handleOrgValue(type, propertyValueList, paramMap, isPrimary);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_TITLE)) {
|
|
handleTitleValue(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_ROLE)) {
|
|
// This conflicts with TITLE. Ignore for now...
|
|
// handleTitleValue(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_PHOTO)
|
|
|| propertyName.equals(VCardConstants.PROPERTY_LOGO)) {
|
|
Collection<String> paramMapValue = paramMap.get("VALUE");
|
|
if (paramMapValue != null && paramMapValue.contains("URL")) {
|
|
// Currently we do not have appropriate example for testing this case.
|
|
} else {
|
|
final Collection<String> typeCollection = paramMap.get("TYPE");
|
|
String formatName = null;
|
|
boolean isPrimary = false;
|
|
if (typeCollection != null) {
|
|
for (String typeValue : typeCollection) {
|
|
if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
|
|
isPrimary = true;
|
|
} else if (formatName == null) {
|
|
formatName = typeValue;
|
|
}
|
|
}
|
|
}
|
|
addPhotoBytes(formatName, propertyBytes, isPrimary);
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_TEL)) {
|
|
String phoneNumber = null;
|
|
boolean isSip = false;
|
|
if (VCardConfig.isVersion40(mVCardType)) {
|
|
// Given propValue is in URI format, not in phone number format used until
|
|
// vCard 3.0.
|
|
if (propValue.startsWith("sip:")) {
|
|
isSip = true;
|
|
} else if (propValue.startsWith("tel:")) {
|
|
phoneNumber = propValue.substring(4);
|
|
} else {
|
|
// We don't know appropriate way to handle the other schemas. Also,
|
|
// we may still have non-URI phone number. To keep given data as much as
|
|
// we can, just save original value here.
|
|
phoneNumber = propValue;
|
|
}
|
|
} else {
|
|
phoneNumber = propValue;
|
|
}
|
|
|
|
if (isSip) {
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
handleSipCase(propValue, typeCollection);
|
|
} else {
|
|
if (propValue.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
final Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection,
|
|
phoneNumber);
|
|
final int type;
|
|
final String label;
|
|
if (typeObject instanceof Integer) {
|
|
type = (Integer) typeObject;
|
|
label = null;
|
|
} else {
|
|
type = Phone.TYPE_CUSTOM;
|
|
label = typeObject.toString();
|
|
}
|
|
|
|
final boolean isPrimary;
|
|
if (typeCollection != null &&
|
|
typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else {
|
|
isPrimary = false;
|
|
}
|
|
|
|
addPhone(type, phoneNumber, label, isPrimary);
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
|
|
// The phone number available via Skype.
|
|
Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
final int type = Phone.TYPE_OTHER;
|
|
final boolean isPrimary;
|
|
if (typeCollection != null
|
|
&& typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else {
|
|
isPrimary = false;
|
|
}
|
|
addPhone(type, propValue, null, isPrimary);
|
|
} else if (sImMap.containsKey(propertyName)) {
|
|
final int protocol = sImMap.get(propertyName);
|
|
boolean isPrimary = false;
|
|
int type = -1;
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
if (typeCollection != null) {
|
|
for (String typeString : typeCollection) {
|
|
if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else if (type < 0) {
|
|
if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
|
|
type = Im.TYPE_HOME;
|
|
} else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
|
|
type = Im.TYPE_WORK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (type < 0) {
|
|
type = Im.TYPE_HOME;
|
|
}
|
|
addIm(protocol, null, propValue, type, isPrimary);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_NOTE)) {
|
|
addNote(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_URL)) {
|
|
if (mWebsiteList == null) {
|
|
mWebsiteList = new ArrayList<WebsiteData>(1);
|
|
}
|
|
mWebsiteList.add(new WebsiteData(propValue));
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_BDAY)) {
|
|
mBirthday = new BirthdayData(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) {
|
|
mAnniversary = new AnniversaryData(propValue);
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
|
|
mNameData.mPhoneticGiven = propValue;
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
|
|
mNameData.mPhoneticMiddle = propValue;
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
|
|
mNameData.mPhoneticFamily = propValue;
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_IMPP)) {
|
|
// See also RFC 4770 (for vCard 3.0)
|
|
if (propValue.startsWith("sip:")) {
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
handleSipCase(propValue, typeCollection);
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_SIP)) {
|
|
if (!TextUtils.isEmpty(propValue)) {
|
|
final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
|
|
handleSipCase(propValue, typeCollection);
|
|
}
|
|
} else if (propertyName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
|
|
final List<String> customPropertyList = VCardUtils.constructListFromValue(propValue,
|
|
mVCardType);
|
|
handleAndroidCustomProperty(customPropertyList);
|
|
} else if (propertyName.toUpperCase().startsWith("X-")) {
|
|
// Catch all for X- properties. The caller can decide what to do with these.
|
|
if (mUnknownXData == null) {
|
|
mUnknownXData = new ArrayList<Pair<String, String>>();
|
|
}
|
|
mUnknownXData.add(new Pair<String, String>(propertyName, propValue));
|
|
} else {
|
|
}
|
|
// Be careful when adding some logic here, as some blocks above may use "return".
|
|
}
|
|
|
|
/**
|
|
* @param propValue may contain "sip:" at the beginning.
|
|
* @param typeCollection
|
|
*/
|
|
private void handleSipCase(String propValue, Collection<String> typeCollection) {
|
|
if (TextUtils.isEmpty(propValue)) {
|
|
return;
|
|
}
|
|
if (propValue.startsWith("sip:")) {
|
|
propValue = propValue.substring(4);
|
|
if (propValue.length() == 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int type = -1;
|
|
String label = null;
|
|
boolean isPrimary = false;
|
|
if (typeCollection != null) {
|
|
for (final String typeStringOrg : typeCollection) {
|
|
final String typeStringUpperCase = typeStringOrg.toUpperCase();
|
|
if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_PREF)) {
|
|
isPrimary = true;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_HOME)) {
|
|
type = SipAddress.TYPE_HOME;
|
|
} else if (typeStringUpperCase.equals(VCardConstants.PARAM_TYPE_WORK)) {
|
|
type = SipAddress.TYPE_WORK;
|
|
} else if (type < 0) { // If no other type is specified before
|
|
if (typeStringUpperCase.startsWith("X-")) { // If X- or x-
|
|
label = typeStringOrg.substring(2);
|
|
} else {
|
|
label = typeStringOrg;
|
|
}
|
|
type = SipAddress.TYPE_CUSTOM;
|
|
}
|
|
}
|
|
}
|
|
if (type < 0) {
|
|
type = SipAddress.TYPE_OTHER;
|
|
}
|
|
addSip(propValue, type, label, isPrimary);
|
|
}
|
|
|
|
public void addChild(VCardEntry child) {
|
|
if (mChildren == null) {
|
|
mChildren = new ArrayList<VCardEntry>();
|
|
}
|
|
mChildren.add(child);
|
|
}
|
|
|
|
private void handleAndroidCustomProperty(final List<String> customPropertyList) {
|
|
if (mAndroidCustomDataList == null) {
|
|
mAndroidCustomDataList = new ArrayList<AndroidCustomData>();
|
|
}
|
|
mAndroidCustomDataList
|
|
.add(AndroidCustomData.constructAndroidCustomData(customPropertyList));
|
|
}
|
|
|
|
/**
|
|
* Construct the display name. The constructed data must not be null.
|
|
*/
|
|
private String constructDisplayName() {
|
|
String displayName = null;
|
|
// FullName (created via "FN" or "NAME" field) is prefered.
|
|
if (!TextUtils.isEmpty(mNameData.mFormatted)) {
|
|
displayName = mNameData.mFormatted;
|
|
} else if (!mNameData.emptyStructuredName()) {
|
|
displayName = VCardUtils.constructNameFromElements(mVCardType, mNameData.mFamily,
|
|
mNameData.mMiddle, mNameData.mGiven, mNameData.mPrefix, mNameData.mSuffix);
|
|
} else if (!mNameData.emptyPhoneticStructuredName()) {
|
|
displayName = VCardUtils.constructNameFromElements(mVCardType,
|
|
mNameData.mPhoneticFamily, mNameData.mPhoneticMiddle, mNameData.mPhoneticGiven);
|
|
} else if (mEmailList != null && mEmailList.size() > 0) {
|
|
displayName = mEmailList.get(0).mAddress;
|
|
} else if (mPhoneList != null && mPhoneList.size() > 0) {
|
|
displayName = mPhoneList.get(0).mNumber;
|
|
} else if (mPostalList != null && mPostalList.size() > 0) {
|
|
displayName = mPostalList.get(0).getFormattedAddress(mVCardType);
|
|
} else if (mOrganizationList != null && mOrganizationList.size() > 0) {
|
|
displayName = mOrganizationList.get(0).getFormattedString();
|
|
}
|
|
if (displayName == null) {
|
|
displayName = "";
|
|
}
|
|
return displayName;
|
|
}
|
|
|
|
/**
|
|
* Consolidate several fielsds (like mName) using name candidates,
|
|
*/
|
|
public void consolidateFields() {
|
|
mNameData.displayName = constructDisplayName();
|
|
}
|
|
|
|
/**
|
|
* @return true when this object has nothing meaningful for Android's
|
|
* Contacts, and thus is "ignorable" for Android's Contacts. This
|
|
* does not mean an original vCard is really empty. Even when the
|
|
* original vCard has some fields, this may ignore it if those
|
|
* fields cannot be transcoded into Android's Contacts
|
|
* representation.
|
|
*/
|
|
public boolean isIgnorable() {
|
|
IsIgnorableIterator iterator = new IsIgnorableIterator();
|
|
iterateAllData(iterator);
|
|
return iterator.getResult();
|
|
}
|
|
|
|
/**
|
|
* Constructs the list of insert operation for this object. When the
|
|
* operationList argument is null, this method creates a new ArrayList and
|
|
* return it. The returned object is filled with new insert operations for
|
|
* this object. When operationList argument is not null, this method appends
|
|
* those new operations into the object instead of creating a new ArrayList.
|
|
*
|
|
* @param resolver {@link ContentResolver} object to be used in this method.
|
|
* @param operationList object to be filled. You can use this argument to
|
|
* concatinate operation lists. If null, this method creates a
|
|
* new array object.
|
|
* @return If operationList argument is null, new object with new insert
|
|
* operations. If it is not null, the operationList object with
|
|
* operations inserted by this method.
|
|
*/
|
|
public ArrayList<ContentProviderOperation> constructInsertOperations(ContentResolver resolver,
|
|
ArrayList<ContentProviderOperation> operationList) {
|
|
if (operationList == null) {
|
|
operationList = new ArrayList<ContentProviderOperation>();
|
|
}
|
|
|
|
if (isIgnorable()) {
|
|
return operationList;
|
|
}
|
|
|
|
final int backReferenceIndex = operationList.size();
|
|
|
|
// After applying the batch the first result's Uri is returned so it is important that
|
|
// the RawContact is the first operation that gets inserted into the list.
|
|
ContentProviderOperation.Builder builder = ContentProviderOperation
|
|
.newInsert(RawContacts.CONTENT_URI);
|
|
if (mAccount != null) {
|
|
builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
|
|
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
|
|
} else {
|
|
builder.withValue(RawContacts.ACCOUNT_NAME, null);
|
|
builder.withValue(RawContacts.ACCOUNT_TYPE, null);
|
|
}
|
|
// contacts favorites
|
|
if (getStarred()) {
|
|
builder.withValue(RawContacts.STARRED, 1);
|
|
}
|
|
operationList.add(builder.build());
|
|
|
|
int start = operationList.size();
|
|
iterateAllData(new InsertOperationConstrutor(operationList, backReferenceIndex));
|
|
int end = operationList.size();
|
|
|
|
return operationList;
|
|
}
|
|
|
|
public static VCardEntry buildFromResolver(ContentResolver resolver) {
|
|
return buildFromResolver(resolver, Contacts.CONTENT_URI);
|
|
}
|
|
|
|
public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
|
|
return null;
|
|
}
|
|
|
|
private String listToString(List<String> list) {
|
|
final int size = list.size();
|
|
if (size > 1) {
|
|
StringBuilder builder = new StringBuilder();
|
|
int i = 0;
|
|
for (String type : list) {
|
|
builder.append(type);
|
|
if (i < size - 1) {
|
|
builder.append(";");
|
|
}
|
|
}
|
|
return builder.toString();
|
|
} else if (size == 1) {
|
|
return list.get(0);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
public final NameData getNameData() {
|
|
return mNameData;
|
|
}
|
|
|
|
public final List<NicknameData> getNickNameList() {
|
|
return mNicknameList;
|
|
}
|
|
|
|
public final String getBirthday() {
|
|
return mBirthday != null ? mBirthday.mBirthday : null;
|
|
}
|
|
|
|
public final List<NoteData> 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<WebsiteData> getWebsiteList() {
|
|
return mWebsiteList;
|
|
}
|
|
|
|
/**
|
|
* @hide this interface may be changed for better support of vCard 4.0 (UID)
|
|
*/
|
|
public final List<VCardEntry> getChildlen() {
|
|
return mChildren;
|
|
}
|
|
|
|
public String getDisplayName() {
|
|
if (mNameData.displayName == null) {
|
|
mNameData.displayName = constructDisplayName();
|
|
}
|
|
return mNameData.displayName;
|
|
}
|
|
|
|
public List<Pair<String, String>> getUnknownXData() {
|
|
return mUnknownXData;
|
|
}
|
|
}
|